changeset 4123:209bdd361615

Merged v4-preview20190726-fixes
author Sascha Wilde <wilde@intevation.de>
date Thu, 01 Aug 2019 14:22:24 +0200
parents b785b6bef578 (diff) 9f03eb3817d6 (current diff)
children acd87a3d246b
files
diffstat 25 files changed, 790 insertions(+), 271 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Thu Aug 01 14:10:25 2019 +0200
+++ b/.hgtags	Thu Aug 01 14:22:24 2019 +0200
@@ -12,3 +12,4 @@
 b166cb97b98a40f33e977c96e65e79bf22e92fca v3.1
 d78af8354b95cea86744459f350edb16662dadd0 v3.1
 5396581cf20334cbc5e69280e5d9b192640d96b9 v4-preview20190717
+aececbc3d04798d905e65196ac0870d081776ca2 v4-preview20190726
--- a/README.md	Thu Aug 01 14:10:25 2019 +0200
+++ b/README.md	Thu Aug 01 14:22:24 2019 +0200
@@ -21,6 +21,21 @@
 For further details see [docs/DEVELOPMENT](docs/DEVELOPMENT.md),
 
 
+## Running Tests
+
+- Running database tests:
+
+  * You will need a PostgreSQL cluster with PostGIS and pgTAP.
+  * To run the tests use the script `./schema/run_tests.sh`.
+  * `./schema/run_tests.sh --help` shows you available options.
+    Per default the script will create (and drop if it already exists)
+    a database named "gemma_test" and all necessary roles in the postgres
+    default cluster (listening on port 5432) and run the tests
+    in that database.
+  * The script must be run as a user with PostgreSQL super user rights.
+    By convention this is "postgres" on most systems.
+
+
 ## Setup Database
 
 - You will need a PostgreSQL cluster with PostGIS.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODOs-historization_ng.md	Thu Aug 01 14:22:24 2019 +0200
@@ -0,0 +1,51 @@
+# TODOs for HNG (Historization NextGen)
+
+## gauges-reference-water-levels
+
+Could be seen as direct part of the gauge data and therefore kept with
+the foll primary key reference...
+
+## Water level diagrams:
+
+One time range might reference different verions of one gauge:
+- How is that hanedled currently?
+- How to handle it in HNG?
+
+## Map display:
+
+Ensure only current gauges are shown in all situations
+- Gauges layer
+- Search
+- more?
+
+## Bottlenecks:
+
+Always associate the correct gauge, when:
+- Uploading Sounding results (how is that currently handled?)
+- Displaying Sounding data
+- Creating/showing X-Cuts
+- Unclear: what constraints shall we chekc for on bottleneck imports?
+
+Cut end of validit _after_ accepting the new one.
+
+## Sounding Results:
+
+- Maybe add a constraint to check, that a ref gauge is available?
+  Maybe not neccessary as import isn't possible anyway, as the
+  reference gauge is needed for the import process... (So the
+  constraint would never catch anything, as no data could be produced
+  without ref gauge) Only upside: we would triger if there is an error
+  in the backend generating SR with invalid ref data.
+- Check during upload that matching bottleneck and reference gauge
+  data is available (and use ist correctly)
+- Use correct reference gauge for X-Cuts
+- (Maybe later) use matching BN data for display
+
+## EVERYTHING ELSE
+
+Not being mentioned here, doesn't mean something is done...
+
+
+# DONE
+
+Stuff done will bemoved moved here for reference:
--- a/client/src/components/gauge/HydrologicalConditions.vue	Thu Aug 01 14:10:25 2019 +0200
+++ b/client/src/components/gauge/HydrologicalConditions.vue	Thu Aug 01 14:22:24 2019 +0200
@@ -241,38 +241,41 @@
     },
     // Diagram legend
     addDiagramLegend(position, offset, color) {
-      let x = offset.x,
-        y = offset.y;
-      let width =
-        (this.pdf.doc.getStringUnitWidth("Long-term Amplitude") * 10) /
-          (72 / 25.6) +
-        5;
+      let x = offset.x + 2, // 2 is the radius of the circle
+        y = offset.y,
+        padding = 3;
+      this.pdf.doc.setFontStyle("normal");
+      this.pdf.doc.setFontSize(10);
+      let width = this.pdf.doc.getTextWidth("Long-term Amplitude") + padding;
       // if position is on the right, x needs to be calculate with pdf width and
       // the size of the element
       if (["topright", "bottomright"].indexOf(position) !== -1) {
         x = this.pdf.width - offset.x - width;
       }
       if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
-        y = this.pdf.height - offset.y - this.getTextHeight(4);
+        y = this.pdf.height - offset.y - this.getTextHeight(5) - 2;
       }
-      this.pdf.doc.setFontSize(10);
+      if (y < this.getTextHeight(1)) {
+        y = y + this.getTextHeight(1) / 2;
+      }
+      console.log(y);
       this.pdf.doc.setTextColor(color);
       this.pdf.doc.setDrawColor("white");
       this.pdf.doc.setFillColor("red");
       this.pdf.doc.circle(x, y, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 1, "" + this.yearCompare);
+      this.pdf.doc.text(x + padding, y + 1, "" + this.yearCompare);
       this.pdf.doc.setFillColor("orange");
       this.pdf.doc.circle(x, y + 5, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 6, "Q25%");
+      this.pdf.doc.text(x + padding, y + 6, "Q25%");
       this.pdf.doc.setFillColor("black");
       this.pdf.doc.circle(x, y + 10, 2, "FD");
       this.pdf.doc.text(x + 3, y + 11, "Median ");
       this.pdf.doc.setFillColor("purple");
       this.pdf.doc.circle(x, y + 15, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 16, "Q75%");
+      this.pdf.doc.text(x + padding, y + 16, "Q75%");
       this.pdf.doc.setFillColor("lightsteelblue");
       this.pdf.doc.circle(x, y + 20, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 21, "Long-term Amplitude");
