From 7f55e1b2bc64ea947d61523972a5fc9ec74836e3 Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sun, 10 May 2020 14:55:03 +0000
Subject: [PATCH 1/5] We now support real foreign keys

---
 src/Database/DBStructure.php  | 107 +++++++++++++++++++++++++++++++++-
 static/dbstructure.config.php |  50 +++++++++-------
 update.php                    |  26 +++++++++
 3 files changed, 159 insertions(+), 24 deletions(-)

diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php
index dc13bd656..5e5d71be8 100644
--- a/src/Database/DBStructure.php
+++ b/src/Database/DBStructure.php
@@ -162,11 +162,16 @@ class DBStructure
 		$comment = "";
 		$sql_rows = [];
 		$primary_keys = [];
+		$foreign_keys = [];
+
 		foreach ($structure["fields"] AS $fieldname => $field) {
 			$sql_rows[] = "`" . DBA::escape($fieldname) . "` " . self::FieldCommand($field);
 			if (!empty($field['primary'])) {
 				$primary_keys[] = $fieldname;
 			}
+			if (!empty($field['foreign'])) {
+				$foreign_keys[$fieldname] = $field;
+			}
 		}
 
 		if (!empty($structure["indexes"])) {
@@ -178,6 +183,10 @@ class DBStructure
 			}
 		}
 
+		foreach ($foreign_keys AS $fieldname => $parameters) {
+			$sql_rows[] = self::foreignCommand($name, $fieldname, $parameters);
+		}
+
 		if (isset($structure["engine"])) {
 			$engine = " ENGINE=" . $structure["engine"];
 		}
@@ -295,7 +304,7 @@ class DBStructure
 		$database = [];
 
 		if (is_null($tables)) {
-			$tables = q("SHOW TABLES");
+			$tables = DBA::toArray(DBA::p("SHOW TABLES"));
 		}
 
 		if (DBA::isResult($tables)) {
@@ -387,6 +396,7 @@ class DBStructure
 
 						// Remove the relation data that is used for the referential integrity
 						unset($parameters['relation']);
+						unset($parameters['foreign']);
 
 						// We change the collation after the indexes had been changed.
 						// This is done to avoid index length problems.
@@ -441,6 +451,40 @@ class DBStructure
 					}
 				}
 
+				$existing_foreign_keys = $database[$name]['foreign_keys'];
+
+				// Foreign keys
+				// Compare the field structure field by field
+				foreach ($structure["fields"] AS $fieldname => $parameters) {
+					if (empty($parameters['foreign'])) {
+						continue;
+					}
+
+					$constraint = self::getConstraintName($name, $fieldname, $parameters);
+
+					unset($existing_foreign_keys[$constraint]);
+
+					if (empty($database[$name]['foreign_keys'][$constraint])) {
+						$sql2 = self::addForeignKey($name, $fieldname, $parameters);
+
+						if ($sql3 == "") {
+							$sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2;
+						} else {
+							$sql3 .= ", " . $sql2;
+						}
+					}
+				}
+
+				foreach ($existing_foreign_keys as $constraint => $param) {
+					$sql2 = self::dropForeignKey($constraint);
+
+					if ($sql3 == "") {
+						$sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2;
+					} else {
+						$sql3 .= ", " . $sql2;
+					}
+				}
+
 				if (isset($database[$name]["table_status"]["Comment"])) {
 					$structurecomment = $structure["comment"] ?? '';
 					if ($database[$name]["table_status"]["Comment"] != $structurecomment) {
@@ -596,7 +640,7 @@ class DBStructure
 			}
 		}
 
