$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"])) {
}
}
+ foreach ($foreign_keys AS $fieldname => $parameters) {
+ $sql_rows[] = self::foreignCommand($name, $fieldname, $parameters);
+ }
+
if (isset($structure["engine"])) {
$engine = " ENGINE=" . $structure["engine"];
}
$database = [];
if (is_null($tables)) {
- $tables = q("SHOW TABLES");
+ $tables = DBA::toArray(DBA::p("SHOW TABLES"));
}
if (DBA::isResult($tables)) {
// 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.
}
}
+ $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) {
}
}
- View::create($verbose, $action);
+ View::create(false, $action);
if ($action && !$install) {
DI::config()->set('system', 'maintenance', 0);
$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)) {
$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) {
}
}
- 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)
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.
*
* {"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"
* ],
* ...
* ],
* ],
*
+ * 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.
*
*/
use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) {
- define('DB_UPDATE_VERSION', 1347);
+ define('DB_UPDATE_VERSION', 1348);
}
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" => ""],
],
"indexes" => [
"PRIMARY" => ["id"],
+ "client_id" => ["client_id"]
]
],
"cache" => [
"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" => [
"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"],
"iaid" => ["iaid"],
"psid_wall" => ["psid", "wall"],
"uri-id" => ["uri-id"],
+ "parent-uri-id" => ["parent-uri-id"],
+ "thr-parent-id" => ["thr-parent-id"],
]
],
"item-activity" => [
"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" => ""]
],
"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" => ""],
"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" => [
"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"],
"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"],
"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" => [
"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" => [
"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" => [