+      this.pdf.doc.text(x + padding, y + 21, "Long-term Amplitude");
     },
     getPrintLayout(svgHeight) {
       return {
--- a/client/src/components/gauge/Waterlevel.vue	Thu Aug 01 14:10:25 2019 +0200
+++ b/client/src/components/gauge/Waterlevel.vue	Thu Aug 01 14:22:24 2019 +0200
@@ -209,12 +209,10 @@
         this.dateFrom.toLocaleDateString() +
         " - " +
         this.dateTo.toLocaleDateString();
-
       this.generatePDF({
         templateData: this.templateData,
         diagramTitle: diagramTitle
       });
-
       this.pdf.doc.save(
         this.selectedGauge.properties.objname + " Waterlevel-Diagram.pdf"
       );
@@ -247,33 +245,35 @@
     },
     // Diagram legend
     addDiagramLegend(position, offset, color) {
-      let x = offset.x;
-      let y = offset.y;
+      let x = offset.x + 2, // 2 is the radius of the circle
+        y = offset.y,
+        padding = 3;
+      this.pdf.doc.setFontStyle("normal");
       this.pdf.doc.setFontSize(10);
-      let width =
-        (this.pdf.doc.getStringUnitWidth("Navigable Range") * 10) /
-          (72 / 25.6) +
-        5;
+      let width = this.pdf.doc.getTextWidth("Navigable Range") + padding;
       if (["topright", "bottomright"].indexOf(position) !== -1) {
         x = this.pdf.width - offset.x - width;
       }
       if (["bottomright", "bottomleft"].indexOf(position) !== -1) {
-        y = this.pdf.height - offset.y - this.getTextHeight(4);
+        y = this.pdf.height - offset.y - this.getTextHeight(3);
+      }
+      if (y < this.getTextHeight(1)) {
+        y = y + this.getTextHeight(1) / 2;
       }
       this.pdf.doc.setTextColor(color);
       this.pdf.doc.setDrawColor("white");
       this.pdf.doc.setFillColor("steelblue");
       this.pdf.doc.circle(x, y, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 1, "Waterlevel");
+      this.pdf.doc.text(x + padding, y + 1, "Waterlevel");
       this.pdf.doc.setFillColor("#dae6f0");
       this.pdf.doc.circle(x, y + 5, 2, "FD");
       this.pdf.doc.setFillColor("#e5ffe5");
       this.pdf.doc.circle(x, y + 10, 2, "FD");
-      this.pdf.doc.text(x + 3, y + 11, "Navigable Range");
+      this.pdf.doc.text(x + padding, y + 11, "Navigable Range");
       this.pdf.doc.setDrawColor("#90b4d2");
       this.pdf.doc.setFillColor("#90b4d2");
       this.pdf.doc.circle(x, y + 5, 0.6, "FD");
-      this.pdf.doc.text(x + 3, y + 6, "Prediction");
+      this.pdf.doc.text(x + padding, y + 6, "Prediction");
     },
     getPrintLayout(svgHeight) {
       return {
--- a/client/src/lib/mixins.js	Thu Aug 01 14:10:25 2019 +0200
+++ b/client/src/lib/mixins.js	Thu Aug 01 14:22:24 2019 +0200
@@ -174,7 +174,7 @@
       svg2pdf(svg, this.pdf.doc, {
         xOffset: x,
         yOffset: y,
-        scale: 0.354
+        scale: this.pixel2millimeter(1, 80)
       });
       offScreen.removeChild(svg);
     },
--- a/pkg/controllers/bottlenecks.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/controllers/bottlenecks.go	Thu Aug 01 14:22:24 2019 +0200
@@ -48,7 +48,7 @@
   JOIN waterway.fairway_availability fa
     ON efa.fairway_availability_id = fa.id
   JOIN waterway.bottlenecks bn
-    ON fa.bottleneck_id = bn.id
+    ON fa.bottleneck_id = bn.bottleneck_id
   WHERE
     bn.validity @> current_timestamp AND
     bn.objnam = $1 AND
@@ -83,7 +83,7 @@
 FROM waterway.gauges_reference_water_levels grwl
   JOIN waterway.bottlenecks bns
     ON grwl.location = bns.gauge_location
-      AND grwl.validity = bns.gauge_validity
+      AND grwl.validity @> current_timestamp
 WHERE bns.validity @> current_timestamp
   AND bns.objnam = $1
   AND grwl.depth_reference like 'LDC%'
--- a/pkg/controllers/surveys.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/controllers/surveys.go	Thu Aug 01 14:22:24 2019 +0200
@@ -32,9 +32,9 @@
   g.objname AS gauge_objname,
   r.value AS waterlevel_value
 FROM waterway.bottlenecks AS b
+  JOIN waterway.sounding_results AS s ON b.bottleneck_id = s.bottleneck_id
   JOIN waterway.gauges AS g
-    ON b.gauge_location = g.location AND b.gauge_validity = g.validity
-  JOIN waterway.sounding_results AS s ON b.bottleneck_id = s.bottleneck_id
+    ON b.gauge_location = g.location AND s.date_info::timestamptz <@ g.validity
   LEFT JOIN waterway.gauges_reference_water_levels AS r
     ON s.depth_reference = r.depth_reference
       AND g.location = r.location AND g.validity = r.validity
--- a/pkg/imports/agm.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/agm.go	Thu Aug 01 14:22:24 2019 +0200
@@ -169,7 +169,6 @@
 	agmInsertSQL = `
 INSERT INTO waterway.gauge_measurements (
   location,
-  validity,
   measure_date,
   country_code,
   sender,
@@ -182,12 +181,6 @@
   staging_done
 ) VALUES (
   ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int),
-  COALESCE(
-    (SELECT validity FROM waterway.gauges
-       WHERE location
-            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
-         AND validity @> CAST($6 AS timestamp with time zone)),
-    tstzrange(NULL, NULL)),
   $6,
   $7,
   $8,
--- a/pkg/imports/bn.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/bn.go	Thu Aug 01 14:22:24 2019 +0200
@@ -55,7 +55,6 @@
   bottleneck_id,
   validity,
   gauge_location,
-  gauge_validity,
   objnam,
   nobjnm,
   stretch,
@@ -67,11 +66,10 @@
   limiting,
   date_info,
   source_organization
-) SELECT
+) VALUES (
   $1,
-  validity * $2, -- intersections with gauge validity ranges
-  location,
-  validity,
+  $2::tstzrange,
+  isrs_fromText($3),
   $4,
   $5,
   (SELECT r FROM r),
@@ -87,8 +85,7 @@
   $12,
   $13,
   $14
-  FROM waterway.gauges
-  WHERE location = isrs_fromText($3) AND validity && $2
+)
 RETURNING id
 `
 
@@ -102,7 +99,6 @@
   bottleneck_id,
   validity,
   gauge_location,
-  gauge_validity,
   objnam,
   nobjnm,
   stretch,
@@ -114,11 +110,10 @@
   limiting,
   date_info,
   source_organization
-) = ( SELECT
+) = (
   $2,
-  validity * $3, -- intersections with gauge validity ranges
-  location,
-  validity,
+  $3::tstzrange,
+  isrs_fromText($4),
   $5,
   $6,
   (SELECT r FROM r),
@@ -134,8 +129,7 @@
   $13,
   $14::timestamptz,
   $15
-  FROM waterway.gauges
-  WHERE location = isrs_fromText($4) AND validity && $3 )
+)
 WHERE id=$1
 RETURNING id
 `
@@ -151,7 +145,6 @@
   bottleneck_id,
   validity,
   gauge_location,
-  gauge_validity,
   objnam,
   nobjnm,
   stretch,
@@ -165,9 +158,8 @@
   staging_done
 ) = ( SELECT
   $1,
-  validity * $2, -- intersections with gauge validity ranges
-  location,
-  validity,
+  $2::tstzrange,
+  isrs_fromText($3),
   $4,
   $5,
   (SELECT r FROM r),
@@ -179,8 +171,6 @@
   $13::timestamptz,
   $14,
   true
-  FROM waterway.gauges
-  WHERE location = isrs_fromText($3) AND validity && $2
 )
 `
 
@@ -190,14 +180,16 @@
   bottleneck_id,
   validity,
   staging_done
-) = ( SELECT
+) = (
   $1,
-  validity * $2, -- intersections with gauge validity ranges
+  $2::tstzrange,
   true
-  FROM waterway.gauges
-  WHERE location = isrs_fromText($3) AND validity && $2
 )
 `
+	// FIXME: Is this still neede wtih the new simplified historization
+	// model?  My intuition is: no it isn't and should be removed, but we
+	// should double check before doing so... [sw]
+	//
 	// Alignment with gauge validity might have generated new entries
 	// for the same time range. Thus, remove the old ones
 	deleteObsoleteBNSQL = `
