-- ============================================================================= -- Phase 6: Mandantentrennung, Koordinaten und die Indizes für die Karte. -- -- Vier Dinge: -- -- 1. Row Level Security für die acht neuen Tabellen. -- -- 2. Koordinaten doppelt halten. Prisma kann den PostGIS-Typ nicht lesen, -- also stehen Dezimalgrade daneben — und ein Trigger sorgt dafür, dass -- beide nie auseinanderlaufen. Dasselbe Muster wie beim Einsatz aus -- Phase 3, jetzt auch für Standort, Hydrant, Objekt und Foerderpunkt. -- -- 3. GiST-Indizes auf den Punkten. Ohne sie ist die Umkreissuche ein -- vollstaendiger Durchlauf der Tabelle — bei tausend Hydranten im -- Ausrueckegebiet auf der Anfahrt zu langsam. -- -- 4. Volltextsuche ueber Objekte: der Name allein genuegt nicht, gesucht -- wird auch nach Strasse, Gefahren und Ansprechpartner. -- ============================================================================= -- ----------------------------------------------------------------------------- -- Row Level Security -- ----------------------------------------------------------------------------- DO $$ DECLARE tabelle text; BEGIN FOREACH tabelle IN ARRAY ARRAY[ 'hydrant', 'objekt', 'objektkontakt', 'objektplan', 'lagekarte', 'lageelement', 'wasserfoerderung', 'foerderpunkt' ] LOOP EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', tabelle); EXECUTE format( 'CREATE POLICY %I ON %I FOR ALL ' 'USING ("organisationId" = aktuelle_organisation()) ' 'WITH CHECK ("organisationId" = aktuelle_organisation())', tabelle || '_mandant', tabelle ); END LOOP; END; $$; -- ----------------------------------------------------------------------------- -- Koordinaten: Dezimalgrad und PostGIS-Punkt zusammenhalten -- -- Eine Funktion fuer alle Tabellen: sie erwartet die Spalten "breitengrad", -- "laengengrad" und "koordinaten" und laesst sich damit an jede Tabelle -- haengen, die einen Ort hat. -- ----------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION koordinaten_setzen() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE breite double precision; laenge double precision; BEGIN breite := (to_jsonb(NEW) ->> 'breitengrad')::double precision; laenge := (to_jsonb(NEW) ->> 'laengengrad')::double precision; IF breite IS NULL OR laenge IS NULL THEN NEW := jsonb_populate_record(NEW, jsonb_build_object('koordinaten', NULL)); RETURN NEW; END IF; -- Reihenfolge beachten: ST_MakePoint nimmt erst die Laenge, dann die Breite. NEW := jsonb_populate_record( NEW, jsonb_build_object( 'koordinaten', ST_AsEWKT(ST_SetSRID(ST_MakePoint(laenge, breite), 4326)::geography) ) ); RETURN NEW; END; $$; COMMENT ON FUNCTION koordinaten_setzen() IS 'Haelt den PostGIS-Punkt einer Tabelle mit ihren Dezimalgraden im Gleichlauf.'; CREATE TRIGGER standort_koordinaten BEFORE INSERT OR UPDATE OF "breitengrad", "laengengrad" ON "standort" FOR EACH ROW EXECUTE FUNCTION koordinaten_setzen(); CREATE TRIGGER hydrant_koordinaten BEFORE INSERT OR UPDATE OF "breitengrad", "laengengrad" ON "hydrant" FOR EACH ROW EXECUTE FUNCTION koordinaten_setzen(); CREATE TRIGGER objekt_koordinaten BEFORE INSERT OR UPDATE OF "breitengrad", "laengengrad" ON "objekt" FOR EACH ROW EXECUTE FUNCTION koordinaten_setzen(); -- ----------------------------------------------------------------------------- -- Koordinaten, die es auf der Erde nicht gibt, sind ein Tippfehler -- ----------------------------------------------------------------------------- ALTER TABLE "hydrant" ADD CONSTRAINT "hydrant_koordinaten_gueltig" CHECK ( "breitengrad" >= -90 AND "breitengrad" <= 90 AND "laengengrad" >= -180 AND "laengengrad" <= 180 ); ALTER TABLE "objekt" ADD CONSTRAINT "objekt_koordinaten_gueltig" CHECK ( ("breitengrad" IS NULL OR ("breitengrad" >= -90 AND "breitengrad" <= 90)) AND ("laengengrad" IS NULL OR ("laengengrad" >= -180 AND "laengengrad" <= 180)) ); ALTER TABLE "standort" ADD CONSTRAINT "standort_koordinaten_gueltig" CHECK ( ("breitengrad" IS NULL OR ("breitengrad" >= -90 AND "breitengrad" <= 90)) AND ("laengengrad" IS NULL OR ("laengengrad" >= -180 AND "laengengrad" <= 180)) ); ALTER TABLE "foerderpunkt" ADD CONSTRAINT "foerderpunkt_koordinaten_gueltig" CHECK ( "breitengrad" >= -90 AND "breitengrad" <= 90 AND "laengengrad" >= -180 AND "laengengrad" <= 180 ); -- Eine Foerderstrecke ohne Wasser oder mit einem Schlauch, den es nicht gibt. ALTER TABLE "wasserfoerderung" ADD CONSTRAINT "wasserfoerderung_sinnvoll" CHECK ( "durchmesser" IN (52, 75, 110) AND "foerdermenge" > 0 AND "foerdermenge" <= 10000 AND "ausgangsdruck" > 0 AND "ausgangsdruck" <= 16 AND "eingangsdruck" >= 0 AND "eingangsdruck" < "ausgangsdruck" ); -- ----------------------------------------------------------------------------- -- Indizes fuer die Umkreissuche -- -- GiST auf der Geografie: damit beantwortet Postgres "was liegt im Umkreis von -- 300 Metern" ueber den Index statt ueber die ganze Tabelle. -- ----------------------------------------------------------------------------- CREATE INDEX "hydrant_umkreis" ON "hydrant" USING GIST ("koordinaten") WHERE "geloeschtAm" IS NULL; CREATE INDEX "objekt_umkreis" ON "objekt" USING GIST ("koordinaten") WHERE "geloeschtAm" IS NULL; -- ----------------------------------------------------------------------------- -- Volltextsuche ueber Objekte -- -- Trigram wie bei den Geraeten aus Phase 1: gesucht wird mit Tippfehlern und -- Wortfragmenten, nicht mit vollstaendigen Woertern. -- ----------------------------------------------------------------------------- CREATE INDEX "objekt_suche_name" ON "objekt" USING GIN ("name" gin_trgm_ops); CREATE INDEX "objekt_suche_strasse" ON "objekt" USING GIN ("strasse" gin_trgm_ops); CREATE INDEX "objekt_suche_gefahren" ON "objekt" USING GIN ("gefahren" gin_trgm_ops); CREATE INDEX "hydrant_suche_bezeichnung" ON "hydrant" USING GIN ("bezeichnung" gin_trgm_ops); CREATE INDEX "hydrant_suche_strasse" ON "hydrant" USING GIN ("strasse" gin_trgm_ops); -- ----------------------------------------------------------------------------- -- Rechte fuer die Anwendungsrolle -- ----------------------------------------------------------------------------- DO $$ BEGIN IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'florian_app') THEN EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO florian_app'; EXECUTE 'GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO florian_app'; EXECUTE 'REVOKE INSERT, UPDATE, DELETE ON "berechtigung" FROM florian_app'; EXECUTE 'REVOKE UPDATE, DELETE ON "audit_log" FROM florian_app'; EXECUTE 'REVOKE UPDATE, DELETE ON "kiosk_vorgang" FROM florian_app'; EXECUTE 'REVOKE UPDATE, DELETE ON "druckmessung" FROM florian_app'; EXECUTE 'REVOKE UPDATE, DELETE ON "atemschutz_ereignis" FROM florian_app'; ELSE RAISE NOTICE 'Rolle florian_app nicht vorhanden — Rechtevergabe uebersprungen.'; END IF; END; $$;