From 2471f53aa41812ac9b7e92bc7505aa2536da9633 Mon Sep 17 00:00:00 2001
From: Quazgar <quazgar@posteo.de>
Date: Tue, 7 Apr 2020 14:02:10 +0000
Subject: [PATCH] Fix self-inheritance in cache table

---
 patches/utils/patch_header.sh        | 17 ++++++-
 procedures/deleteEntity.sql          | 12 ++++-
 procedures/deleteIsaCache.sql        | 14 ++++++
 procedures/insertEntity.sql          | 16 +++++++
 procedures/insertIsaCache.sql        | 72 ++++++++++++++++++----------
 procedures/retrieveEntityParents.sql | 33 +++++++++++--
 update_sql_procedures.sh             | 16 ++++++-
 7 files changed, 145 insertions(+), 35 deletions(-)

diff --git a/patches/utils/patch_header.sh b/patches/utils/patch_header.sh
index fec3997..9fd7d05 100644
--- a/patches/utils/patch_header.sh
+++ b/patches/utils/patch_header.sh
@@ -4,7 +4,8 @@
 #
 # Copyright (C) 2018 Research Group Biomedical Physics,
 # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
-# Copyright 2019 Daniel Hornung
+# Copyright (C) 2019-2020 Daniel Hornung
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -101,7 +102,8 @@ while test $# -gt 0; do
  esac
 done
 
-source "$ENV_FILE" || true
+[[ -e "$ENV_FILE" ]] && source "$ENV_FILE" \
+        || echo "No ENV_FILE in environment or not found: $ENV_FILE"
 
 if [[ -z "$DATABASE_NAME" && -z "$MYSQL_CONNECTION" ]]
 then
@@ -187,6 +189,17 @@ function mysql_execute {
 	set -e
 }
 
+# Takes a filename with commands instead of command line arguments
+function mysql_execute_file {
+	set +e
+	$CMD_MYSQL $MYSQL_CONNECTION < "$1"
+	ret=${PIPESTATUS[0]}
+	if [ "$ret" -ne 0 ]; then
+		failure "MYSQL ERROR"
+	fi
+	set -e
+}
+
 function redo_table {
 	  $CMD_MYSQL $MYSQL_CONNECTION < ${DATABASE_NAME}.${1}.${OLD_VERSION}.dump.sql
 }
diff --git a/procedures/deleteEntity.sql b/procedures/deleteEntity.sql
index c83b1d1..0a70f20 100644
--- a/procedures/deleteEntity.sql
+++ b/procedures/deleteEntity.sql
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -21,11 +22,18 @@
  * ** end header
  */
 
+/* Delete a (sparse) Entity from the database.
 
+Note that all properties, including is-a relationships of this Entity should
+have been deleted before.  This can be done for example with the
+`deleteEntityProperties` procedure.
 
+Parameters
+==========
 
-
-
+EntityID : UNSIGNED
+The ID of the Entity.
+*/
 
 DROP PROCEDURE IF EXISTS db_2_0.deleteEntity;
 delimiter //
diff --git a/procedures/deleteIsaCache.sql b/procedures/deleteIsaCache.sql
index 57bb651..6713d4a 100644
--- a/procedures/deleteIsaCache.sql
+++ b/procedures/deleteIsaCache.sql
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -25,6 +26,19 @@ SET GLOBAL log_bin_trust_function_creators = 1;
 DROP PROCEDURE IF EXISTS db_2_0.deleteIsa;
 DELIMITER //
 
+/* Delete "is a" relations from the given entity towards ancestors.
+
+Note that relations towards descendants are not deleted (they probably should have been deleted before).
+
+After this procedure, there are no more entries in `isa_cache`, where the
+parameter entity is a child or inside the rpath.
+
+Parameters
+==========
+
+EntityID : UNSIGNED
+Child entity for which all parental relations should be deleted.
+*/
 CREATE PROCEDURE db_2_0.deleteIsa(IN EntityID INT UNSIGNED)
 BEGIN
 	
diff --git a/procedures/insertEntity.sql b/procedures/insertEntity.sql
index 5b05d36..8e307a8 100644
--- a/procedures/insertEntity.sql
+++ b/procedures/insertEntity.sql
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -24,6 +25,21 @@
 
 DROP PROCEDURE IF EXISTS db_2_0.insertEntity;
 delimiter //
+/* Insert an Entity
+
+Parameters
+==========
+
+EntityName : VARCHAR(255)
+
+EntityDesc : TEXT
+
+EntityRole : VARCHAR(255)
+Currently one of 'RECORDTYPE', 'RECORD', 'FILE', 'DOMAIN', 'PROPERTY',
+'DATATYPE', 'ROLE', 'QUERYTEMPLATE'
+
+ACL : VARBINARY(65525)
+*/
 CREATE PROCEDURE db_2_0.insertEntity(in EntityName VARCHAR(255), in EntityDesc TEXT, in EntityRole VARCHAR(255), in ACL VARBINARY(65525))
 BEGIN
     DECLARE NewEntityID INT UNSIGNED DEFAULT NULL;
diff --git a/procedures/insertIsaCache.sql b/procedures/insertIsaCache.sql
index 735ba12..aacb4cd 100644
--- a/procedures/insertIsaCache.sql
+++ b/procedures/insertIsaCache.sql
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,42 +24,61 @@
 DROP PROCEDURE IF EXISTS db_2_0.insertIsa;
 DELIMITER //
 
+/* Insert an "is a" relation
+
+This procedure fills the isa_cache table.  All passed entities must be existing
+in entities.id.
+
+Parameters
+==========
+
+c : UNSIGNED
+The child entity
+
+p : UNSIGNED
+The parent entity
+*/
 CREATE PROCEDURE db_2_0.insertIsa(IN c INT UNSIGNED, IN p INT UNSIGNED)