-		View::create($verbose, $action);
+		View::create(false, $action);
 
 		if ($action && !$install) {
 			DI::config()->set('system', 'maintenance', 0);
@@ -620,6 +664,11 @@ class DBStructure
 
 		$indexes = q("SHOW INDEX FROM `%s`", $table);
 
+		$foreign_keys = DBA::selectToArray(['INFORMATION_SCHEMA' => 'KEY_COLUMN_USAGE'],
+			['COLUMN_NAME', 'CONSTRAINT_NAME', 'REFERENCED_TABLE_NAME', 'REFERENCED_COLUMN_NAME'],
+			["`TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `REFERENCED_TABLE_SCHEMA` IS NOT NULL",
+			DBA::databaseName(), $table]);
+
 		$table_status = q("SHOW TABLE STATUS WHERE `name` = '%s'", $table);
 
 		if (DBA::isResult($table_status)) {
@@ -630,6 +679,15 @@ class DBStructure
 
 		$fielddata = [];
 		$indexdata = [];
+		$foreigndata = [];
+
+		if (DBA::isResult($foreign_keys)) {
+			foreach ($foreign_keys as $foreign_key) {
+				$constraint = $foreign_key['CONSTRAINT_NAME'];
+				unset($foreign_key['CONSTRAINT_NAME']); 
+				$foreigndata[$constraint] = $foreign_key;
+			}
+		}
 
 		if (DBA::isResult($indexes)) {
 			foreach ($indexes AS $index) {
@@ -682,7 +740,8 @@ class DBStructure
 			}
 		}
 
-		return ["fields" => $fielddata, "indexes" => $indexdata, "table_status" => $table_status];
+		return ["fields" => $fielddata, "indexes" => $indexdata,
+			"foreign_keys" => $foreigndata, "table_status" => $table_status];
 	}
 
 	private static function dropIndex($indexname)
@@ -703,6 +762,48 @@ class DBStructure
 		return ($sql);
 	}
 
+	private static function getConstraintName(string $tablename, string $fieldname, array $parameters)
+	{
+		$foreign_table = array_keys($parameters['foreign'])[0];
+		$foreign_field = array_values($parameters['foreign'])[0];
+
+		return $tablename . "-" . $fieldname. "-" . $foreign_table. "-" . $foreign_field;
+	}
+
+	private static function foreignCommand(string $tablename, string $fieldname, array $parameters) {
+		$foreign_table = array_keys($parameters['foreign'])[0];
+		$foreign_field = array_values($parameters['foreign'])[0];
+
+		$constraint = self::getConstraintName($tablename, $fieldname, $parameters);
+
+		$sql = "CONSTRAINT `" . $constraint . "` FOREIGN KEY (`" . $fieldname . "`)" .
+			" REFERENCES `" . $foreign_table . "` (`" . $foreign_field . "`)";
+
+		if (!empty($parameters['foreign']['on update'])) {
+			$sql .= " ON UPDATE " . strtoupper($parameters['foreign']['on update']);
+		} else {
+			$sql .= " ON UPDATE RESTRICT";
+		}
+
+		if (!empty($parameters['foreign']['on delete'])) {
+			$sql .= " ON DELETE " . strtoupper($parameters['foreign']['on delete']);
+		} else {
+			$sql .= " ON DELETE CASCADE";
+		}
+
+		return $sql;
+	}
+
+	private static function addForeignKey(string $tablename, string $fieldname, array $parameters)
+	{
+		return sprintf("ADD %s", self::foreignCommand($tablename, $fieldname, $parameters));
+	}
+
+	private static function dropForeignKey(string $constraint)
+	{
+		return sprintf("DROP FOREIGN KEY `%s`", $constraint);
+	}
+
 	/**
 	 * Constructs a GROUP BY clause from a UNIQUE index definition.
 	 *
diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php
index 603327cd0..4d061452b 100755
--- a/static/dbstructure.config.php
+++ b/static/dbstructure.config.php
@@ -32,7 +32,7 @@
  *			{"default" => "<default value>",}
  *			{"default" => NULL_DATE,} (for datetime fields)
  *			{"primary" => "1",}
- *			{"relation" => ["<foreign key table name>" => "<foreign key field name>"],}
+ *			{"foreign|relation" => ["<foreign key table name>" => "<foreign key field name>"],}
  *			"comment" => "Description of the fields"
  *		],
  *		...
@@ -44,6 +44,9 @@
  *	],
  * ],
  *
+ * Whenever possible prefer "foreign" before "relation" with the foreign keys.
+ * "foreign" adds true foreign keys on the database level, while "relation" simulates this behaviour.
+ *
  * If you need to make any change, make sure to increment the DB_UPDATE_VERSION constant value below.
  *
  */