@@ -532,7 +524,6 @@
 	err = findMatchingBNStmt.QueryRowContext(ctx,
 		bn.Bottleneck_id,
 		&validity,
-		bn.Fk_g_fid,
 	).Scan(&existing_bn_id)
 	switch {
 	case err == sql.ErrNoRows:
--- a/pkg/imports/fa.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/fa.go	Thu Aug 01 14:22:24 2019 +0200
@@ -4,13 +4,14 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 // License-Filename: LICENSES/AGPL-3.0.txt
 //
-// Copyright (C) 2018 by via donau
+// Copyright (C) 2018,2019 by via donau
 //   – Österreichische Wasserstraßen-Gesellschaft mbH
 // Software engineering by Intevation GmbH
 //
 // Author(s):
 //  * Raimund Renkert <raimund.renkert@intevation.de>
 //  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+//  * Sascha Wilde <wilde@intevation.de>
 
 package imports
 
@@ -67,10 +68,7 @@
   source_organization
 ) VALUES (
   $1,
-  -- Always associate fairway availability data to newest bottleneck
-  -- version to prevent problems in analysis over longer time periods
-  (SELECT id FROM waterway.bottlenecks WHERE bottleneck_id = $2
-     ORDER BY validity DESC FETCH FIRST ROW ONLY),
+  $2,
   $3,
   $4,
   $5,
@@ -309,6 +307,8 @@
 	var faID int64
 	feedback.Info("Found %d fairway availabilities", len(fas))
 	for _, faRes := range fas {
+		// FIXME: The following test is propably unneccessary as already
+		//   done by DB constraints...  [sw]
 		if !bnIds.contains(faRes.Bottleneck_id) {
 			feedback.Warn("Bottleneck %s not found in database.", faRes.Bottleneck_id)
 			continue
--- a/pkg/imports/gm.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/gm.go	Thu Aug 01 14:22:24 2019 +0200
@@ -62,7 +62,6 @@
 	insertGMSQL = `
 INSERT INTO waterway.gauge_measurements (
   location,
-  validity,
   measure_date,
   sender,
   language_code,
@@ -75,12 +74,6 @@
   staging_done
 ) VALUES (
   ($1, $2, $3, $4, $5),
-  COALESCE(
-    (SELECT validity FROM waterway.gauges
-       WHERE location
-            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
-         AND validity @> CAST($6 AS timestamp with time zone)),
-    tstzrange(NULL, NULL)),
   $6,
   $7,
   $8,
@@ -99,7 +92,6 @@
 	insertGPSQL = `
 INSERT INTO waterway.gauge_predictions (
   location,
-  validity,
   measure_date,
   sender,
   language_code,
@@ -112,12 +104,6 @@
   source_organization
 ) VALUES(
   ($1, $2, $3, $4, $5),
-  COALESCE(
-    (SELECT validity FROM waterway.gauges
-       WHERE location
-            = ($1::char(2), $2::char(3), $3::char(5), $4::char(5), $5::int)
-         AND validity @> CAST($6 AS timestamp with time zone)),
-    tstzrange(NULL, NULL)),
   $6,
   $7,
   $8,
--- a/pkg/imports/sr.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/sr.go	Thu Aug 01 14:22:24 2019 +0200
@@ -116,13 +116,11 @@
 	insertHullSQL = `
 INSERT INTO waterway.sounding_results (
   bottleneck_id,
-  bottleneck_validity,
   date_info,
   depth_reference,
   area
 ) SELECT
   bottleneck_id,
-  validity,
   $2::date,
   $3,
   (SELECT
@@ -190,7 +188,7 @@
 FROM waterway.gauges_reference_water_levels grwl
   JOIN waterway.bottlenecks bns
     ON grwl.location = bns.gauge_location
-      AND grwl.validity = bns.gauge_validity
+      AND grwl.validity @> CAST($2 AS timestamptz)
 WHERE bns.objnam = $1
   AND bns.validity @> CAST($2 AS timestamptz)
   AND grwl.depth_reference like 'LDC%'
--- a/pkg/imports/wg.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/imports/wg.go	Thu Aug 01 14:22:24 2019 +0200
@@ -67,7 +67,7 @@
 
 const (
 	eraseObsoleteGaugesSQL = `
-UPDATE waterway.gauges SET erased = true
+UPDATE waterway.gauges SET erased = true, validity = validity - '[now,)'
 WHERE NOT erased
   AND (location).country_code = ANY($1)
   AND isrs_astext(location) <> ALL($2)
--- a/pkg/models/sr.go	Thu Aug 01 14:10:25 2019 +0200
+++ b/pkg/models/sr.go	Thu Aug 01 14:22:24 2019 +0200
@@ -41,7 +41,7 @@
 SELECT EXISTS(SELECT 1
   FROM waterway.bottlenecks bn
     JOIN waterway.gauges g
-      ON bn.gauge_location = g.location AND bn.gauge_validity = g.validity
+      ON bn.gauge_location = g.location AND $3::timestamptz <@ g.validity
     JOIN waterway.gauges_reference_water_levels rl
       ON g.location = rl.location AND g.validity = rl.validity
   WHERE bn.objnam = $1
@@ -85,7 +85,8 @@
 		err = conn.QueryRowContext(ctx,
 			checkDepthReferenceSQL,
 			m.Bottleneck,
-			m.DepthReference).Scan(&b)
+			m.DepthReference,
+			m.Date.Time).Scan(&b)
 		switch {
 		case !b:
 			errs = append(errs,
--- a/schema/auth_tests.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/auth_tests.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -75,11 +75,11 @@
 
 PREPARE bn_insert (varchar, geometry(MULTIPOLYGON, 4326)) AS
     INSERT INTO waterway.bottlenecks (
-        gauge_location, gauge_validity, validity,
+        gauge_location, validity,
         bottleneck_id, stretch, area, rb, lb, responsible_country,
         revisiting_time, limiting, date_info, source_organization)
         SELECT
-            location, validity, validity,
+            location, validity,
             $1,
             isrsrange(('AT', 'XXX', '00001', '00000', 0)::isrs,
                 ('AT', 'XXX', '00001', '00000', 2)::isrs),
--- a/schema/gemma.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/gemma.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -72,122 +72,134 @@
 $$
 LANGUAGE plpgsql;
 
--- Trigger functions to be used as statement-level AFTER triggers,
--- associating time-based referencing objects to matching version
-CREATE OR REPLACE FUNCTION move_gauge_referencing() RETURNS trigger AS
+-- Trigger function to be used as a constraint trigger to enforce
+-- existance of a referenced gauge with intersecting validity.  The
+-- columns with the referenced gauge isrs code an the validity are
+-- given as arguments to the trigger function.
+CREATE OR REPLACE FUNCTION check_valid_gauge() RETURNS trigger AS
 $$
 DECLARE
-    new_bn int;
-    new_bns int[];
+    -- FIXME: I'm using text for the isrs code and cast it on demand.
+    -- If someone is able to get it to work with isrs or isrs_base as
+    -- type, feel free to show me how its done...  ;-) [sw]
+    referenced_gauge text;
+    new_validity tstzrange;
 BEGIN
-    -- Avoid unnecessary execution ON UPDATE if validity did not change
-    IF OLD IS NULL OR NEW.validity <> OLD.validity THEN
-        UPDATE waterway.gauge_measurements
-        SET validity = NEW.validity
-        WHERE location = NEW.location
-            AND measure_date <@ NEW.validity;
-
-        -- build bottleneck validities from intersections with gauge validities
-        FOR new_bn IN
-            INSERT INTO waterway.bottlenecks (
-                    bottleneck_id,
-                    validity,
-                    gauge_location,
-                    gauge_validity,
-                    objnam,
-                    nobjnm,
-                    stretch,
-                    area,
-                    rb,
-                    lb,
-                    responsible_country,
-                    revisiting_time,
-                    limiting,
-                    date_info,
-                    source_organization,
-                    staging_done
-                ) SELECT
-                    b.bottleneck_id,
-                    -- Anticipate non-intersecting gauge validities:
-                    b.validity * CASE WHEN g.validity = NEW.validity
-                        THEN NEW.validity ELSE g.validity - NEW.validity END,
-                    b.gauge_location,
-                    g.validity,
-                    b.objnam,
-                    b.nobjnm,
-                    b.stretch,
-                    b.area,
-                    b.rb,
-                    b.lb,
-                    b.responsible_country,
-                    b.revisiting_time,
-                    b.limiting,
-                    b.date_info,
-                    b.source_organization,
-                    b.staging_done
-                FROM waterway.bottlenecks b JOIN waterway.gauges g
-                    ON b.gauge_location = g.location
-                WHERE b.gauge_location = NEW.location
-                    AND b.validity && NEW.validity
-                    -- Avoid duplicate intersection results:
-                    AND NOT (b.validity <@ NEW.validity
-                        AND g.validity <> NEW.validity)
-            ON CONFLICT (bottleneck_id, validity) DO UPDATE SET
-                -- Associate to new matching gauge version
-                gauge_validity = EXCLUDED.gauge_validity
-            RETURNING id
-        LOOP
-            new_bns = new_bns || new_bn;
-        END LOOP;
-        -- Delete bottleneck versions superseded by new intersections:
-        DELETE FROM waterway.bottlenecks
-        WHERE gauge_location = NEW.location
-            AND validity && NEW.validity
-            AND id <> ALL(new_bns);
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_gauge
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_validity
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.gauges
+                  WHERE location = referenced_gauge::isrs
+                  AND validity && new_validity )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching gauge %s found.',
+                    isrs_AsText(referenced_gauge::isrs)),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
     END IF;
-    RETURN NULL; -- ignored
 END;
 $$
 LANGUAGE plpgsql;
 
-CREATE OR REPLACE FUNCTION move_bottleneck_referencing() RETURNS trigger AS
+-- The same for objects with a timestamp instead of a validity range.
+CREATE OR REPLACE FUNCTION check_valid_gauge_ts() RETURNS trigger AS
 $$
+DECLARE
+    -- FIXME: I'm using text for the isrs code and cast it on demand.
+    -- If someone is able to get it to work with isrs or isrs_base as
+    -- type, feel free to show me how its done...  ;-) [sw]
+    referenced_gauge text;
+    new_tstz timestamptz;
 BEGIN
-    -- Avoid unnecessary execution ON UPDATE if validity did not change
-    IF OLD IS NULL OR NEW.validity <> OLD.validity THEN
-        UPDATE waterway.sounding_results
-        SET bottleneck_validity = NEW.validity
-        WHERE bottleneck_id = NEW.bottleneck_id
-          AND CAST(date_info AS timestamptz) <@ NEW.validity;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_gauge
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_tstz
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.gauges
+                  WHERE location = referenced_gauge::isrs
+                  AND validity @> new_tstz )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching gauge %s found.',
+                    isrs_AsText(referenced_gauge::isrs)),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
+    END IF;
+END;
+$$
+LANGUAGE plpgsql;
 