-BEGIN
+insert_is_a_proc: BEGIN
 
-    INSERT INTO isa_cache (child, parent, rpath) VALUES (c,p,c);
-	
-    -- foreach supertype of p
+    INSERT INTO isa_cache (child, parent, rpath) VALUES (c, p, c);
+
+    IF p = c THEN
+        -- Any additional entries would be redundant.
+        LEAVE insert_is_a_proc;
+    END IF;
+
+    -- Insert ancestors older than parents:
+    -- for each supertype of p
     --     INSERT (c, supertype, p);
     INSERT IGNORE INTO isa_cache SELECT
         c
-            AS child,
+            AS child,   -- Current child
         i.parent
-            AS parent,
-        IF(p=i.rpath or i.rpath=parent,
-           p,
-           concat(p, ">", i.rpath))
+            AS parent,  -- Existing supertype
+        IF(p=i.rpath or i.rpath=parent,  -- If ancestor=parent or parent = grandparent:
+           p,                            -- New parent (directly)
+           concat(p, ">", i.rpath))      -- Else "p>super.rpath"
             AS rpath
-        FROM isa_cache AS i WHERE i.child = p;
-    
+        FROM isa_cache AS i WHERE i.child = p;  -- Select rows with supertype
     
-    -- foreach subtype of c insert each supertype of p
+    -- Propagate to descendants:
+    -- for each subtype of c: insert each supertype of p
     INSERT IGNORE INTO isa_cache SELECT
-        l.child,
-        r.parent,
-        if(l.rpath=l.child and r.rpath=c,
-           c,
-           concat(if(l.rpath=l.child,
-                     c,
-                     concat(l.rpath, '>', c)),
-                  if(r.rpath=c,
-                     '',
-                     concat('>', r.rpath))))
+        l.child,    -- Descendant as found in isa_cache
+        r.parent,   -- Ancestor as found in isa_cache
+        if(l.rpath=l.child and r.rpath=c,  -- if distance=1 for left and right:
+           c,                              -- rpath = current child
+           concat(if(l.rpath=l.child,        -- if dist=1 for descendant:
+                     c,                         -- rpath starts with c
+                     concat(l.rpath, '>', c)),  -- rpath starts with "desc.rpath > c"
+                  if(r.rpath=c,              -- if dist=1 for ancestor
+                     '',                        -- rpath is finished
+                     concat('>', r.rpath))))    -- rpath continuees with " > ancest.rpath"
             AS rpath
-        FROM isa_cache as l
-            INNER JOIN isa_cache as r ON (l.parent = r.child AND l.parent=c);
-
-
+        FROM
+            isa_cache as l INNER JOIN isa_cache as r
+            ON (l.parent = c AND c = r.child); -- Left: descendants of c, right: ancestors
 	
 END;
 //
diff --git a/procedures/retrieveEntityParents.sql b/procedures/retrieveEntityParents.sql
index 9b99c36..ffd9f13 100644
--- a/procedures/retrieveEntityParents.sql
+++ b/procedures/retrieveEntityParents.sql
@@ -4,6 +4,7 @@
  *
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -23,8 +24,33 @@
 
 
 DROP PROCEDURE IF EXISTS db_2_0.retrieveEntityParents;
-delimiter //
+DELIMITER //
 
+/* Retrieve the parents of an Entity.
+
+Parameters
+==========
+
+EntityID : UNSIGNED
+Child entity for which all parental relations should be deleted.
+
+Returns
+=======
+ParentID : INT UNSIGNED
+  Each parent's ID
+
+ParentName :
+  The parent's name.
+
+ParentDescription :
+  The parent's description.
+
+ParentRole :
+  The parent's Role.
+
+ACL :
+  Access control list something
+*/
 CREATE PROCEDURE db_2_0.retrieveEntityParents(in EntityID INT UNSIGNED)
 BEGIN
 
@@ -32,6 +58,7 @@ SELECT parent AS ParentID, name AS ParentName, description AS ParentDescription,
 
 
 
+END
+//
 
-END//
-delimiter ;
+DELIMITER ;
diff --git a/update_sql_procedures.sh b/update_sql_procedures.sh
index 7b0b457..22f823a 100755
--- a/update_sql_procedures.sh
+++ b/update_sql_procedures.sh
@@ -5,6 +5,7 @@
 #
 # Copyright (C) 2018 Research Group Biomedical Physics,
 # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -29,11 +30,22 @@ source patches/utils/patch_header.sh
 echo -n "updating rules ... "
 set -e
 
-mysql_execute "$(sed s/db_2_0/$DATABASE_NAME/g rules.sql)"
+temp_rules_sql=$(tempfile --suffix=.sql)
+sed s/db_2_0/$DATABASE_NAME/g rules.sql > "$temp_rules_sql"
+mysql_execute_file "$temp_rules_sql"
+# We keep this for debugging purposes...
+# rm "$temp_rules_sql"
+
 echo "[OK]"
 
 echo -n "updating procedures ... "
-mysql_execute "$(sed s/db_2_0/$DATABASE_NAME/g procedures/*.sql procedures/query/*.sql)" 
+temp_proc_sql=$(tempfile --suffix=.sql)
+sed -e "s/db_2_0/$DATABASE_NAME/g" procedures/*.sql procedures/query/*.sql \
+    > "$temp_proc_sql"
+mysql_execute_file "$temp_proc_sql"
+# We keep this for debugging purposes...
+# rm "$temp_proc_sql"
+
 success
 
 
-- 
GitLab