diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 145280fa2a..1bc5b65aa8 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -30,7 +30,7 @@ class Foreign_link extends Managed_DataObject return array( 'fields' => array( 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'link to user on this system, if exists'), - 'foreign_id' => array('type' => 'int', 'size' => 'big', 'unsigned' => true, 'not null' => true, 'description' => 'link to user on foreign service, if exists'), + 'foreign_id' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'link to user on foreign service, if exists'), 'service' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to service'), 'credentials' => array('type' => 'varchar', 'length' => 191, 'description' => 'authc credentials, typically a password'), 'noticesync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies'), @@ -53,7 +53,7 @@ class Foreign_link extends Managed_DataObject ); } - static function getByUserID($user_id, $service) + public static function getByUserID($user_id, $service) { if (empty($user_id) || empty($service)) { throw new ServerException('Empty user_id or service for Foreign_link::getByUserID'); @@ -71,7 +71,7 @@ class Foreign_link extends Managed_DataObject return $flink; } - static function getByForeignID($foreign_id, $service) + public static function getByForeignID($foreign_id, $service) { if (empty($foreign_id) || empty($service)) { throw new ServerException('Empty foreign_id or service for Foreign_link::getByForeignID'); @@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject return $flink; } - function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync) + public function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync) { if ($noticesend) { $this->noticesync |= FOREIGN_NOTICE_SEND; @@ -125,7 +125,7 @@ class Foreign_link extends Managed_DataObject } // Convenience methods - function getForeignUser() + public function getForeignUser() { $fuser = new Foreign_user(); $fuser->service = $this->service; @@ -140,29 +140,30 @@ class Foreign_link extends Managed_DataObject return $fuser; } - function getUser() + public function getUser() { return Profile::getByID($this->user_id)->getUser(); } - function getProfile() + public function getProfile() { return Profile::getByID($this->user_id); } // Make sure we only ever delete one record at a time - function safeDelete() + public function safeDelete() { if (!empty($this->user_id) && !empty($this->foreign_id) - && !empty($this->service)) - { + && !empty($this->service)) { return $this->delete(); } else { - common_debug(LOG_WARNING, + common_debug( + LOG_WARNING, 'Foreign_link::safeDelete() tried to delete a ' . 'Foreign_link without a fully specified compound key: ' - . var_export($this, true)); + . var_export($this, true) + ); return false; } } diff --git a/lib/database/mysqlschema.php b/lib/database/mysqlschema.php index e43f4079f5..bc6e8c2fd2 100644 --- a/lib/database/mysqlschema.php +++ b/lib/database/mysqlschema.php @@ -83,39 +83,53 @@ class MysqlSchema extends Schema $name = $row['COLUMN_NAME']; $field = []; - // warning -- 'unsigned' attr on numbers isn't given in DATA_TYPE and friends. - // It is stuck in on COLUMN_TYPE though (eg 'bigint(20) unsigned') - $field['type'] = $type = $row['DATA_TYPE']; + $type = $field['type'] = $row['DATA_TYPE']; - if ($type == 'char' || $type == 'varchar') { - if ($row['CHARACTER_MAXIMUM_LENGTH'] !== null) { - $field['length'] = intval($row['CHARACTER_MAXIMUM_LENGTH']); - } - } - if ($type == 'decimal') { - // Other int types may report these values, but they're irrelevant. - // Just ignore them! - if ($row['NUMERIC_PRECISION'] !== null) { - $field['precision'] = intval($row['NUMERIC_PRECISION']); - } - if ($row['NUMERIC_SCALE'] !== null) { - $field['scale'] = intval($row['NUMERIC_SCALE']); - } + switch ($type) { + case 'char': + case 'varchar': + if (!is_null($row['CHARACTER_MAXIMUM_LENGTH'])) { + $field['length'] = (int) $row['CHARACTER_MAXIMUM_LENGTH']; + } + break; + case 'decimal': + // Other int types may report these values, but they're irrelevant. + // Just ignore them! + if (!is_null($row['NUMERIC_PRECISION'])) { + $field['precision'] = (int) $row['NUMERIC_PRECISION']; + } + if (!is_null($row['NUMERIC_SCALE'])) { + $field['scale'] = (int) $row['NUMERIC_SCALE']; + } + break; + case 'enum': + $enum = preg_replace("/^enum\('(.+)'\)$/", '\1', $row['COLUMN_TYPE']); + $field['enum'] = explode("','", $enum); + break; } + + if ($row['IS_NULLABLE'] == 'NO') { $field['not null'] = true; } - if ($row['COLUMN_DEFAULT'] !== null) { - // Hack for timestamp columns - if ($row['COLUMN_DEFAULT'] === 'current_timestamp()') { - // skip timestamp columns as they get a CURRENT_TIMESTAMP default implicitly + $col_default = $row['COLUMN_DEFAULT']; + if (!is_null($col_default) && $col_default !== 'NULL') { + if ($this->isNumericType($field)) { + $field['default'] = (int) $col_default; + } elseif ($col_default === 'CURRENT_TIMESTAMP' + || $col_default === 'current_timestamp()') { + // A hack for "datetime" fields + // Skip "timestamp" as they get a CURRENT_TIMESTAMP default implicitly if ($type !== 'timestamp') { $field['default'] = 'CURRENT_TIMESTAMP'; } - } elseif ($this->isNumericType($type)) { - $field['default'] = intval($row['COLUMN_DEFAULT']); } else { - $field['default'] = $row['COLUMN_DEFAULT']; + $match = "/^'(.*)'$/"; + if (preg_match($match, $col_default)) { + $field['default'] = preg_replace($match, '\1', $col_default); + } else { + $field['default'] = $col_default; + } } } if ($row['COLUMN_KEY'] !== null) { @@ -135,11 +149,11 @@ class MysqlSchema extends Schema // ^ ...... how to specify? } - /* @fixme check against defaults? - if ($row['CHARACTER_SET_NAME'] !== null) { - $def['charset'] = $row['CHARACTER_SET_NAME']; - $def['collate'] = $row['COLLATION_NAME']; - }*/ + $table_props = $this->getTableProperties($table, ['TABLE_COLLATION']); + $collate = $row['COLLATION_NAME']; + if (!empty($collate) && $collate !== $table_props['TABLE_COLLATION']) { + $field['collate'] = $collate; + } $def['fields'][$name] = $field; } @@ -389,7 +403,7 @@ class MysqlSchema extends Schema public function appendAlterExtras(array &$phrase, $tableName, array $def) { // Check for table properties: make sure we're using a sane - // engine type and charset/collation. + // engine type and collation. // @fixme make the default engine configurable? $oldProps = $this->getTableProperties($tableName, ['ENGINE', 'TABLE_COLLATION']); $engine = $this->preferredEngine($def); @@ -403,12 +417,24 @@ class MysqlSchema extends Schema } } + private function isNumericType(array $cd): bool + { + $ints = array_map( + function ($s) { + return $s . 'int'; + }, + ['tiny', 'small', 'medium', 'big'] + ); + $ints = array_merge($ints, ['int', 'numeric', 'serial']); + return in_array(strtolower($cd['type']), $ints); + } + /** * Is this column a string type? * @param array $cd * @return bool */ - private function _isString(array $cd) + private function isStringType(array $cd): bool { $strings = ['char', 'varchar', 'text']; return in_array(strtolower($cd['type']), $strings); @@ -448,7 +474,6 @@ class MysqlSchema extends Schema { $map = [ 'integer' => 'int', - 'bool' => 'tinyint', 'numeric' => 'decimal', ]; @@ -476,15 +501,13 @@ class MysqlSchema extends Schema public function typeAndSize(string $name, array $column) { if ($column['type'] === 'enum') { + $vals = []; foreach ($column['enum'] as &$val) { - $vals[] = "'" . $val . "'"; + $vals[] = "'{$val}'"; } return 'enum(' . implode(',', $vals) . ')'; - } elseif ($this->_isString($column)) { + } elseif ($this->isStringType($column)) { $col = parent::typeAndSize($name, $column); - if (!empty($column['charset'])) { - $col .= ' CHARSET ' . $column['charset']; - } if (!empty($column['collate'])) { $col .= ' COLLATE ' . $column['collate']; } @@ -501,16 +524,39 @@ class MysqlSchema extends Schema * This lets us strip out unsupported things like comments, foreign keys, * or type variants that we wouldn't get back from getTableDef(). * + * @param string $tableName * @param array $tableDef * @return array */ - public function filterDef(array $tableDef) + public function filterDef(string $tableName, array $tableDef) { - $version = $this->conn->getVersion(); + $tableDef = parent::filterDef($tableName, $tableDef); + + // Get existing table collation if the table exists. + // To know if collation that's been set is unique for the table. + try { + $table_props = $this->getTableProperties($tableName, ['TABLE_COLLATION']); + $table_collate = $table_props['TABLE_COLLATION']; + } catch (SchemaTableMissingException $e) { + $table_collate = null; + } + foreach ($tableDef['fields'] as $name => &$col) { - if ($col['type'] == 'serial') { - $col['type'] = 'int'; - $col['auto_increment'] = true; + switch ($col['type']) { + case 'serial': + $col['type'] = 'int'; + $col['auto_increment'] = true; + break; + case 'bool': + $col['type'] = 'int'; + $col['size'] = 'tiny'; + $col['default'] = (int) $col['default']; + break; + } + + if (!empty($col['collate']) + && $col['collate'] === $table_collate) { + unset($col['collate']); } $col['type'] = $this->mapType($col); diff --git a/lib/database/pgsqlschema.php b/lib/database/pgsqlschema.php index 1923c121d7..399d4814ee 100644 --- a/lib/database/pgsqlschema.php +++ b/lib/database/pgsqlschema.php @@ -110,8 +110,8 @@ class PgsqlSchema extends Schema } if ($row['column_default'] !== null) { $field['default'] = $row['column_default']; - if ($this->isNumericType($type)) { - $field['default'] = intval($field['default']); + if ($this->isNumericType($field)) { + $field['default'] = (int) $field['default']; } } @@ -266,6 +266,12 @@ class PgsqlSchema extends Schema return $out; } + private function isNumericType(array $cd): bool + { + $ints = ['int', 'numeric', 'serial']; + return in_array(strtolower($cd['type']), $ints); + } + /** * Return the proper SQL for creating or * altering a column. @@ -395,11 +401,14 @@ class PgsqlSchema extends Schema * This lets us strip out unsupported things like comments, foreign keys, * or type variants that we wouldn't get back from getTableDef(). * + * @param string $tableName * @param array $tableDef * @return array */ - public function filterDef(array $tableDef) + public function filterDef(string $tableName, array $tableDef) { + $tableDef = parent::filterDef($tableName, $tableDef); + foreach ($tableDef['fields'] as $name => &$col) { // No convenient support for field descriptions unset($col['description']); @@ -409,22 +418,19 @@ class PgsqlSchema extends Schema $col['type'] = 'int'; $col['auto_increment'] = true; break; + case 'timestamp': + // FIXME: ON UPDATE CURRENT_TIMESTAMP + if (!array_key_exists('default', $col)) { + $col['default'] = 'CURRENT_TIMESTAMP'; + } + // no break case 'datetime': - // Replace archaic MySQL-specific zero-dates with NULL + // Replace archaic MySQL-specific zero dates with NULL if (($col['default'] ?? null) === '0000-00-00 00:00:00') { $col['default'] = null; $col['not null'] = false; } break; - case 'timestamp': - // In MariaDB: If the column does not permit NULL values, - // assigning NULL (or not referencing the column at all - // when inserting) will set the column to CURRENT_TIMESTAMP - // FIXME: ON UPDATE CURRENT_TIMESTAMP - if ($col['not null'] && !isset($col['default'])) { - $col['default'] = 'CURRENT_TIMESTAMP'; - } - break; } $col['type'] = $this->mapType($col); diff --git a/lib/database/schema.php b/lib/database/schema.php index 186b1f755a..8549ad5bdf 100644 --- a/lib/database/schema.php +++ b/lib/database/schema.php @@ -139,7 +139,7 @@ class Schema public function buildCreateTable($name, $def) { $def = $this->validateDef($name, $def); - $def = $this->filterDef($def); + $def = $this->filterDef($name, $def); $sql = []; foreach ($def['fields'] as $col => $colDef) { @@ -572,10 +572,10 @@ class Schema // Filter the DB-independent table definition to match the current // database engine's features and limitations. $def = $this->validateDef($tableName, $def); - $def = $this->filterDef($def); + $def = $this->filterDef($tableName, $def); $statements = []; - $fields = $this->diffArrays($old, $def, 'fields', [$this, 'columnsEqual']); + $fields = $this->diffArrays($old, $def, 'fields'); $uniques = $this->diffArrays($old, $def, 'unique keys'); $indexes = $this->diffArrays($old, $def, 'indexes'); $foreign = $this->diffArrays($old, $def, 'foreign keys'); @@ -813,20 +813,6 @@ class Schema return $this->conn->quoteSmart($val); } - /** - * Check if two column definitions are equivalent. - * The default implementation checks _everything_ but in many cases - * you may be able to discard a bunch of equivalencies. - * - * @param array $a - * @param array $b - * @return bool - */ - public function columnsEqual(array $a, array $b) - { - return !array_diff_assoc($a, $b) && !array_diff_assoc($b, $a); - } - /** * Returns the array of names from an array of * ColumnDef objects. @@ -886,9 +872,11 @@ class Schema if (isset($cd['default'])) { $line[] = 'default'; $line[] = $this->quoteDefaultValue($cd); - } elseif (!empty($cd['not null'])) { - // Can't have both not null AND default! - $line[] = 'not null'; + } + if (!empty($cd['not null'])) { + $line[] = 'NOT NULL'; + } else { + $line[] = 'NULL'; } return implode(' ', $line); @@ -1004,11 +992,21 @@ class Schema * This lets us strip out unsupported things like comments, foreign keys, * or type variants that we wouldn't get back from getTableDef(). * + * @param string $tableName * @param array $tableDef * @return array */ - public function filterDef(array $tableDef) + public function filterDef(string $tableName, array $tableDef) { + foreach ($tableDef['fields'] as $name => &$col) { + if (array_key_exists('default', $col) && is_null($col['default'])) { + unset($col['default']); + } + if (array_key_exists('not null', $col) && $col['not null'] !== true) { + unset($col['not null']); + } + } + return $tableDef; } @@ -1037,13 +1035,6 @@ class Schema return $def; } - public function isNumericType($type) - { - $type = strtolower($type); - $known = ['int', 'serial', 'numeric']; - return in_array($type, $known); - } - /** * Pull info from the query into a fun-fun array of dooooom * diff --git a/plugins/ActivityPub/classes/Activitypub_rsa.php b/plugins/ActivityPub/classes/Activitypub_rsa.php index 58f8cbe325..2c44df0350 100644 --- a/plugins/ActivityPub/classes/Activitypub_rsa.php +++ b/plugins/ActivityPub/classes/Activitypub_rsa.php @@ -61,7 +61,7 @@ class Activitypub_rsa extends Managed_DataObject ], 'primary key' => ['profile_id'], 'foreign keys' => [ - 'activitypub_profile_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + 'activitypub_rsa_profile_id_fkey' => ['profile', ['profile_id' => 'id']], ], ]; } @@ -182,7 +182,7 @@ class Activitypub_rsa extends Managed_DataObject $apRSA = new Activitypub_rsa(); $apRSA->profile_id = $profile->getID(); $apRSA->public_key = $public_key; - $apRSA->modified = common_sql_now(); + $apRSA->created = common_sql_now(); if (!$apRSA->update()) { $apRSA->insert(); }