-        -- Always associate fairway availability data to newest bottleneck
-        -- version to prevent problems in analysis over longer time periods
-        WITH
-        bn AS (SELECT id, validity FROM waterway.bottlenecks
-            WHERE bottleneck_id = NEW.bottleneck_id),
-        latest AS (SELECT id FROM bn
-            -- Candidates are past new validity or just inserted/updated
-            WHERE NOT validity &< NEW.validity OR id = NEW.id
-            ORDER BY upper(validity) DESC FETCH FIRST ROW ONLY)
-        UPDATE waterway.fairway_availability
-        SET bottleneck_id = (SELECT id FROM latest)
-        WHERE bottleneck_id IN(SELECT id FROM bn EXCEPT SELECT id FROM latest);
+-- Trigger function to be used as a constraint trigger to enforce
+-- existance of a referenced bottleneck with validity at a given time.
+-- The columns with the referenced bottleneck id and the timestamp are
+-- given as arguments to the trigger function.
+CREATE OR REPLACE FUNCTION check_valid_bottleneck_ts() RETURNS trigger AS
+$$
+DECLARE
+    referenced_bottleneck_id text;
+    new_tstz timestamptz;
+BEGIN
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_bottleneck_id
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_tstz
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.bottlenecks
+                  WHERE bottleneck_id = referenced_bottleneck_id
+                  AND validity @> new_tstz )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching bottleneck %s for %s found.',
+                    referenced_bottleneck_id, new_tstz),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
     END IF;
-    RETURN NULL; -- ignored
 END;
 $$
 LANGUAGE plpgsql;
 
 -- Constraint trigger: sounding Results must intersect with the area
--- of the bottleneck they belong to.
+-- of the bottleneck they belong to.  The "xx" at the beginning of the
+-- name is to ensure, it is fired last after other triggers.
 CREATE OR REPLACE FUNCTION check_sr_in_bn_area() RETURNS trigger
 LANGUAGE plpgsql
 AS $$
 BEGIN
     IF NOT st_intersects((SELECT area
                           FROM waterway.bottlenecks
-                          WHERE (bottleneck_id, validity)
-                              =(NEW.bottleneck_id, NEW.bottleneck_validity)),
+                          WHERE bottleneck_id = NEW.bottleneck_id
+                            AND validity @> NEW.date_info::timestamptz),
                          NEW.area)
     THEN
         RAISE EXCEPTION
@@ -444,9 +456,6 @@
     CREATE UNIQUE INDEX gauges_erased_unique_constraint
         ON gauges (location)
         WHERE NOT erased