@@ -51,7 +54,7 @@
 use Friendica\Database\DBA;
 
 if (!defined('DB_UPDATE_VERSION')) {
-	define('DB_UPDATE_VERSION', 1347);
+	define('DB_UPDATE_VERSION', 1348);
 }
 
 return [
@@ -158,7 +161,7 @@ return [
 		"comment" => "OAuth usage",
 		"fields" => [
 			"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
-			"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => ["clients" => "client_id"],
+			"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"],
 				"comment" => ""],
 			"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
 			"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
@@ -166,6 +169,7 @@ return [
 		],
 		"indexes" => [
 			"PRIMARY" => ["id"],
+			"client_id" => ["client_id"]
 		]
 	],
 	"cache" => [
@@ -367,7 +371,7 @@ return [
 	"diaspora-interaction" => [
 		"comment" => "Signed Diaspora Interaction",
 		"fields" => [
-			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"interaction" => ["type" => "mediumtext", "comment" => "The Diaspora interaction"]
 		],
 		"indexes" => [
@@ -652,13 +656,13 @@ return [
 			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "relation" => ["thread" => "iid"]],
 			"guid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "A unique identifier for this item"],
 			"uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
-			"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"uri-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
 			"parent" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => "item.id of the parent to this item if it is a reply of some form; otherwise this must be set to the id of this item"],
 			"parent-uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "uri of the parent to this item"],
-			"parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the parent uri"],
+			"parent-uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the parent uri"],
 			"thr-parent" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "If the parent of this item is not the top-level item in the conversation, the uri of the immediate parent; otherwise set to parent-uri"],
-			"thr-parent-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the thread parent uri"],
+			"thr-parent-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the thread parent uri"],
 			"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation timestamp."],
 			"edited" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of last edit (default is created)"],
 			"commented" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of last comment/reply to this item"],
