]> git.mxchange.org Git - friendica.git/commitdiff
We now support real foreign keys
authorMichael <heluecht@pirati.ca>
Sun, 10 May 2020 14:55:03 +0000 (14:55 +0000)
committerMichael <heluecht@pirati.ca>
Sun, 10 May 2020 14:55:03 +0000 (14:55 +0000)
src/Database/DBStructure.php
static/dbstructure.config.php
update.php

index dc13bd6566d94228cab5d63205dde2461c2533c1..5e5d71be8958cecb7b2a0569eebd9c45109b75ab 100644 (file)
@@ -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.
         *
index 603327cd057142f36f174f374db1bd14240ce027..4d061452b35e65599b00b35c5f956e26b1b7851c 100755 (executable)
@@ -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" => [
index b7575e6c3918eb7b03e879b782dc0c3360b503e7..be1890b7070abf725a2b7db1c53d1da32c6512df 100644 (file)
@@ -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;
+}