-    -- Associate referencing objects to matching gauge version
-    CREATE TRIGGER move_referencing AFTER INSERT OR UPDATE OF validity
-        ON gauges FOR EACH ROW EXECUTE FUNCTION move_gauge_referencing()
 
     CREATE TABLE gauges_reference_water_levels (
         location isrs NOT NULL,
@@ -462,12 +471,7 @@
     CREATE TABLE gauge_measurements (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
         location isrs NOT NULL,
-        validity tstzrange NOT NULL,
-        CONSTRAINT gauge_key
-            FOREIGN KEY (location, validity) REFERENCES gauges
-            ON UPDATE CASCADE,
         measure_date timestamp with time zone NOT NULL,
-        CHECK (measure_date <@ validity),
         country_code char(2) NOT NULL REFERENCES countries,
         sender varchar NOT NULL, -- "from" element from NtS response
         language_code varchar NOT NULL REFERENCES language_codes,
@@ -479,18 +483,16 @@
         staging_done boolean NOT NULL DEFAULT false,
         UNIQUE (measure_date, location, staging_done)
     )
+    CREATE CONSTRAINT TRIGGER waterway_gauge_measurements_reference_gauge
+        AFTER INSERT OR UPDATE OF location ON gauge_measurements
+        FOR EACH ROW EXECUTE FUNCTION check_valid_gauge_ts('location','measure_date')
     -- For fast retrieval of newest measurement per location:
     CREATE INDEX gauge_measurements_location_measure_date_desc
         ON waterway.gauge_measurements (location, measure_date DESC)
 
     CREATE TABLE gauge_predictions (
         location isrs NOT NULL,
-        validity tstzrange NOT NULL,
-        CONSTRAINT gauge_key
-            FOREIGN KEY (location, validity) REFERENCES gauges
-            ON UPDATE CASCADE,
         measure_date timestamp with time zone NOT NULL,
-        CHECK (measure_date >= lower(validity)),
         country_code char(2) NOT NULL REFERENCES countries,
         sender varchar NOT NULL, -- "from" element from NtS response
         language_code varchar NOT NULL REFERENCES language_codes,
@@ -503,6 +505,9 @@
         source_organization varchar NOT NULL, -- "originator" from NtS response
         PRIMARY KEY (measure_date, location, date_issue)
     )
+    CREATE CONSTRAINT TRIGGER waterway_gauge_predictions_reference_gauge
+        AFTER INSERT OR UPDATE OF location ON gauge_predictions
+        FOR EACH ROW EXECUTE FUNCTION check_valid_gauge_ts('location','measure_date')
 
     CREATE TABLE waterway_axis (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
@@ -648,11 +653,6 @@
         EXCLUDE USING GiST (bottleneck_id WITH =, validity WITH &&)
             DEFERRABLE INITIALLY DEFERRED,
         gauge_location isrs NOT NULL,
-        gauge_validity tstzrange NOT NULL,
-        CHECK(validity <@ gauge_validity),
-        CONSTRAINT gauge_key
-            FOREIGN KEY (gauge_location, gauge_validity) REFERENCES gauges
-                ON UPDATE CASCADE,
         objnam varchar,
         nobjnm varchar,
         stretch isrsrange NOT NULL,
@@ -675,10 +675,9 @@
         source_organization varchar NOT NULL,
         staging_done boolean NOT NULL DEFAULT false
     )
-    -- Associate referencing objects to matching bottleneck version
-    CREATE TRIGGER move_referencing AFTER INSERT OR UPDATE OF validity
-        ON bottlenecks FOR EACH ROW
-        EXECUTE FUNCTION move_bottleneck_referencing()
+    CREATE CONSTRAINT TRIGGER waterway_bottlenecks_reference_gauge
+        AFTER INSERT OR UPDATE OF gauge_location ON bottlenecks
+        FOR EACH ROW EXECUTE FUNCTION check_valid_gauge('gauge_location','validity')
 
     CREATE TABLE bottlenecks_riverbed_materials (
         bottleneck_id int NOT NULL REFERENCES bottlenecks(id)
@@ -691,14 +690,7 @@
     CREATE TABLE sounding_results (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
         bottleneck_id varchar NOT NULL,
-        bottleneck_validity tstzrange NOT NULL,
-        CONSTRAINT bottleneck_key
-            FOREIGN KEY (bottleneck_id, bottleneck_validity)
-                REFERENCES bottlenecks (bottleneck_id, validity)
-                ON UPDATE CASCADE,
         date_info date NOT NULL,
-        CHECK (tstzrange(date_info::timestamptz,
-            date_info::timestamptz + '1 d'::interval) && bottleneck_validity),
         UNIQUE (bottleneck_id, date_info),
         area geography(POLYGON, 4326) NOT NULL
             CHECK(ST_IsValid(CAST(area AS geometry))),
@@ -709,7 +701,11 @@
         octree_index bytea,
         staging_done boolean NOT NULL DEFAULT false
     )
-    CREATE CONSTRAINT TRIGGER sounding_results_in_bn_area
+    CREATE CONSTRAINT TRIGGER a_sounding_results_reference_bottleneck
+        AFTER INSERT OR UPDATE OF bottleneck_id ON sounding_results
+        FOR EACH ROW
+        EXECUTE FUNCTION check_valid_bottleneck_ts('bottleneck_id','date_info')
+    CREATE CONSTRAINT TRIGGER b_sounding_results_in_bn_area
         AFTER INSERT OR UPDATE ON sounding_results
         FOR EACH ROW EXECUTE FUNCTION check_sr_in_bn_area()
 
@@ -728,7 +724,7 @@
     CREATE TABLE fairway_availability (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
         position_code char(2) REFERENCES position_codes,
-        bottleneck_id int NOT NULL REFERENCES bottlenecks(id),
+        bottleneck_id varchar NOT NULL,
         surdat date NOT NULL,
         UNIQUE (bottleneck_id, surdat),
         -- additional_data xml -- Currently not relevant for GEMMA
@@ -736,6 +732,18 @@
         date_info timestamp with time zone NOT NULL,
         source_organization varchar NOT NULL
     )
+    -- FIXME: From the DRC it is unclear what the exact semantics of
+    --   surdat and Date_Info ar unclear.  Currently we assume that
+    --   (fk_bn_fid,surdat) has to be unique, but that might be false.
+    --   Anyway, I will date_info here to check for an matching
+    --   reference gauge at the bottleneck.  The reason for this
+    --   decision is purely practical (and might be semantically
+    --   disputable: the bottleneck data in the demo system is not old
+    --   enough to cover rthe surdat times...
+    CREATE CONSTRAINT TRIGGER fairway_availability_referenced_bottleneck
+        AFTER INSERT OR UPDATE OF bottleneck_id ON fairway_availability
+        FOR EACH ROW
+        EXECUTE FUNCTION check_valid_bottleneck_ts('bottleneck_id','date_info')
 
     CREATE TABLE fa_reference_values (
         fairway_availability_id int NOT NULL REFERENCES fairway_availability,
--- a/schema/gemma_tests.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/gemma_tests.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -30,40 +30,3 @@
     $$,
     23505, NULL,
     'No duplicate geometries can be inserted into waterway_area');
-
-START TRANSACTION;
-CREATE TEMP TABLE new_v (v) AS
-    SELECT tstzrange(current_timestamp - '2 d'::interval,
-        current_timestamp - '12 h'::interval);
-INSERT INTO waterway.gauges (
-    location,
-    validity,
-    objname,
-    geom,
-    zero_point,
-    date_info,
-    source_organization,
-    lastupdate,
-    erased)
-VALUES (
-    ('AT', 'XXX', '00001', 'G0001', 1)::isrs,
-    (SELECT v FROM new_v),
-    'testgauge',
-    ST_geomfromtext('POINT(0 0)', 4326),
-    0,
-    current_timestamp,
-    'testorganization',
-    current_timestamp,
-    true);
--- Fix validity of old entry to match exclusion constraint
-UPDATE waterway.gauges SET
-    validity = validity - (SELECT v FROM new_v)
-WHERE location = ('AT', 'XXX', '00001', 'G0001', 1)::isrs
-    AND validity && (SELECT v FROM new_v)
-    AND NOT erased;
-COMMIT;
-SELECT results_eq($$
-    SELECT count(*) FROM waterway.bottlenecks GROUP BY bottleneck_id;
-    $$,
-    CAST(ARRAY[2,2] AS bigint[]),
-    'Bottlenecks have been split to two new matching gauge versions');
--- a/schema/geoserver_views.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/geoserver_views.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -121,12 +121,12 @@
         g.forecast_accuracy_1d
     FROM waterway.bottlenecks b
         LEFT JOIN waterway.gauges_base_view g
-            ON b.gauge_location = g.location AND b.gauge_validity = g.validity
+            ON b.gauge_location = g.location AND g.validity @> current_timestamp
         LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
                     bottleneck_id, date_info, critical
                 FROM waterway.fairway_availability
                 ORDER BY bottleneck_id, date_info DESC) AS fal
-            ON b.id = fal.bottleneck_id
+            ON b.bottleneck_id = fal.bottleneck_id
         LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
                     bottleneck_id, max(date_info) AS date_max
                 FROM waterway.sounding_results
@@ -218,4 +218,4 @@
             ON sd.subtrahend = srs.id
         JOIN waterway.bottlenecks bn
             ON srm.bottleneck_id = bn.bottleneck_id
-                AND srm.bottleneck_validity = bn.validity;
+                AND srm.date_info::timestamptz <@ bn.validity;
--- a/schema/run_tests.sh	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/run_tests.sh	Thu Aug 01 14:22:24 2019 +0200
@@ -5,35 +5,87 @@
 # SPDX-License-Identifier: AGPL-3.0-or-later
 # License-Filename: LICENSES/AGPL-3.0.txt
 #
-# Copyright (C) 2018 by via donau
+# Copyright (C) 2018, 2019 by via donau
 #   – Österreichische Wasserstraßen-Gesellschaft mbH
 # Software engineering by Intevation GmbH
 #
 # Author(s):
 #  * Tom Gottfried <tom@intevation.de>
 
-dropdb --if-exists gemma_test
+ME=`basename "$0"`
+BASEDIR=`dirname "$0"`
+
+usage()
+{
+  cat <<EOF
+$ME [OPTION]...
+
+Options:
+  -d, --db=NAME    create (and drop if exists) the database NAME.
+                   Default: "gemma_test"
+  -p, --port=PORT  connect do the postgresql cluster at PORT.
+                   Default is the postgresql standard port 5432
+      --help       display this help and exit
+
+EOF
+}
+
+# Defaults:
+
+db=gemma_test
+port=5432
+
+# Parse options:
 
-./install-db.sh -d gemma_test
-psql -qv ON_ERROR_STOP= -c 'CREATE EXTENSION pgtap' -d gemma_test
+OPTS=`getopt \
+      -l help,db:,port: \
+      -o d:p: -n "$ME" -- "$@"`
+[ $? -eq 0 ] || { usage ; exit 1 ; }
+
+eval set -- "$OPTS"
+
+while true ; do
+  case "$1" in
+    --db|-d)
+      db="$2"
+      shift 2
+      ;;
+    --port|-p)
+      port="$2"
+      shift 2
+      ;;
+    --help)
+      { usage ; exit 0 ; }
+      ;;
+    --)
+      shift
+      break
+      ;;
+  esac
+done
+
+dropdb --if-exists -p "$port" "$db"
+
+./install-db.sh -d "$db" -p "$port"
+psql -qv ON_ERROR_STOP= -c 'CREATE EXTENSION pgtap' -d "$db" -p "$port"
 
 # Collect test roles to be dropped
 # Concatenate with dummy role to prevent syntax error if there is no test role