@@ -756,6 +760,8 @@ return [
 			"iaid" => ["iaid"],
 			"psid_wall" => ["psid", "wall"],
 			"uri-id" => ["uri-id"],
+			"parent-uri-id" => ["parent-uri-id"],
+			"thr-parent-id" => ["thr-parent-id"],
 		]
 	],
 	"item-activity" => [
@@ -763,7 +769,7 @@ return [
 		"fields" => [
 			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
 			"uri" => ["type" => "varchar(255)", "comment" => ""],
-			"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"uri-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
 			"activity" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""]
 		],
@@ -779,7 +785,7 @@ return [
 		"fields" => [
 			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
 			"uri" => ["type" => "varchar(255)", "comment" => ""],
-			"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"uri-plink-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
 			"title" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "item title"],
 			"content-warning" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
@@ -930,13 +936,14 @@ return [
 			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
 			"notify-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["notify" => "id"], "comment" => ""],
 			"master-parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
-			"master-parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
+			"master-parent-uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
 			"parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => ""],
 			"receiver-uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"],
 				"comment" => "User id"],
 		],
 		"indexes" => [
 			"PRIMARY" => ["id"],
+			"master-parent-uri-id" => ["master-parent-uri-id"],
 		]
 	],
 	"oembed" => [
@@ -1293,10 +1300,10 @@ return [
 	"post-category" => [
 		"comment" => "post relation to categories",
 		"fields" => [
-			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
-			"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
+			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1",  "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["user" => "uid"], "comment" => "User id"],
 			"type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "comment" => ""],
-			"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["tag" => "id"], "comment" => ""],
+			"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["tag" => "id", "on delete" => "restrict"], "comment" => ""],
 		],
 		"indexes" => [
 			"PRIMARY" => ["uri-id", "uid", "type", "tid"],
@@ -1306,7 +1313,7 @@ return [
 	"post-delivery-data" => [
 		"comment" => "Delivery data for items",
 		"fields" => [
-			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"postopts" => ["type" => "text", "comment" => "External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery"],
 			"inform" => ["type" => "mediumtext", "comment" => "Additional receivers of the linked item"],
 			"queue_count" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Initial number of delivery recipients, used as item.delivery_queue_count"],
@@ -1325,15 +1332,15 @@ return [
 	"post-tag" => [
 		"comment" => "post relation to tags",
 		"fields" => [
-			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+			"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
 			"type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "comment" => ""],
-			"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["tag" => "id"], "comment" => ""],
-			"cid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["contact" => "id"], "comment" => "Contact id of the mentioned public contact"],
+			"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["tag" => "id", "on delete" => "restrict"], "comment" => ""],
+			"cid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["contact" => "id", "on delete" => "restrict"], "comment" => "Contact id of the mentioned public contact"],
 		],
 		"indexes" => [
 			"PRIMARY" => ["uri-id", "type", "tid", "cid"],
-			"uri-id" => ["tid"],
-			"cid" => ["tid"]
+			"tid" => ["tid"],
+			"cid" => ["cid"]
 		]
 	],
 	"thread" => [
@@ -1386,13 +1393,14 @@ return [
 		"fields" => [
 			"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
 			"secret" => ["type" => "text", "comment" => ""],
-			"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => ["clients" => "client_id"]],
+			"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"]],
 			"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
 			"scope" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
 			"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
 		],
 		"indexes" => [
 			"PRIMARY" => ["id"],
+			"client_id" => ["client_id"]
 		]
 	],
 	"user" => [
@@ -1493,7 +1501,7 @@ return [
 	"verb" => [
 		"comment" => "Activity Verbs",
 		"fields" => [
-			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
+			"id" => ["type" => "smallint unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
 			"name" => ["type" => "varchar(100)", "not null" => "1", "default" => "", "comment" => ""]
 		],
 		"indexes" => [
diff --git a/update.php b/update.php
index b7575e6c3..be1890b70 100644
--- a/update.php
+++ b/update.php
@@ -431,3 +431,29 @@ function update_1347()
 
 	return Update::SUCCESS;
 }
+
+function pre_update_1348()
+{
+	DBA::insert('contact', ['nurl' => '']);
+	DBA::update('contact', ['id' => 0], ['id' => DBA::lastInsertId()]);
+
+	// The tables "permissionset" and "tag" could or could not exist during the update.
+	// This depends upon the previous version. Depending upon this situation we have to add
+	// the "0" values before adding the foreign keys - or after would be sufficient.
+
+	update_1348();
+}
+
+function update_1348()
+{
+	// Insert a permissionset with id=0
+	// Setting it to -1 and then changing the value to 0 tricks the auto increment
+	DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);	
+	DBA::update('permissionset', ['id' => 0], ['id' => DBA::lastInsertId()]);
+
+	DBA::insert('tag', ['name' => '']);
+	DBA::update('tag', ['id' => 0], ['id' => DBA::lastInsertId()]);
+
+	// to-do: Tag / contact
+	return Update::SUCCESS;
+}

From ecdf3b798b862a81abc7d0e0b5b0bccf413b6a87 Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sun, 10 May 2020 15:43:43 +0000
Subject: [PATCH 2/5] True foreign keys for the Permissionset

---
 static/dbstructure.config.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php
index 4d061452b..833d809e7 100755
--- a/static/dbstructure.config.php
+++ b/static/dbstructure.config.php
@@ -692,7 +692,7 @@ return [
 			"unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "item has not been seen"],
 			"mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "The owner of this item was mentioned in it"],
 			"forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
-			"psid" => ["type" => "int unsigned", "relation" => ["permissionset" => "id"], "comment" => "ID of the permission set of this post"],
+			"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"],
 			// It has to be decided whether these fields belong to the user or the structure
 			"resource-id" => ["type" => "varchar(32)", "not null" => "1", "default" => "", "comment" => "Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type"],
 			"event-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["event" => "id"], "comment" => "Used to link to the event.id"],
@@ -1187,7 +1187,7 @@ return [
 			"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
 			"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner user id"],
 			"order" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "1", "comment" => "Field ordering per user"],
-			"psid" => ["type" => "int unsigned", "relation" => ["permissionset" => "id"], "comment" => "ID of the permission set of this profile field - 0 = public"],
+			"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this profile field - 0 = public"],
 			"label" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Label of the field"],
 			"value" => ["type" => "text", "comment" => "Value of the field"],
 			"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "creation time"],

From 156712597c39d7e930118568fe3560eb8c066eda Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sun, 10 May 2020 17:37:02 +0000
Subject: [PATCH 3/5] Improved update mechanism (more error handling)

---
 update.php | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/update.php b/update.php
index be1890b70..bb733ebc2 100644
--- a/update.php
+++ b/update.php
@@ -435,13 +435,18 @@ function update_1347()
 function pre_update_1348()
 {
 	DBA::insert('contact', ['nurl' => '']);
-	DBA::update('contact', ['id' => 0], ['id' => DBA::lastInsertId()]);
+	$lastid = DBA::lastInsertId();
+	if ($lastid != 0) {
+		DBA::update('contact', ['id' => 0], ['id' => $lastid]);
+	}
 
 	// The tables "permissionset" and "tag" could or could not exist during the update.
 	// This depends upon the previous version. Depending upon this situation we have to add
 	// the "0" values before adding the foreign keys - or after would be sufficient.
 
 	update_1348();
+
+	return Update::SUCCESS;
 }
 
 function update_1348()
@@ -449,11 +454,17 @@ function update_1348()
 	// Insert a permissionset with id=0
 	// Setting it to -1 and then changing the value to 0 tricks the auto increment
 	DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);	
-	DBA::update('permissionset', ['id' => 0], ['id' => DBA::lastInsertId()]);
+	$lastid = DBA::lastInsertId();
+	if ($lastid != 0) {
+		DBA::update('permissionset', ['id' => 0], ['id' => $lastid]);
+	}
+
 
 	DBA::insert('tag', ['name' => '']);
-	DBA::update('tag', ['id' => 0], ['id' => DBA::lastInsertId()]);
+	$lastid = DBA::lastInsertId();
+	if ($lastid != 0) {
+		DBA::update('tag', ['id' => 0], ['id' => $lastid]);
+	}
 
-	// to-do: Tag / contact
 	return Update::SUCCESS;
 }

From 366ff0a8b7294a309cab4750e4801e62bb0d144d Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sun, 10 May 2020 17:41:16 +0000
Subject: [PATCH 4/5] Check for existance before creation

---
 update.php | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/update.php b/update.php
index bb733ebc2..3d8721a34 100644
--- a/update.php
+++ b/update.php
@@ -434,10 +434,12 @@ function update_1347()
 
 function pre_update_1348()
 {
-	DBA::insert('contact', ['nurl' => '']);
-	$lastid = DBA::lastInsertId();
-	if ($lastid != 0) {
-		DBA::update('contact', ['id' => 0], ['id' => $lastid]);
+	if (!DBA::exists('contact', ['id' => 0])) {
+		DBA::insert('contact', ['nurl' => '']);
+		$lastid = DBA::lastInsertId();
+		if ($lastid != 0) {
+			DBA::update('contact', ['id' => 0], ['id' => $lastid]);
+		}
 	}
 
 	// The tables "permissionset" and "tag" could or could not exist during the update.
@@ -453,17 +455,20 @@ function update_1348()
 {
 	// Insert a permissionset with id=0
 	// Setting it to -1 and then changing the value to 0 tricks the auto increment
-	DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);	
-	$lastid = DBA::lastInsertId();
-	if ($lastid != 0) {
-		DBA::update('permissionset', ['id' => 0], ['id' => $lastid]);
+	if (!DBA::exists('permissionset', ['id' => 0])) {
+		DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);	
+		$lastid = DBA::lastInsertId();
+		if ($lastid != 0) {
+			DBA::update('permissionset', ['id' => 0], ['id' => $lastid]);
+		}
 	}
 
-
-	DBA::insert('tag', ['name' => '']);
-	$lastid = DBA::lastInsertId();
-	if ($lastid != 0) {
-		DBA::update('tag', ['id' => 0], ['id' => $lastid]);
+	if (!DBA::exists('tag', ['id' => 0])) {
+		DBA::insert('tag', ['name' => '']);
+		$lastid = DBA::lastInsertId();
+		if ($lastid != 0) {
+			DBA::update('tag', ['id' => 0], ['id' => $lastid]);
+		}
 	}
 
 	return Update::SUCCESS;

From 28600a7cf8ecc98ca3f041cbfee74ff0dc8a827e Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sun, 10 May 2020 17:48:34 +0000
Subject: [PATCH 5/5] Fixed description

---
 update.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/update.php b/update.php
index 3d8721a34..77f111bba 100644
--- a/update.php
+++ b/update.php
@@ -454,7 +454,7 @@ function pre_update_1348()
 function update_1348()
 {
 	// Insert a permissionset with id=0
-	// Setting it to -1 and then changing the value to 0 tricks the auto increment
+	// Inserting it without an ID and then changing the value to 0 tricks the auto increment
 	if (!DBA::exists('permissionset', ['id' => 0])) {
 		DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);	
 		$lastid = DBA::lastInsertId();