-TEST_ROLES=$(psql -qtc \
+TEST_ROLES=$(psql -d "$db" -p "$port" -qtc \
     "SELECT concat_ws(',', 'test', string_agg(rolname, ',')) FROM pg_roles
          WHERE rolname LIKE 'test%'")
 
 # Drop test roles, add test data and run tests
-psql -qXv ON_ERROR_STOP= -v -d gemma_test \
+psql -qXv ON_ERROR_STOP= -v -d "$db" -p "$port" \
     -c 'SET client_min_messages TO WARNING' \
     -c "DROP ROLE IF EXISTS $TEST_ROLES" \
-    -f tap_tests_data.sql \
-    -c "SELECT plan(71 + (
+    -f "$BASEDIR"/tap_tests_data.sql \
+    -c "SELECT plan(70 + (
             SELECT count(*)::int
                 FROM information_schema.tables
                 WHERE table_schema = 'waterway'))" \
-    -f gemma_tests.sql \
-    -f isrs_tests.sql \
-    -f auth_tests.sql \
-    -f manage_users_tests.sql \
+    -f "$BASEDIR"/gemma_tests.sql \
+    -f "$BASEDIR"/isrs_tests.sql \
+    -f "$BASEDIR"/auth_tests.sql \
+    -f "$BASEDIR"/manage_users_tests.sql \
     -c 'SELECT * FROM finish()'
--- a/schema/tap_tests_data.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/tap_tests_data.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -57,7 +57,7 @@
         current_timestamp,
         'testorganization',
         current_timestamp)
-    RETURNING location, validity, validity),
+    RETURNING location, validity),
 bns AS (
     VALUES (
         'testbottleneck1',
@@ -75,7 +75,7 @@
         1, 'depth', current_timestamp, 'testorganization', true
     ))
 INSERT INTO waterway.bottlenecks (
-    gauge_location, gauge_validity, validity,
+    gauge_location, validity,
     bottleneck_id, stretch, area, rb, lb, responsible_country,
     revisiting_time, limiting, date_info, source_organization, staging_done)
     SELECT * FROM gs, bns;
--- a/schema/update-db.sh	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/update-db.sh	Thu Aug 01 14:22:24 2019 +0200
@@ -21,7 +21,7 @@
 $ME [OPTION]...
 
 Options:
-  -d, --db=NAME    create the database NAME.  Default: "gemma"
+  -d, --db=NAME    update the database NAME.  Default: "gemma"
   -p, --port=PORT  connect do the postgresql cluster at PORT.
                    Default is the postgresql standard port 5432
       --help       display this help and exit
@@ -98,13 +98,17 @@
 
 current_ver=$( get_version )
 
-for d in $BASEDIR/updates/* ; do
+for d in "$BASEDIR"/updates/* ; do
   new_ver=$( basename $d )
   if [ -d "$d" ] && [ "$new_ver" -gt $current_ver ] ; then
     echo "Running updates for $new_ver ..."
 
-    psql -1qv ON_ERROR_STOP= -p "$port" -d "$db" \
-      $(find "$d" -type f -printf ' -f %p') \
+    file_args=""
+    for f in "$d"/* ; do
+      file_args+=" -f $f"
+    done
+
+    psql -1qv ON_ERROR_STOP= -p "$port" -d "$db" $file_args \
       -c "INSERT INTO gemma_schema_version(version) VALUES ($new_ver)"
 
   fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema/updates/1100/01.remove_gauge_validity_refs.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -0,0 +1,197 @@
+-- This is Free Software under GNU Affero General Public License v >= 3.0
+-- without warranty, see README.md and license for details.
+
+-- SPDX-License-Identifier: AGPL-3.0-or-later
+-- License-Filename: LICENSES/AGPL-3.0.txt
+
+-- Copyright (C) 2019 by via donau
+--   – Österreichische Wasserstraßen-Gesellschaft mbH
+-- Software engineering by Intevation GmbH
+
+-- Author(s):
+--  * Sascha Wilde <sascha.wilde@intevation.de>
+
+--
+-- CONSTRAINT FUNCTIONS
+--
+
+-- We still want to ensure, that there is at least a valid gauge at
+-- any time of the referencing objects validity.  To ensure this we
+-- need a trigger constraint:
+CREATE OR REPLACE FUNCTION check_valid_gauge() RETURNS trigger AS
+$$
+DECLARE
+    -- FIXME: I'm using text for the isrs code and cast it on demand.
+    -- If someone is able to get it to work with isrs or isrs_base as
+    -- type, feel free to show me how its done...  ;-) [sw]
+    referenced_gauge text;
+    new_validity tstzrange;
+BEGIN
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_gauge
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_validity
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.gauges
+                  WHERE location = referenced_gauge::isrs
+                  AND validity && new_validity )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching gauge %s found.',
+                    isrs_AsText(referenced_gauge::isrs)),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
+    END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+-- The same for objects with a timestamp instead of a validity range.
+CREATE OR REPLACE FUNCTION check_valid_gauge_ts() RETURNS trigger AS
+$$
+DECLARE
+    -- FIXME: I'm using text for the isrs code and cast it on demand.
+    -- If someone is able to get it to work with isrs or isrs_base as
+    -- type, feel free to show me how its done...  ;-) [sw]
+    referenced_gauge text;
+    new_tstz timestamptz;
+BEGIN
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_gauge
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_tstz
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.gauges
+                  WHERE location = referenced_gauge::isrs
+                  AND validity @> new_tstz )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching gauge %s for %s found.',
+                    (isrs_AsText(referenced_gauge::isrs)), new_tstz),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
+    END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+
+--
+-- BOTTLENECKS
+--
+
+-- Dynamic version of bottlenecks_geoserver view:
+-- Instead of a static reference to a specific gauge data set via
+-- primary key (location, validity) we check for a currently
+-- valid gauge (for the currently valid bottleneck) at executiuon
+-- time.
+CREATE OR REPLACE VIEW waterway.bottlenecks_geoserver AS
+    SELECT
+        b.id,
+        b.bottleneck_id,
+        b.objnam,
+        b.nobjnm,
+        b.stretch,
+        b.area,
+        b.rb,
+        b.lb,
+        b.responsible_country,
+        b.revisiting_time,
+        b.limiting,
+        b.date_info,
+        b.source_organization,
+        g.location AS gauge_isrs_code,
+        g.objname AS gauge_objname,
+        g.reference_water_levels,
+        fal.date_info AS fa_date_info,
+        fal.critical AS fa_critical,
+        g.gm_measuredate,
+        g.gm_waterlevel,
+        g.gm_n_14d,
+        srl.date_max,
+        g.forecast_accuracy_3d,
+        g.forecast_accuracy_1d
+    FROM waterway.bottlenecks b
+        LEFT JOIN waterway.gauges_base_view g
+            ON b.gauge_location = g.location AND g.validity @> current_timestamp
+        LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
+                    bottleneck_id, date_info, critical
+                FROM waterway.fairway_availability
+                ORDER BY bottleneck_id, date_info DESC) AS fal
+            ON b.id = fal.bottleneck_id
+        LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
+                    bottleneck_id, max(date_info) AS date_max
+                FROM waterway.sounding_results
+                GROUP BY bottleneck_id
+                ORDER BY bottleneck_id DESC) AS srl
+            ON b.bottleneck_id = srl.bottleneck_id
+    WHERE b.validity @> current_timestamp;
+
+-- As we resolve the correct gauge data to use on runtime, we drop the
+-- hard reference to the gauges vaidity:
+ALTER TABLE waterway.bottlenecks DROP IF EXISTS gauge_validity;
+
+DROP TRIGGER IF EXISTS waterway_bottlenecks_reference_gauge
+    ON waterway.bottlenecks;
+CREATE CONSTRAINT TRIGGER waterway_bottlenecks_reference_gauge
+    AFTER INSERT OR UPDATE OF gauge_location ON waterway.bottlenecks
+    FOR EACH ROW EXECUTE FUNCTION check_valid_gauge('gauge_location','validity');
+
+
+--
+-- GAUGE MEASUREMENTS
+--
+
+-- As we resolve the correct gauge data to use on runtime, we drop the
+-- hard reference to the gauges vaidity:
+ALTER TABLE waterway.gauge_measurements DROP IF EXISTS validity;
+
+DROP TRIGGER IF EXISTS waterway_gauge_measurements_reference_gauge
+    ON waterway.gauge_measurements;
+CREATE CONSTRAINT TRIGGER waterway_gauge_measurements_reference_gauge
+    AFTER INSERT OR UPDATE OF location ON waterway.gauge_measurements
+    FOR EACH ROW EXECUTE FUNCTION check_valid_gauge_ts('location','measure_date');
+
+ALTER TABLE waterway.gauge_predictions DROP IF EXISTS validity;
+
+DROP TRIGGER IF EXISTS waterway_gauge_predictions_reference_gauge
+    ON waterway.gauge_predictions;
+CREATE CONSTRAINT TRIGGER waterway_gauge_predictions_reference_gauge
+    AFTER INSERT OR UPDATE OF location ON waterway.gauge_predictions
+    FOR EACH ROW EXECUTE FUNCTION check_valid_gauge_ts('location','measure_date');
+
+--
+-- DROP NO LONGER NEEDED TRIGGER FUNCTION
+--
+
+-- This used to update foreign key references.  As these references no
+-- longer exist we dont need this magic any more...
+DROP TRIGGER IF EXISTS move_referencing ON waterway.gauges;
+DROP FUNCTION IF EXISTS move_gauge_referencing();
+
+
+--
+-- ADDITIONAL NOTES
+--
+-- waterway.gauges_reference_water_levels still has a hard foreign key
+-- reference to waterway.gauges.  As this data actually is part of the
+-- gauges data and the seperation in two tables is exclusively due to
+-- schema modeling we leave it alone!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema/updates/1100/02.remove_bottleneck_validity_refs.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -0,0 +1,256 @@
+-- This is Free Software under GNU Affero General Public License v >= 3.0
+-- without warranty, see README.md and license for details.
+
+-- SPDX-License-Identifier: AGPL-3.0-or-later
+-- License-Filename: LICENSES/AGPL-3.0.txt
+
+-- Copyright (C) 2019 by via donau
+--   – Österreichische Wasserstraßen-Gesellschaft mbH
+-- Software engineering by Intevation GmbH
+
+-- Author(s):
+--  * Sascha Wilde <sascha.wilde@intevation.de>
+
+--
+-- CONSTRAINT FUNCTIONS
+--
+
+-- We still want to ensure, that there is at least a valid bottleneck
+-- at any time of the referencing objects validity.  To ensure this we
+-- need a trigger constraint:
+CREATE OR REPLACE FUNCTION check_valid_bottleneck_ts() RETURNS trigger AS
+$$
+DECLARE
+    referenced_bottleneck_id text;
+    new_tstz timestamptz;
+BEGIN
+    EXECUTE format('SELECT $1.%I', TG_ARGV[0])
+        INTO referenced_bottleneck_id
+        USING NEW;
+    EXECUTE format('SELECT $1.%I', TG_ARGV[1])
+        INTO new_tstz
+        USING NEW;
+    IF EXISTS ( SELECT * FROM waterway.bottlenecks
+                  WHERE bottleneck_id = referenced_bottleneck_id
+                  AND validity @> new_tstz )
+    THEN
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = format('No matching bottleneck %s for %s found.',
+                    referenced_bottleneck_id, new_tstz),
+                ERRCODE = 23505,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                COLUMN = TG_ARGV[0],
+                CONSTRAINT = TG_NAME;
+    END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+-- Redifine constraint trigger: sounding Results must intersect with
+-- the area of the bottleneck they belong to.  Bottleneck is
+-- determined dynamically via date_info.
+CREATE OR REPLACE FUNCTION check_sr_in_bn_area() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    IF NOT st_intersects((SELECT area
+                          FROM waterway.bottlenecks
+                          WHERE bottleneck_id = NEW.bottleneck_id
+                            AND validity @> NEW.date_info::timestamptz),
+                         NEW.area)
+    THEN
+        RAISE EXCEPTION
+            'new row for relation "%" violates constraint trigger "%"',
+                TG_TABLE_NAME, TG_NAME
+            USING
+                DETAIL = 'Failing row area has no intersection with bottleneck.',
+                ERRCODE = 23514,
+                SCHEMA = TG_TABLE_SCHEMA,
+                TABLE = TG_TABLE_NAME,
+                CONSTRAINT = TG_NAME;
+    END IF;
+    RETURN NEW;
+END;
+$$;
+
+
+--
+-- SOUNDING RESULTS
+--
+
+-- Dynamic version of sounding_differences geoserver view: Instead of
+-- a static reference to a specific bottleneck data set via primary
+-- key (id, validity) we check for a bottleneck valid at the time of
+-- the survey at executiuon time.
+CREATE OR REPLACE VIEW waterway.sounding_differences AS
+    SELECT
+        sd.id           AS id,
+        bn.objnam       AS objnam,
+        srm.date_info   AS minuend,
+        srs.date_info   AS subtrahend,
+        sdcl.height     AS height,
+        CAST(sdcl.lines AS geometry(multilinestring, 4326)) AS lines
+    FROM caching.sounding_differences sd
+        JOIN caching.sounding_differences_contour_lines sdcl
+            ON sd.id = sdcl.sounding_differences_id
+        JOIN waterway.sounding_results srm
+            ON sd.minuend = srm.id
+        JOIN waterway.sounding_results srs
+            ON sd.subtrahend = srs.id
+        JOIN waterway.bottlenecks bn
+            ON srm.bottleneck_id = bn.bottleneck_id
+                AND srm.date_info::timestamptz <@ bn.validity;
+
+-- As we resolve the correct gauge data to use on runtime, we drop the
+-- hard reference to the bottlenecks vaidity:
+ALTER TABLE waterway.sounding_results DROP IF EXISTS bottleneck_validity;
+
+-- Note, we now use prefixed names, to ensure correct execution order
+-- for the triggers...
+DROP TRIGGER IF EXISTS a_sounding_results_reference_bottleneck
+    ON waterway.sounding_results;
+CREATE CONSTRAINT TRIGGER a_sounding_results_reference_bottleneck
+    AFTER INSERT OR UPDATE OF bottleneck_id ON waterway.sounding_results
+    FOR EACH ROW
+    EXECUTE FUNCTION check_valid_bottleneck_ts('bottleneck_id','date_info');
+
+DROP TRIGGER IF EXISTS sounding_results_in_bn_area
+    ON waterway.sounding_results;
+DROP TRIGGER IF EXISTS b_sounding_results_in_bn_area
+    ON waterway.sounding_results;
+CREATE CONSTRAINT TRIGGER b_sounding_results_in_bn_area
+    AFTER INSERT OR UPDATE ON waterway.sounding_results
+    FOR EACH ROW EXECUTE FUNCTION check_sr_in_bn_area();
+
+
+--
+-- FAIRWAY AVAILABILITY
+--
+
+-- The current implementation associates fairway_availability data
+-- hard with the internal id of an bottleneck.  Lets use the
+-- bottleneck_id (official unique id) instead and to the lookup of an
+-- matching bottleneck for a specific date on demand.
+
+-- Dear reader: The gemma schema update scripts are intended to be run
+--   only once, which is implemented via schema verioning and the
+--   update-db.sh script.  None the less it is very helpful to be able
+--   to run scripts more than once during development and testing,
+--   without harm being done.  Thats the reason for the following code
+--   to be, like it is:
+--
+-- This migration can only be done once, thanks to psql magic we are
+-- able to guard it approprieatly.
+
+SELECT data_type='integer' AS old_fwa_bnid
+    FROM information_schema.columns
+    WHERE table_schema = 'waterway'
+        AND table_name='fairway_availability'
+        AND column_name='bottleneck_id';
+\gset
+\if :old_fwa_bnid
+  \qecho 'Migrating bottleneck_id column in fairway_availability to text id.'
+  -- We temporarily keep the old if field for the migration of existing data:
+  ALTER TABLE waterway.fairway_availability
+      RENAME COLUMN bottleneck_id TO old_bnid;
+  ALTER TABLE waterway.fairway_availability
+      ADD COLUMN bottleneck_id varchar;
+
+  -- Migrate existing data: Let's be ultra permissive and make the
+  -- migration _before adding the constraint triggger.
+  UPDATE waterway.fairway_availability AS fwa
+      SET bottleneck_id = b.bottleneck_id
+      FROM waterway.bottlenecks b
+      WHERE b.id = fwa.old_bnid;
+  ALTER TABLE waterway.fairway_availability
+      ALTER COLUMN bottleneck_id SET NOT NULL;
+
+  -- Set constraint trigger to make sure a matching BN exists:
+  --
+  -- FIXME: From the DRC it is unclear what the exact semantics of
+  --   surdat and Date_Info ar unclear.  Currently we assume that
+  --   (fk_bn_fid,surdat) has to be unique, but that might be false.
+  --   Anyway, I will date_info here to check for an matching
+  --   reference gauge at the bottleneck.  The reason for this
+  --   decision is purely practical (and might be semantically
+  --   disputable: the bottleneck data in the demo system is not old
+  --   enough to cover rthe surdat times...
+  CREATE CONSTRAINT TRIGGER fairway_availability_referenced_bottleneck
+      AFTER INSERT OR UPDATE OF bottleneck_id ON waterway.fairway_availability
+      FOR EACH ROW
+      EXECUTE FUNCTION check_valid_bottleneck_ts('bottleneck_id','date_info');
+  -- Set NOT NULL constraint for new column
+  ALTER TABLE waterway.fairway_availability
+      ALTER COLUMN bottleneck_id SET NOT NULL;
+
+  -- The change also effects the geoserver bottlenecks view, which
+  -- joined in fairway_availability.  We leave the rather fuzzy match
+  -- (not using any validity time match), as it is unclear what the
+  -- validity period of fwa data is.
+  CREATE OR REPLACE VIEW waterway.bottlenecks_geoserver AS
+      SELECT
+          b.id,
+          b.bottleneck_id,
+          b.objnam,
+          b.nobjnm,
+          b.stretch,
+          b.area,
+          b.rb,
+          b.lb,
+          b.responsible_country,
+          b.revisiting_time,
+          b.limiting,
+          b.date_info,
+          b.source_organization,
+          g.location AS gauge_isrs_code,
+          g.objname AS gauge_objname,
+          g.reference_water_levels,
+          fal.date_info AS fa_date_info,
+          fal.critical AS fa_critical,
+          g.gm_measuredate,
+          g.gm_waterlevel,
+          g.gm_n_14d,
+          srl.date_max,
+          g.forecast_accuracy_3d,
+          g.forecast_accuracy_1d
+      FROM waterway.bottlenecks b
+          LEFT JOIN waterway.gauges_base_view g
+              ON b.gauge_location = g.location AND g.validity @> current_timestamp
+          LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
+                      bottleneck_id, date_info, critical
+                  FROM waterway.fairway_availability
+                  ORDER BY bottleneck_id, date_info DESC) AS fal
+              ON b.bottleneck_id = fal.bottleneck_id
+          LEFT JOIN (SELECT DISTINCT ON (bottleneck_id)
+                      bottleneck_id, max(date_info) AS date_max
+                  FROM waterway.sounding_results
+                  GROUP BY bottleneck_id
+                  ORDER BY bottleneck_id DESC) AS srl
+              ON b.bottleneck_id = srl.bottleneck_id
+      WHERE b.validity @> current_timestamp;
+
+  -- Finally dropt the old column
+  ALTER TABLE waterway.fairway_availability
+      DROP COLUMN old_bnid;
+  ALTER TABLE waterway.fairway_availability
+      ADD CONSTRAINT fairway_availability_bottleneck_id_surdat_key
+      UNIQUE (bottleneck_id, surdat);
+\else
+  \qecho 'NOTICE: bottleneck_id column in fairway_availability alread migrated.'
+\endif
+
+
+--
+-- DROP NO LONGER NEEDED TRIGGER FUNCTION
+--
+
+-- This used to update foreign key references.  As these references no
+-- longer exist we dont need this magic any more...
+DROP TRIGGER IF EXISTS move_referencing ON waterway.bottlenecks;
+DROP FUNCTION IF EXISTS move_bottleneck_referencing();
--- a/schema/version.sql	Thu Aug 01 14:10:25 2019 +0200
+++ b/schema/version.sql	Thu Aug 01 14:22:24 2019 +0200
@@ -1,1 +1,1 @@
-INSERT INTO gemma_schema_version(version) VALUES (1010);
+INSERT INTO gemma_schema_version(version) VALUES (1100);