From 16b5ddd230848fbbc3a50bfe493a4797b81efe65 Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Wed, 11 Sep 2019 14:14:40 +0300 Subject: [PATCH] [DATABASE] Re-introduce PostgreSQL support --- .../SYSTEM_ADMINISTRATORS/CONFIGURE.md | 13 +- actions/sup.php | 4 +- classes/File.php | 14 +- classes/File_redirection.php | 14 +- classes/Managed_DataObject.php | 11 +- classes/Memcached_DataObject.php | 191 ++++++++++-------- classes/Notice_prefs.php | 70 ++++--- classes/Profile.php | 50 +++-- classes/Profile_list.php | 29 ++- classes/Profile_prefs.php | 61 +++--- extlib/DB/DataObject.php | 24 ++- lib/database/mysqlschema.php | 25 ++- lib/database/pgsqlschema.php | 140 +++++++------ lib/database/schema.php | 23 ++- lib/search/search_engines.php | 54 +++-- lib/util/installer.php | 48 +++-- plugins/ActivitySpam/classes/Spam_score.php | 3 +- plugins/DBQueue/classes/DBQueueManager.php | 44 ++-- .../OStatus/scripts/update-profile-data.php | 48 +++-- plugins/OpenID/openid.php | 132 ++++++------ plugins/SphinxSearch/README | 2 +- scripts/upgrade.php | 16 +- 22 files changed, 606 insertions(+), 410 deletions(-) diff --git a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md index 39d3fdfb75..6a386821d0 100644 --- a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md +++ b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md @@ -164,10 +164,9 @@ The ones that you may want to set are listed below for clarity. * `database` (string, required, default null): a DSN (Data Source Name) for your GNU social database. This is in the format - 'protocol://username:password@hostname/databasename', where 'protocol' is ' - mysql' or 'mysqli' (or possibly 'postgresql', if you really know what - you're doing), 'username' is the username, 'password' is the password, - and etc. + 'protocol://username:password@hostname/databasename', where 'protocol' is + 'mysqli' or 'pgsql' or 'mysql', 'username' is the username, 'password' is + the password, and etc. * `ini_yourdbname` (string, default null): if your database is not named 'statusnet', you'll need to set this to point to the location of the statusnet.ini file. @@ -178,9 +177,9 @@ The ones that you may want to set are listed below for clarity. 'MDB2' to use the other driver type for DB_DataObject, but note that it breaks the OpenID libraries, which only support PEAR::DB. -* `type` (enum["mysql", "postgresql"], default 'mysql'): Used for certain - database-specific optimization code. Assumes mysql if not set. MySQL also - covers MySQLi and MariaDB. +* `type` (enum["mysql", "pgsql"], default 'mysql'): Used for certain + database-specific optimization code. Assumes mysql if not set. "mysql" + covers MariaDB, Oracle MySQL, mysqli or otherwise. * `mirror` (array, default null): you can set this to an array of DSNs, in the format of the above 'database' value. If it's set, certain read-only diff --git a/actions/sup.php b/actions/sup.php index 2ad434c5d2..cf78a2f7d3 100644 --- a/actions/sup.php +++ b/actions/sup.php @@ -68,9 +68,7 @@ class SupAction extends Action $notice->query('SELECT profile_id, max(id) AS max_id ' . 'FROM ( ' . 'SELECT profile_id, id FROM notice ' . - ((common_config('db','type') == 'pgsql') ? - 'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' : - "WHERE created > TIMESTAMP '" . $divider . "' ") . + "WHERE created > TIMESTAMP '" . $divider . "' " . ') AS latest ' . 'GROUP BY profile_id'); diff --git a/classes/File.php b/classes/File.php index 4beef14807..34dba9ae58 100644 --- a/classes/File.php +++ b/classes/File.php @@ -943,12 +943,20 @@ class File extends Managed_DataObject $tablefix = new $classname; // urlhash is hash('sha256', $url) in the File table echo "Updating urlhash fields in $table table..."; - // Maybe very MySQL specific :( + switch (common_config('db', 'type')) { + case 'pgsql': + $url_sha256 = 'encode(sha256(CAST("url" AS bytea)), \'hex\')'; + break; + case 'mysql': + $url_sha256 = 'sha2(`url`, 256)'; + break; + default: + throw new ServerException('Unknown DB type selected.'); + } $tablefix->query(sprintf( 'UPDATE %1$s SET urlhash = %2$s;', $tablefix->escapedTableName(), - // The line below is "result of sha256 on column `url`" - 'sha2(url, 256)' + $url_sha256 )); echo "DONE.\n"; echo "Resuming core schema upgrade..."; diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 853c341e78..fa58f422df 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -457,12 +457,20 @@ class File_redirection extends Managed_DataObject $tablefix = new $classname; // urlhash is hash('sha256', $url) in the File table echo "Updating urlhash fields in $table table..."; - // Maybe very MySQL specific :( + switch (common_config('db', 'type')) { + case 'pgsql': + $url_sha256 = 'encode(sha256(CAST("url" AS bytea)), \'hex\')'; + break; + case 'mysql': + $url_sha256 = 'sha2(`url`, 256)'; + break; + default: + throw new ServerException('Unknown DB type selected.'); + } $tablefix->query(sprintf( 'UPDATE %1$s SET urlhash = %2$s;', $tablefix->escapedTableName(), - // The line below is "result of sha256 on column `url`" - 'sha2(url, 256)' + $url_sha256 )); echo "DONE.\n"; echo "Resuming core schema upgrade..."; diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php index 315d3c7021..5684d8080a 100644 --- a/classes/Managed_DataObject.php +++ b/classes/Managed_DataObject.php @@ -226,11 +226,12 @@ abstract class Managed_DataObject extends Memcached_DataObject $type = $column['type']; // For quoting style... - $intTypes = array('int', - 'integer', - 'float', - 'serial', - 'numeric'); + $intTypes = [ + 'int', + 'float', + 'serial', + 'numeric' + ]; if (in_array($type, $intTypes)) { $style = DB_DATAOBJECT_INT; } else { diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index a4687aeae6..e6dc4d1fc0 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -1,23 +1,25 @@ . +// This file is part of GNU social - https://www.gnu.org/software/social +// +// GNU social is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GNU social is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with GNU social. If not, see . + +/** + * @copyright 2008, 2009 StatusNet, Inc. + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -if (!defined('GNUSOCIAL')) { exit(1); } +defined('GNUSOCIAL') || die(); class Memcached_DataObject extends Safe_DataObject { @@ -30,7 +32,7 @@ class Memcached_DataObject extends Safe_DataObject * @param mixed $v key field value, or leave out for primary key lookup * @return mixed Memcached_DataObject subtype or false */ - static function getClassKV($cls, $k, $v=null) + public static function getClassKV($cls, $k, $v = null) { if (is_null($v)) { $v = $k; @@ -71,7 +73,7 @@ class Memcached_DataObject extends Safe_DataObject * * @return array Array of objects, in order */ - static function multiGetClass($cls, $keyCol, array $keyVals, $skipNulls=true) + public static function multiGetClass($cls, $keyCol, array $keyVals, $skipNulls = true) { $obj = new $cls; @@ -100,8 +102,27 @@ class Memcached_DataObject extends Safe_DataObject $keyVals[$key] = $obj->escape($val); } - // FIND_IN_SET will make sure we keep the desired order - $obj->orderBy(sprintf("FIND_IN_SET(%s, '%s')", $keyCol, implode(',', $keyVals))); + switch (common_config('db', 'type')) { + case 'pgsql': + // "position" will make sure we keep the desired order + $obj->orderBy(sprintf( + "position(',' || CAST(%s AS text) || ',' IN ',%s,')", + $keyCol, + implode(',', $keyVals) + )); + break; + case 'mysql': + // "find_in_set" will make sure we keep the desired order + $obj->orderBy(sprintf( + "find_in_set(%s, '%s')", + $keyCol, + implode(',', $keyVals) + )); + break; + default: + throw new ServerException('Unknown DB type selected.'); + } + $obj->find(); return $obj; @@ -117,7 +138,7 @@ class Memcached_DataObject extends Safe_DataObject * * @return array Array mapping $keyVals to objects, or null if not found */ - static function pivotGetClass($cls, $keyCol, array $keyVals, array $otherCols = array()) + public static function pivotGetClass($cls, $keyCol, array $keyVals, array $otherCols = []) { if (is_array($keyCol)) { foreach ($keyVals as $keyVal) { @@ -130,7 +151,6 @@ class Memcached_DataObject extends Safe_DataObject $toFetch = array(); foreach ($keyVals as $keyVal) { - if (is_array($keyCol)) { $kv = array_combine($keyCol, $keyVal); } else { @@ -147,7 +167,7 @@ class Memcached_DataObject extends Safe_DataObject } else { $result[$keyVal] = $i; } - } else if (!empty($keyVal)) { + } elseif (!empty($keyVal)) { $toFetch[] = $keyVal; } } @@ -207,7 +227,7 @@ class Memcached_DataObject extends Safe_DataObject return $result; } - static function _inMultiKey($i, $cols, $values) + public static function _inMultiKey($i, $cols, $values) { $types = array(); @@ -255,7 +275,7 @@ class Memcached_DataObject extends Safe_DataObject return $query; } - static function pkeyColsClass($cls) + public static function pkeyColsClass($cls) { $i = new $cls; $types = $i->keyTypes(); @@ -272,7 +292,7 @@ class Memcached_DataObject extends Safe_DataObject return $pkey; } - static function listFindClass($cls, $keyCol, array $keyVals) + public static function listFindClass($cls, $keyCol, array $keyVals) { $i = new $cls; $i->whereAddIn($keyCol, $keyVals, $i->columnType($keyCol)); @@ -283,7 +303,7 @@ class Memcached_DataObject extends Safe_DataObject return $i; } - static function listGetClass($cls, $keyCol, array $keyVals) + public static function listGetClass($cls, $keyCol, array $keyVals) { $pkeyMap = array_fill_keys($keyVals, array()); $result = array_fill_keys($keyVals, array()); @@ -296,7 +316,7 @@ class Memcached_DataObject extends Safe_DataObject // We only cache keys -- not objects! foreach ($keyVals as $keyVal) { - $l = self::cacheGet(sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal)); + $l = self::cacheGet(sprintf('%s:list-ids:%s:%s', strtolower($cls), $keyCol, $keyVal)); if ($l !== false) { $pkeyMap[$keyVal] = $l; foreach ($l as $pkey) { @@ -312,7 +332,7 @@ class Memcached_DataObject extends Safe_DataObject foreach ($pkeyMap as $keyVal => $pkeyList) { foreach ($pkeyList as $pkeyVal) { - $i = $keyResults[implode(',',$pkeyVal)]; + $i = $keyResults[implode(',', $pkeyVal)]; if (!empty($i)) { $result[$keyVal][] = $i; } @@ -338,15 +358,17 @@ class Memcached_DataObject extends Safe_DataObject // no results found for our keyVals, so we leave them as empty arrays } foreach ($toFetch as $keyVal) { - self::cacheSet(sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal), - $pkeyMap[$keyVal]); + self::cacheSet( + sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal), + $pkeyMap[$keyVal] + ); } } return $result; } - function columnType($columnName) + public function columnType($columnName) { $keys = $this->table(); if (!array_key_exists($columnName, $keys)) { @@ -365,7 +387,7 @@ class Memcached_DataObject extends Safe_DataObject /** * @todo FIXME: Should this return false on lookup fail to match getKV? */ - static function pkeyGetClass($cls, array $kv) + public static function pkeyGetClass($cls, array $kv) { $i = self::multicache($cls, $kv); if ($i !== false) { // false == cache miss @@ -395,7 +417,7 @@ class Memcached_DataObject extends Safe_DataObject } } - function insert() + public function insert() { $result = parent::insert(); if ($result) { @@ -405,7 +427,7 @@ class Memcached_DataObject extends Safe_DataObject return $result; } - function update($dataObject=false) + public function update($dataObject = false) { if (is_object($dataObject) && $dataObject instanceof Memcached_DataObject) { $dataObject->decache(); # might be different keys @@ -418,17 +440,19 @@ class Memcached_DataObject extends Safe_DataObject return $result; } - function delete($useWhere=false) + public function delete($useWhere = false) { $this->decache(); # while we still have the values! return parent::delete($useWhere); } - static function memcache() { + public static function memcache() + { return Cache::instance(); } - static function cacheKey($cls, $k, $v) { + public static function cacheKey($cls, $k, $v) + { if (is_object($cls) || is_object($k) || (is_object($v) && !($v instanceof DB_DataObject_Cast))) { $e = new Exception(); common_log(LOG_ERR, __METHOD__ . ' object in param: ' . @@ -438,7 +462,8 @@ class Memcached_DataObject extends Safe_DataObject return Cache::key(strtolower($cls).':'.$k.':'.$vstr); } - static function getcached($cls, $k, $v) { + public static function getcached($cls, $k, $v) + { $c = self::memcache(); if (!$c) { return false; @@ -456,7 +481,7 @@ class Memcached_DataObject extends Safe_DataObject } } - function keyTypes() + public function keyTypes() { // ini-based classes return number-indexed arrays. handbuilt // classes return column => keytype. Make this uniform. @@ -472,18 +497,17 @@ class Memcached_DataObject extends Safe_DataObject global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { $this->databaseStructure(); - } return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]; } - function encache() + public function encache() { $c = self::memcache(); if (!$c) { return false; - } else if ($this->tableName() == 'user' && is_object($this->id)) { + } elseif ($this->tableName() === 'user' && is_object($this->id)) { // Special case for User bug $e = new Exception(); common_log(LOG_ERR, __METHOD__ . ' caching user with User object as ID ' . @@ -498,7 +522,7 @@ class Memcached_DataObject extends Safe_DataObject } } - function decache() + public function decache() { $c = self::memcache(); @@ -513,7 +537,7 @@ class Memcached_DataObject extends Safe_DataObject } } - function _allCacheKeys() + public function _allCacheKeys() { $ckeys = array(); @@ -524,7 +548,6 @@ class Memcached_DataObject extends Safe_DataObject $pval = array(); foreach ($types as $key => $type) { - assert(!empty($key)); if ($type == 'U') { @@ -532,7 +555,7 @@ class Memcached_DataObject extends Safe_DataObject continue; } $ckeys[] = self::cacheKey($this->tableName(), $key, self::valueString($this->$key)); - } else if ($type == 'K' || $type == 'N') { + } elseif (in_array($type, ['K', 'N'])) { $pkey[] = $key; $pval[] = self::valueString($this->$key); } else { @@ -552,7 +575,7 @@ class Memcached_DataObject extends Safe_DataObject return $ckeys; } - static function multicache($cls, $kv) + public static function multicache($cls, $kv) { ksort($kv); $c = self::memcache(); @@ -563,7 +586,7 @@ class Memcached_DataObject extends Safe_DataObject } } - static function multicacheKey($cls, $kv) + public static function multicacheKey($cls, $kv) { ksort($kv); $pkeys = implode(',', array_keys($kv)); @@ -571,30 +594,35 @@ class Memcached_DataObject extends Safe_DataObject return self::cacheKey($cls, $pkeys, $pvals); } - function getSearchEngine($table) + public function getSearchEngine($table) { require_once INSTALLDIR . '/lib/search/search_engines.php'; - if (Event::handle('GetSearchEngine', array($this, $table, &$search_engine))) { - if ('mysql' === common_config('db', 'type')) { - $type = common_config('search', 'type'); - if ($type == 'like') { - $search_engine = new MySQLLikeSearch($this, $table); - } else if ($type == 'fulltext') { - $search_engine = new MySQLSearch($this, $table); - } else { - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException('Unknown search type: ' . $type); + if (Event::handle('GetSearchEngine', [$this, $table, &$search_engine])) { + $type = common_config('search', 'type'); + if ($type === 'like') { + $search_engine = new SQLLikeSearch($this, $table); + } elseif ($type === 'fulltext') { + switch (common_config('db', 'type')) { + case 'pgsql': + $search_engine = new PostgreSQLSearch($this, $table); + break; + case 'mysql': + $search_engine = new MySQLSearch($this, $table); + break; + default: + throw new ServerException('Unknown DB type selected.'); } } else { - $search_engine = new PGSearch($this, $table); + // Low level exception. No need for i18n as discussed with Brion. + throw new ServerException('Unknown search type: ' . $type); } } return $search_engine; } - static function cachedQuery($cls, $qry, $expiry=3600) + public static function cachedQuery($cls, $qry, $expiry = 3600) { $c = self::memcache(); if (!$c) { @@ -631,7 +659,7 @@ class Memcached_DataObject extends Safe_DataObject * @access private * @return mixed none or PEAR_Error */ - function _query($string) + public function _query($string) { if (common_config('db', 'annotate_queries')) { $string = $this->annotateQuery($string); @@ -680,7 +708,7 @@ class Memcached_DataObject extends Safe_DataObject * @param string $string SQL query string * @return string SQL query string, with a comment in it */ - function annotateQuery($string) + public function annotateQuery($string) { $ignore = array('annotateQuery', '_query', @@ -707,7 +735,7 @@ class Memcached_DataObject extends Safe_DataObject } $here = $frame['class'] . '::' . $func; break; - } else if (isset($frame['type']) && $frame['type'] == '->') { + } elseif (isset($frame['type']) && $frame['type'] === '->') { if ($frame['object'] === $this && in_array($func, $ignore)) { continue; } @@ -736,7 +764,7 @@ class Memcached_DataObject extends Safe_DataObject // Sanitize a query for logging // @fixme don't trim spaces in string literals - function sanitizeQuery($string) + public function sanitizeQuery($string) { $string = preg_replace('/\s+/', ' ', $string); $string = trim($string); @@ -746,7 +774,7 @@ class Memcached_DataObject extends Safe_DataObject // We overload so that 'SET NAMES "utf8mb4"' is called for // each connection - function _connect() + public function _connect() { global $_DB_DATAOBJECT, $_PEAR; @@ -757,7 +785,7 @@ class Memcached_DataObject extends Safe_DataObject $exists = true; } else { $exists = false; - } + } // @fixme horrible evil hack! // @@ -794,7 +822,7 @@ class Memcached_DataObject extends Safe_DataObject if (!empty($conn)) { if ($DB instanceof DB_mysqli || $DB instanceof MDB2_Driver_mysqli) { mysqli_set_charset($conn, 'utf8mb4'); - } else if ($DB instanceof DB_mysql || $DB instanceof MDB2_Driver_mysql) { + } elseif ($DB instanceof DB_mysql || $DB instanceof MDB2_Driver_mysql) { mysql_set_charset('utf8mb4', $conn); } } @@ -810,7 +838,7 @@ class Memcached_DataObject extends Safe_DataObject // XXX: largely cadged from DB_DataObject - function _getDbDsnMD5() + public function _getDbDsnMD5() { if ($this->_database_dsn_md5) { return $this->_database_dsn_md5; @@ -828,7 +856,7 @@ class Memcached_DataObject extends Safe_DataObject return $sum; } - function _getDbDsn() + public function _getDbDsn() { global $_DB_DATAOBJECT; @@ -843,14 +871,13 @@ class Memcached_DataObject extends Safe_DataObject $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; if (!$dsn) { - if (!$this->_database) { $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; } - if ($this->_database && !empty($options["database_{$this->_database}"])) { + if ($this->_database && !empty($options["database_{$this->_database}"])) { $dsn = $options["database_{$this->_database}"]; - } else if (!empty($options['database'])) { + } elseif (!empty($options['database'])) { $dsn = $options['database']; } } @@ -863,7 +890,7 @@ class Memcached_DataObject extends Safe_DataObject return $dsn; } - static function blow() + public static function blow() { $c = self::memcache(); @@ -882,7 +909,7 @@ class Memcached_DataObject extends Safe_DataObject return $c->delete($cacheKey); } - function fixupTimestamps() + public function fixupTimestamps() { // Fake up timestamp columns $columns = $this->table(); @@ -893,12 +920,12 @@ class Memcached_DataObject extends Safe_DataObject } } - function debugDump() + public function debugDump() { common_debug("debugDump: " . common_log_objstring($this)); } - function raiseError($message, $type = null, $behaviour = null) + public function raiseError($message, $type = null, $behavior = null) { $id = get_class($this); if (!empty($this->id)) { @@ -911,7 +938,7 @@ class Memcached_DataObject extends Safe_DataObject throw new ServerException("[$id] DB_DataObject error [$type]: $message"); } - static function cacheGet($keyPart) + public static function cacheGet($keyPart) { $c = self::memcache(); @@ -924,7 +951,7 @@ class Memcached_DataObject extends Safe_DataObject return $c->get($cacheKey); } - static function cacheSet($keyPart, $value, $flag=null, $expiry=null) + public static function cacheSet($keyPart, $value, $flag = null, $expiry = null) { $c = self::memcache(); @@ -937,7 +964,7 @@ class Memcached_DataObject extends Safe_DataObject return $c->set($cacheKey, $value, $flag, $expiry); } - static function valueString($v) + public static function valueString($v) { $vstr = null; if (is_object($v) && $v instanceof DB_DataObject_Cast) { diff --git a/classes/Notice_prefs.php b/classes/Notice_prefs.php index a13d2c44b3..e2c96f482d 100644 --- a/classes/Notice_prefs.php +++ b/classes/Notice_prefs.php @@ -1,32 +1,32 @@ . + /** - * GNU social - * * Data class for Notice preferences * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * * @category Data * @package GNUsocial * @author Mikael Nordfeldth - * @copyright 2013 Free Software Foundation, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ + * @author Diogo Cordeiro + * @copyright 2013 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ +defined('GNUSOCIAL') || die(); + class Notice_prefs extends Managed_DataObject { public $__table = 'notice_prefs'; // table name @@ -58,7 +58,7 @@ class Notice_prefs extends Managed_DataObject ); } - static function getNamespacePrefs(Notice $notice, $namespace, array $topic=array()) + public static function getNamespacePrefs(Notice $notice, $namespace, array $topic = []) { if (empty($topic)) { $prefs = new Notice_prefs(); @@ -76,13 +76,13 @@ class Notice_prefs extends Managed_DataObject return $prefs; } - static function getNamespace(Notice $notice, $namespace, array $topic=array()) + public static function getNamespace(Notice $notice, $namespace, array $topic = []) { $prefs = self::getNamespacePrefs($notice, $namespace, $topic); return $prefs->fetchAll(); } - static function getAll(Notice $notice) + public static function getAll(Notice $notice) { try { $prefs = self::listFind('notice_id', array($notice->getID())); @@ -100,13 +100,17 @@ class Notice_prefs extends Managed_DataObject return $list; } - static function getTopic(Notice $notice, $namespace, $topic) { - return self::getByPK(array('notice_id' => $notice->getID(), - 'namespace' => $namespace, - 'topic' => $topic)); + public static function getTopic(Notice $notice, $namespace, $topic) + { + return self::getByPK([ + 'notice_id' => $notice->getID(), + 'namespace' => $namespace, + 'topic' => $topic, + ]); } - static function getData(Notice $notice, $namespace, $topic, $def=null) { + public static function getData(Notice $notice, $namespace, $topic, $def = null) + { try { $pref = self::getTopic($notice, $namespace, $topic); } catch (NoResultException $e) { @@ -120,7 +124,8 @@ class Notice_prefs extends Managed_DataObject return $pref->data; } - static function getConfigData(Notice $notice, $namespace, $topic) { + public static function getConfigData(Notice $notice, $namespace, $topic) + { try { $data = self::getData($notice, $namespace, $topic); } catch (NoResultException $e) { @@ -140,14 +145,15 @@ class Notice_prefs extends Managed_DataObject * @return true if changes are made, false if no action taken * @throws ServerException if preference could not be saved */ - static function setData(Notice $notice, $namespace, $topic, $data=null) { + public static function setData(Notice $notice, $namespace, $topic, $data = null) + { try { $pref = self::getTopic($notice, $namespace, $topic); if (is_null($data)) { $pref->delete(); } else { $orig = clone($pref); - $pref->data = $data; + $pref->data = DB_DataObject_Cast::blob($data); $pref->update($orig); } return true; @@ -161,7 +167,7 @@ class Notice_prefs extends Managed_DataObject $pref->notice_id = $notice->getID(); $pref->namespace = $namespace; $pref->topic = $topic; - $pref->data = $data; + $pref->data = DB_DataObject_Cast::blob($data); $pref->created = common_sql_now(); if ($pref->insert() === false) { diff --git a/classes/Profile.php b/classes/Profile.php index c0950a856c..17fe104070 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -478,13 +478,24 @@ class Profile extends Managed_DataObject * @return Profile_list resulting lists */ - public function getOtherTags(Profile $scoped = null, $offset = 0, $limit = null, $since_id = 0, $max_id = 0) + public function getOtherTags(Profile $scoped = null, int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) { $list = new Profile_list(); + if (common_config('db', 'type') !== 'mysql') { + $cursor = sprintf( + '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . + 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', + "FROM (profile_tag.modified - TIMESTAMP '1970-01-01 00:00:00')" + ); + } else { + // The SQL/Foundation conforming implementation above doesn't work on MariaDB/MySQL + $cursor = "timestampdiff(SECOND, '1970-01-01', profile_tag.modified) AS `cursor`"; + } + $qry = sprintf( - 'SELECT profile_list.*, unix_timestamp(profile_tag.modified) AS "cursor" ' . - 'FROM profile_tag JOIN profile_list '. + 'SELECT profile_list.*, ' . $cursor . ' ' . + 'FROM profile_tag INNER JOIN profile_list ' . 'ON (profile_tag.tagger = profile_list.tagger ' . ' AND profile_tag.tag = profile_list.tag) ' . 'WHERE profile_tag.tagged = %d ', @@ -502,12 +513,12 @@ class Profile extends Managed_DataObject $qry .= 'AND profile_list.private = FALSE '; } - if ($since_id > 0) { - $qry .= sprintf('AND (cursor > %d) ', $since_id); + if ($since > 0) { + $qry .= 'AND cursor > ' . $since . ' '; } - if ($max_id > 0) { - $qry .= sprintf('AND (cursor < %d) ', $max_id); + if ($upto > 0) { + $qry .= 'AND cursor < ' . $upto . ' '; } $qry .= 'ORDER BY profile_tag.modified DESC '; @@ -558,31 +569,38 @@ class Profile extends Managed_DataObject return ($tags->N == 0) ? false : true; } - public function getTagSubscriptions($offset = 0, $limit = null, $since_id = 0, $max_id = 0) + public function getTagSubscriptions(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) { $lists = new Profile_list(); $subs = new Profile_tag_subscription(); - $lists->joinAdd(array('id', 'profile_tag_subscription:profile_tag_id')); + $lists->joinAdd(['id', 'profile_tag_subscription:profile_tag_id']); - #@fixme: postgres (round(date_part('epoch', my_date))) - $lists->selectAdd('unix_timestamp(profile_tag_subscription.created) as "cursor"'); + if (common_config('db', 'type') !== 'mysql') { + $lists->selectAdd(sprintf( + '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . + 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', + "FROM (profile_tag_subscription.created - TIMESTAMP '1970-01-01 00:00:00')" + )); + } else { + $lists->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag_subscription.created) AS `cursor`"); + } $lists->whereAdd('profile_tag_subscription.profile_id = '.$this->id); - if ($since_id > 0) { - $lists->whereAdd('cursor > ' . $since_id); + if ($since > 0) { + $lists->whereAdd('cursor > ' . $since); } - if ($max_id > 0) { - $lists->whereAdd('cursor <= ' . $max_id); + if ($upto > 0) { + $lists->whereAdd('cursor <= ' . $upto); } if ($offset >= 0 && !is_null($limit)) { $lists->limit($offset, $limit); } - $lists->orderBy('"cursor" DESC'); + $lists->orderBy('profile_tag_subscription.created DESC'); $lists->find(); return $lists; diff --git a/classes/Profile_list.php b/classes/Profile_list.php index f649b80ed1..4e8b231e19 100644 --- a/classes/Profile_list.php +++ b/classes/Profile_list.php @@ -200,7 +200,7 @@ class Profile_list extends Managed_DataObject * @return Profile results */ - public function getSubscribers($offset = 0, $limit = null, $since = 0, $upto = 0) + public function getSubscribers(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) { $subs = new Profile(); @@ -209,8 +209,15 @@ class Profile_list extends Managed_DataObject ); $subs->whereAdd('profile_tag_subscription.profile_tag_id = ' . $this->id); - $subs->selectAdd('unix_timestamp(profile_tag_subscription.' . - 'created) as "cursor"'); + if (common_config('db', 'type') !== 'mysql') { + $subs->selectAdd(sprintf( + '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . + 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', + "FROM (profile_tag_subscription.created - TIMESTAMP '1970-01-01 00:00:00')" + )); + } else { + $subs->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag_subscription.created) AS `cursor`"); + } if ($since != 0) { $subs->whereAdd('cursor > ' . $since); @@ -296,13 +303,21 @@ class Profile_list extends Managed_DataObject * @return Profile results */ - public function getTagged($offset = 0, $limit = null, $since = 0, $upto = 0) + public function getTagged(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) { $tagged = new Profile(); - $tagged->joinAdd(array('id', 'profile_tag:tagged')); + $tagged->joinAdd(['id', 'profile_tag:tagged']); + + if (common_config('db', 'type') !== 'mysql') { + $tagged->selectAdd(sprintf( + '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . + 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', + "FROM (profile_tag.modified - TIMESTAMP '1970-01-01 00:00:00')" + )); + } else { + $tagged->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag.modified) AS `cursor`"); + } - #@fixme: postgres - $tagged->selectAdd('unix_timestamp(profile_tag.modified) as "cursor"'); $tagged->whereAdd('profile_tag.tagger = '.$this->tagger); $tagged->whereAdd("profile_tag.tag = '{$this->tag}'"); diff --git a/classes/Profile_prefs.php b/classes/Profile_prefs.php index d9fcca8baf..d8ad9aab35 100644 --- a/classes/Profile_prefs.php +++ b/classes/Profile_prefs.php @@ -1,32 +1,31 @@ . + /** - * StatusNet, the distributed open-source microblogging tool - * * Data class for Profile preferences * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * * @category Data * @package GNUsocial * @author Mikael Nordfeldth - * @copyright 2013 Free Software Foundation, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ + * @copyright 2013 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ +defined('GNUSOCIAL') || die(); + class Profile_prefs extends Managed_DataObject { public $__table = 'profile_prefs'; // table name @@ -58,7 +57,7 @@ class Profile_prefs extends Managed_DataObject ); } - static function getNamespacePrefs(Profile $profile, $namespace, array $topic=array()) + public static function getNamespacePrefs(Profile $profile, $namespace, array $topic = []) { if (empty($topic)) { $prefs = new Profile_prefs(); @@ -76,13 +75,13 @@ class Profile_prefs extends Managed_DataObject return $prefs; } - static function getNamespace(Profile $profile, $namespace, array $topic=array()) + public static function getNamespace(Profile $profile, $namespace, array $topic = []) { $prefs = self::getNamespacePrefs($profile, $namespace, $topic); return $prefs->fetchAll(); } - static function getAll(Profile $profile) + public static function getAll(Profile $profile) { try { $prefs = self::listFind('profile_id', array($profile->getID())); @@ -100,13 +99,15 @@ class Profile_prefs extends Managed_DataObject return $list; } - static function getTopic(Profile $profile, $namespace, $topic) { + public static function getTopic(Profile $profile, $namespace, $topic) + { return Profile_prefs::getByPK(array('profile_id' => $profile->getID(), 'namespace' => $namespace, 'topic' => $topic)); } - static function getData(Profile $profile, $namespace, $topic, $def=null) { + public static function getData(Profile $profile, $namespace, $topic, $def = null) + { try { $pref = self::getTopic($profile, $namespace, $topic); } catch (NoResultException $e) { @@ -120,7 +121,8 @@ class Profile_prefs extends Managed_DataObject return $pref->data; } - static function getConfigData(Profile $profile, $namespace, $topic) { + public static function getConfigData(Profile $profile, $namespace, $topic) + { try { $data = self::getData($profile, $namespace, $topic); } catch (NoResultException $e) { @@ -140,14 +142,15 @@ class Profile_prefs extends Managed_DataObject * @return true if changes are made, false if no action taken * @throws ServerException if preference could not be saved */ - static function setData(Profile $profile, $namespace, $topic, $data=null) { + public static function setData(Profile $profile, $namespace, $topic, $data = null) + { try { $pref = self::getTopic($profile, $namespace, $topic); if (is_null($data)) { $pref->delete(); } else { $orig = clone($pref); - $pref->data = $data; + $pref->data = DB_DataObject_Cast::blob($data); $pref->update($orig); } return true; @@ -161,7 +164,7 @@ class Profile_prefs extends Managed_DataObject $pref->profile_id = $profile->getID(); $pref->namespace = $namespace; $pref->topic = $topic; - $pref->data = $data; + $pref->data = DB_DataObject_Cast::blob($data); $pref->created = common_sql_now(); if ($pref->insert() === false) { diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php index e58c3e544d..f9678983c4 100644 --- a/extlib/DB/DataObject.php +++ b/extlib/DB/DataObject.php @@ -1804,8 +1804,15 @@ class DB_DataObject extends DB_DataObject_Overload $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? $k : str_replace($replace, '_', $k); - if ($dbtype === 'pgsql' && $tableInfo[$i]['type'] == 'bool') { - $array[$k] = str_replace(['t', 'f'], ['1', '0'], $array[$k]); + if ($dbtype === 'pgsql') { + switch ($tableInfo[$i]['type']) { + case 'bool': + $array[$k] = str_replace(['t', 'f'], ['1', '0'], $array[$k]); + break; + case 'bytea': + $array[$k] = pg_unescape_bytea($array[$k]); + break; + } } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { @@ -2433,7 +2440,7 @@ class DB_DataObject extends DB_DataObject_Overload case 'pgsql': if (!$seq) { - $seq = $DB->getSequenceName(strtolower($this->tableName())); + $seq = $DB->getSequenceName(strtolower($this->tableName() . '_' . $key)); } $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; @@ -2981,8 +2988,15 @@ class DB_DataObject extends DB_DataObject_Overload $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? $k : str_replace($replace, '_', $k); - if ($dbtype === 'pgsql' && $tableInfo[$i]['type'] == 'bool') { - $array[$k] = str_replace(['t', 'f'], ['1', '0'], $array[$k]); + if ($dbtype === 'pgsql') { + switch ($tableInfo[$i]['type']) { + case 'bool': + $array[$k] = str_replace(['t', 'f'], ['1', '0'], $array[$k]); + break; + case 'bytea': + $array[$k] = pg_unescape_bytea($array[$k]); + break; + } } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { diff --git a/lib/database/mysqlschema.php b/lib/database/mysqlschema.php index 8be930f588..1be6ad6977 100644 --- a/lib/database/mysqlschema.php +++ b/lib/database/mysqlschema.php @@ -44,13 +44,14 @@ class MysqlSchema extends Schema * Main public entry point. Use this to get * the singleton object. * - * @param null $conn + * @param object|null $conn + * @param string|null dummy param * @return Schema the (single) Schema object */ - public static function get($conn = null) + public static function get($conn = null, $_ = 'mysql') { if (empty(self::$_single)) { - self::$_single = new Schema($conn); + self::$_single = new Schema($conn, 'mysql'); } return self::$_single; } @@ -105,14 +106,16 @@ class MysqlSchema extends Schema $field['not null'] = true; } if ($row['COLUMN_DEFAULT'] !== null) { - // Hack for timestamp cols - if ($type == 'timestamp' && $row['COLUMN_DEFAULT'] == 'CURRENT_TIMESTAMP') { - // skip because timestamp is numerical, but it accepts datetime strings as well + // Hack for timestamp columns + if ($row['COLUMN_DEFAULT'] === 'current_timestamp()') { + // skip timestamp columns 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']; - if ($this->isNumericType($type)) { - $field['default'] = intval($field['default']); - } } } if ($row['COLUMN_KEY'] !== null) { @@ -305,7 +308,7 @@ class MysqlSchema extends Schema * * @param array $phrase */ - public function appendAlterDropPrimary(array &$phrase) + public function appendAlterDropPrimary(array &$phrase, string $tableName) { $phrase[] = 'DROP PRIMARY KEY'; } @@ -406,6 +409,8 @@ class MysqlSchema extends Schema if ($type == 'int' && in_array($size, ['tiny', 'small', 'medium', 'big'])) { $type = $size . $type; + } elseif ($type == 'float' && $size == 'big') { + $type = 'double'; } elseif (in_array($type, ['blob', 'text']) && in_array($size, ['tiny', 'medium', 'long'])) { $type = $size . $type; diff --git a/lib/database/pgsqlschema.php b/lib/database/pgsqlschema.php index a832d1a6cd..1923c121d7 100644 --- a/lib/database/pgsqlschema.php +++ b/lib/database/pgsqlschema.php @@ -40,6 +40,24 @@ defined('GNUSOCIAL') || die(); */ class PgsqlSchema extends Schema { + public static $_single = null; + + /** + * Main public entry point. Use this to get + * the singleton object. + * + * @param object|null $conn + * @param string|null dummy param + * @return Schema the (single) Schema object + */ + public static function get($conn = null, $_ = 'pgsql') + { + if (empty(self::$_single)) { + self::$_single = new Schema($conn, 'pgsql'); + } + return self::$_single; + } + /** * Returns a table definition array for the table * in the schema with the given name. @@ -72,7 +90,7 @@ class PgsqlSchema extends Schema $field = []; $field['type'] = $type = $row['udt_name']; - if ($type == 'char' || $type == 'varchar') { + if (in_array($type, ['char', 'bpchar', 'varchar'])) { if ($row['character_maximum_length'] !== null) { $field['length'] = intval($row['character_maximum_length']); } @@ -103,7 +121,8 @@ class PgsqlSchema extends Schema // Pulling index info from pg_class & pg_index // This can give us primary & unique key info, but not foreign key constraints // so we exclude them and pick them up later. - $indexInfo = $this->getIndexInfo($table); + $indexInfo = $this->fetchIndexInfo($table); + foreach ($indexInfo as $row) { $keyName = $row['key_name']; @@ -144,7 +163,7 @@ class PgsqlSchema extends Schema if ($keyName == "{$table}_pkey") { $def['primary key'] = $cols; } elseif (preg_match("/^{$table}_(.*)_fkey$/", $keyName, $matches)) { - $fkey = $this->getForeignKeyInfo($table, $keyName); + $fkey = $this->fetchForeignKeyInfo($table, $keyName); $colMap = array_combine($cols, $fkey['col_names']); $def['foreign keys'][$keyName] = [$fkey['table_name'], $colMap]; } else { @@ -180,47 +199,42 @@ class PgsqlSchema extends Schema * @return array of arrays * @throws PEAR_Exception */ - public function getIndexInfo($table) + public function fetchIndexInfo(string $table): array { - $query = 'SELECT ' . - '(SELECT relname FROM pg_class WHERE oid=indexrelid) AS key_name, ' . - '* FROM pg_index ' . - 'WHERE indrelid=(SELECT oid FROM pg_class WHERE relname=\'%s\') ' . - 'AND indisprimary=\'f\' AND indisunique=\'f\' ' . + $query = 'SELECT indexname AS key_name, indexdef AS key_def, pg_index.* ' . + 'FROM pg_index INNER JOIN pg_indexes ON pg_index.indexrelid = CAST(pg_indexes.indexname AS regclass) ' . + 'WHERE tablename = \'%s\' AND indisprimary = FALSE AND indisunique = FALSE ' . 'ORDER BY indrelid, indexrelid'; $sql = sprintf($query, $table); return $this->fetchQueryData($sql); } /** - * Column names from the foreign table can be resolved with a call to getTableColumnNames() * @param string $table - * @param $constraint_name - * @return array array of rows with keys: fkey_name, table_name, table_id, col_names (array of strings) + * @param string $constraint_name + * @return array array of rows with keys: table_name, col_names (array of strings) * @throws PEAR_Exception */ - public function getForeignKeyInfo($table, $constraint_name) + public function fetchForeignKeyInfo(string $table, string $constraint_name): array { // In a sane world, it'd be easier to query the column names directly. // But it's pretty hard to work with arrays such as col_indexes in direct SQL here. $query = 'SELECT ' . - '(SELECT relname FROM pg_class WHERE oid=confrelid) AS table_name, ' . + '(SELECT relname FROM pg_class WHERE oid = confrelid) AS table_name, ' . 'confrelid AS table_id, ' . - '(SELECT indkey FROM pg_index WHERE indexrelid=conindid) AS col_indexes ' . + '(SELECT indkey FROM pg_index WHERE indexrelid = conindid) AS col_indices ' . 'FROM pg_constraint ' . - 'WHERE conrelid=(SELECT oid FROM pg_class WHERE relname=\'%s\') ' . - 'AND conname=\'%s\' ' . - 'AND contype=\'f\''; + 'WHERE conrelid = CAST(\'%s\' AS regclass) AND conname = \'%s\' AND contype = \'f\''; $sql = sprintf($query, $table, $constraint_name); $data = $this->fetchQueryData($sql); if (count($data) < 1) { - throw new Exception("Could not find foreign key " . $constraint_name . " on table " . $table); + throw new Exception('Could not find foreign key ' . $constraint_name . ' on table ' . $table); } $row = $data[0]; return [ 'table_name' => $row['table_name'], - 'col_names' => $this->getTableColumnNames($row['table_id'], $row['col_indexes']) + 'col_names' => $this->getTableColumnNames($row['table_id'], $row['col_indices']) ]; } @@ -252,23 +266,6 @@ class PgsqlSchema extends Schema return $out; } - /** - * Translate the (mostly) mysql-ish column types into somethings more standard - * @param string column type - * - * @return string postgres happy column type - */ - private function _columnTypeTranslation($type) - { - $map = [ - 'datetime' => 'timestamp', - ]; - if (!empty($map[$type])) { - return $map[$type]; - } - return $type; - } - /** * Return the proper SQL for creating or * altering a column. @@ -296,12 +293,15 @@ class PgsqlSchema extends Schema } */ - if (!empty($cd['enum'])) { - foreach($cd['enum'] as &$val) { + // This'll have been added from our transform of 'serial' type + if (!empty($cd['auto_increment'])) { + $line[] = 'GENERATED BY DEFAULT AS IDENTITY'; + } elseif (!empty($cd['enum'])) { + foreach ($cd['enum'] as &$val) { $vals[] = "'" . $val . "'"; } $line[] = 'CHECK (' . $name . ' IN (' . implode(',', $vals) . '))'; - } + } return implode(' ', $line); } @@ -338,6 +338,12 @@ class PgsqlSchema extends Schema } } + public function appendAlterDropPrimary(array &$phrase, string $tableName) + { + // name hack -- is this reliable? + $phrase[] = 'DROP CONSTRAINT ' . $this->quoteIdentifier($tableName . '_pkey'); + } + /** * Append an SQL statement to drop an index from a table. * Note that in PostgreSQL, index names are DB-unique. @@ -354,9 +360,8 @@ class PgsqlSchema extends Schema public function mapType($column) { $map = [ - 'serial' => 'bigserial', // FIXME: creates the wrong name for the sequence for some internal sequence-lookup function, so better fix this to do the real 'create sequence' dance. - 'bool' => 'boolean', - 'numeric' => 'decimal', + 'integer' => 'int', + 'char' => 'bpchar', 'datetime' => 'timestamp', 'blob' => 'bytea', 'enum' => 'text', @@ -367,16 +372,17 @@ class PgsqlSchema extends Schema $type = $map[$type]; } - if ($type == 'int') { - if (!empty($column['size'])) { - $size = $column['size']; - if ($size == 'small') { - return 'int2'; - } elseif ($size == 'big') { - return 'int8'; - } + $size = $column['size'] ?? null; + if ($type === 'int') { + if (in_array($size, ['tiny', 'small'])) { + $type = 'int2'; + } elseif ($size === 'big') { + $type = 'int8'; + } else { + $type = 'int4'; } - return 'int4'; + } elseif ($type === 'float') { + $type = ($size !== 'big') ? 'float4' : 'float8'; } return $type; @@ -398,17 +404,33 @@ class PgsqlSchema extends Schema // No convenient support for field descriptions unset($col['description']); - /* - if (isset($col['size'])) { - // Don't distinguish between tinyint and int. - if ($col['size'] == 'tiny' && $col['type'] == 'int') { - unset($col['size']); - } + switch ($col['type']) { + case 'serial': + $col['type'] = 'int'; + $col['auto_increment'] = true; + break; + case 'datetime': + // 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); unset($col['size']); } + if (!empty($tableDef['primary key'])) { $tableDef['primary key'] = $this->filterKeyDef($tableDef['primary key']); } diff --git a/lib/database/schema.php b/lib/database/schema.php index e9b802c978..6c6ac4e6a6 100644 --- a/lib/database/schema.php +++ b/lib/database/schema.php @@ -64,9 +64,10 @@ class Schema * the schema object. * * @param object|null $conn + * @param string|null Force a database type (necessary for installation purposes in which we don't have a config.php) * @return Schema the Schema object for the connection */ - public static function get($conn = null) + public static function get($conn = null, $dbtype = null) { if (is_null($conn)) { $key = 'default'; @@ -74,9 +75,11 @@ class Schema $key = md5(serialize($conn->dsn)); } - $type = common_config('db', 'type'); + if (is_null($dbtype)) { + $dbtype = common_config('db', 'type'); + } if (empty(self::$_static[$key])) { - $schemaClass = ucfirst($type) . 'Schema'; + $schemaClass = ucfirst($dbtype) . 'Schema'; self::$_static[$key] = new $schemaClass($conn); } return self::$_static[$key]; @@ -369,6 +372,8 @@ class Schema { global $_PEAR; + $qry = []; + if (!is_array($columnNames)) { $columnNames = [$columnNames]; } @@ -377,11 +382,9 @@ class Schema $name = "{$table}_" . implode("_", $columnNames) . "_idx"; } - $res = $this->conn->query( - 'ALTER TABLE ' . $this->quoteIdentifier($table) . - ' ADD INDEX ' . $name . ' (' . - implode(',', $columnNames) . ')' - ); + $this->appendCreateIndex($qry, $table, $name, $columnNames); + + $res = $this->conn->query(implode('; ', $qry)); if ($_PEAR->isError($res)) { PEAR_ErrorToPEAR_Exception($res); @@ -602,7 +605,7 @@ class Schema } if (isset($old['primary key']) && (!isset($def['primary key']) || $def['primary key'] != $old['primary key'])) { - $this->appendAlterDropPrimary($phrase); + $this->appendAlterDropPrimary($phrase, $tableName); } foreach ($fields['add'] as $columnName) { @@ -765,7 +768,7 @@ class Schema $phrase[] = implode(' ', $sql); } - public function appendAlterDropPrimary(array &$phrase) + public function appendAlterDropPrimary(array &$phrase, string $tableName) { $phrase[] = 'DROP CONSTRAINT PRIMARY KEY'; } diff --git a/lib/search/search_engines.php b/lib/search/search_engines.php index 6f3916e6de..070c0760d1 100644 --- a/lib/search/search_engines.php +++ b/lib/search/search_engines.php @@ -70,6 +70,43 @@ class SearchEngine } } +class PostgreSQLSearch extends SearchEngine +{ + public function query($q) + { + if ($this->table === 'profile') { + $cols = implode(" || ' ' || ", array_map( + function ($col) { + return sprintf( + "COALESCE(%s.%s, '')", + common_database_tablename($this->table), + $col + ); + }, + ['nickname', 'fullname', 'location', 'bio', 'homepage'] + )); + + $this->target->whereAdd(sprintf( + 'to_tsvector(\'english\', %2$s) @@ plainto_tsquery(\'%1$s\')', + $this->target->escape($q, true), + $cols + )); + return true; + } elseif ($this->table === 'notice') { + // Don't show imported notices + $this->target->whereAdd('notice.is_local <> ' . Notice::GATEWAY); + + $this->target->whereAdd(sprintf( + 'to_tsvector(\'english\', content) @@ plainto_tsquery(\'%1$s\')', + $this->target->escape($q, true) + )); + return true; + } else { + throw new ServerException('Unknown table: ' . $this->table); + } + } +} + class MySQLSearch extends SearchEngine { public function query($q) @@ -120,7 +157,7 @@ class MySQLSearch extends SearchEngine } } -class MySQLLikeSearch extends SearchEngine +class SQLLikeSearch extends SearchEngine { public function query($q) { @@ -145,18 +182,3 @@ class MySQLLikeSearch extends SearchEngine return true; } } - -class PGSearch extends SearchEngine -{ - public function query($q) - { - if ($this->table === 'profile') { - return $this->target->whereAdd('textsearch @@ plainto_tsquery(\'' . $this->target->escape($q) . '\')'); - } elseif ($this->table === 'notice') { - // XXX: We need to filter out gateway notices (notice.is_local = -2) --Zach - return $this->target->whereAdd('to_tsvector(\'english\', content) @@ plainto_tsquery(\'' . $this->target->escape($q) . '\')'); - } else { - throw new ServerException('Unknown table: ' . $this->table); - } - } -} diff --git a/lib/util/installer.php b/lib/util/installer.php index b4cb322ae2..9115ebdec4 100644 --- a/lib/util/installer.php +++ b/lib/util/installer.php @@ -68,11 +68,11 @@ abstract class Installer 'check_module' => 'mysqli', 'scheme' => 'mysqli', // DSN prefix for PEAR::DB ], - /*'pgsql' => [ - 'name' => 'PostgreSQL', + 'pgsql' => [ + 'name' => 'PostgreSQL 11+', 'check_module' => 'pgsql', 'scheme' => 'pgsql', // DSN prefix for PEAR::DB - ]*/ + ] ]; /** @@ -304,20 +304,34 @@ abstract class Installer throw new Exception('Cannot connect to database: ' . $conn->getMessage()); } - // ensure database encoding is UTF8 - $conn->query('SET NAMES utf8mb4'); - if ($this->dbtype == 'mysql') { - $server_encoding = $conn->getRow("SHOW VARIABLES LIKE 'character_set_server'")[1]; - if ($server_encoding != 'utf8mb4') { - $this->updateStatus("GNU social requires UTF8 character encoding. Your database is " . htmlentities($server_encoding)); + switch ($this->dbtype) { + case 'pgsql': + // ensure the database encoding is UTF8 + $conn->query("SET NAMES 'UTF8'"); + $server_encoding = $conn->getRow('SHOW server_encoding')[0]; + if ($server_encoding !== 'UTF8') { + $this->updateStatus( + 'GNU social requires the UTF8 character encoding. Yours is ' . + htmlentities($server_encoding) + ); + return false; + } + break; + case 'mysql': + // ensure the database encoding is utf8mb4 + $conn->query("SET NAMES 'utf8mb4'"); + $server_encoding = $conn->getRow("SHOW VARIABLES LIKE 'character_set_server'")[1]; + if ($server_encoding !== 'utf8mb4') { + $this->updateStatus( + 'GNU social requires the utf8mb4 character encoding. Yours is ' . + htmlentities($server_encoding) + ); + return false; + } + break; + default: + $this->updateStatus('Unknown DB type selected: ' . $this->dbtype); return false; - } - } elseif ($this->dbtype == 'pgsql') { - $server_encoding = $conn->getRow('SHOW server_encoding')[0]; - if ($server_encoding != 'UTF8') { - $this->updateStatus("GNU social requires UTF8 character encoding. Your database is " . htmlentities($server_encoding)); - return false; - } } $res = $this->updateStatus("Creating database tables..."); @@ -362,7 +376,7 @@ abstract class Installer */ public function createCoreTables(DB_common $conn): bool { - $schema = Schema::get($conn); + $schema = Schema::get($conn, $this->dbtype); $tableDefs = $this->getCoreSchema(); foreach ($tableDefs as $name => $def) { if (defined('DEBUG_INSTALLER')) { diff --git a/plugins/ActivitySpam/classes/Spam_score.php b/plugins/ActivitySpam/classes/Spam_score.php index e8b3311fe9..8c4e735373 100644 --- a/plugins/ActivitySpam/classes/Spam_score.php +++ b/plugins/ActivitySpam/classes/Spam_score.php @@ -83,7 +83,8 @@ class Spam_score extends Managed_DataObject 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice getting scored'), - 'score' => array('type' => 'double', + 'score' => array('type' => 'float', + 'size' => 'big', 'not null' => true, 'description' => 'score for the notice (0.0, 1.0)'), 'scaled' => array('type' => 'int', diff --git a/plugins/DBQueue/classes/DBQueueManager.php b/plugins/DBQueue/classes/DBQueueManager.php index bf8e7fe5d4..fcf9cf8526 100644 --- a/plugins/DBQueue/classes/DBQueueManager.php +++ b/plugins/DBQueue/classes/DBQueueManager.php @@ -1,33 +1,32 @@ . + /** - * StatusNet, the distributed open-source microblogging tool - * * Simple-minded queue manager for storing items in the database * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * * @category QueueManager - * @package StatusNet + * @package GNUsocial * @author Evan Prodromou * @author Brion Vibber * @copyright 2009-2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ +defined('GNUSOCIAL') || die(); + class DBQueueManager extends QueueManager { /** @@ -39,7 +38,7 @@ class DBQueueManager extends QueueManager { $qi = new Queue_item(); - $qi->frame = $this->encode($object); + $qi->frame = DB_DataObject_Cast::blob($this->encode($object)); $qi->transport = $queue; $qi->created = common_sql_now(); $result = $qi->insert(); @@ -121,7 +120,8 @@ class DBQueueManager extends QueueManager // What to do if no handler was found. For example, the OpportunisticQM // should avoid deleting items just because it can't reach XMPP queues etc. - protected function noHandlerFound(Queue_item $qi, $rep=null) { + protected function noHandlerFound(Queue_item $qi, $rep = null) + { $this->_log(LOG_INFO, "[{$qi->transport}:{$rep}] No handler for queue {$qi->transport}; discarding."); $this->_done($qi); } diff --git a/plugins/OStatus/scripts/update-profile-data.php b/plugins/OStatus/scripts/update-profile-data.php index 5b3e00e9fc..a6454bca7d 100755 --- a/plugins/OStatus/scripts/update-profile-data.php +++ b/plugins/OStatus/scripts/update-profile-data.php @@ -1,21 +1,23 @@ #!/usr/bin/env php . +// This file is part of GNU social - https://www.gnu.org/software/social +// +// GNU social is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GNU social is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with GNU social. If not, see . + +/** + * @copyright 2010 StatusNet, Inc. + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); @@ -39,7 +41,8 @@ END_OF_HELP; require_once INSTALLDIR.'/scripts/commandline.inc'; -function showProfileInfo(Ostatus_profile $oprofile) { +function showProfileInfo(Ostatus_profile $oprofile) +{ if ($oprofile->isGroup()) { echo "group\n"; } else { @@ -51,7 +54,8 @@ function showProfileInfo(Ostatus_profile $oprofile) { echo "\n"; } -function fixProfile(Ostatus_profile $oprofile) { +function fixProfile(Ostatus_profile $oprofile) +{ echo "Before:\n"; showProfileInfo($oprofile); @@ -102,10 +106,10 @@ if (have_option('all')) { echo "Failed on URI=="._ve($oprofile->uri).": {$e->getMessage()}\n"; } } -} else if (have_option('suspicious')) { +} elseif (have_option('suspicious')) { $oprofile = new Ostatus_profile(); - $oprofile->joinAdd(array('profile_id', 'profile:id')); - $oprofile->whereAdd("nickname rlike '^[0-9]$'"); + $oprofile->joinAdd(['profile_id', 'profile:id']); + $oprofile->whereAdd("CHAR_LENGTH(nickname) = 1 AND nickname BETWEEN '0' AND '9'"); $oprofile->find(); echo "Found $oprofile->N matching profiles:\n\n"; while ($oprofile->fetch()) { @@ -116,7 +120,7 @@ if (have_option('all')) { echo "Failed on URI=="._ve($oprofile->uri).": {$e->getMessage()}\n"; } } -} else if (!empty($args[0]) && $validate->uri($args[0])) { +} elseif (!empty($args[0]) && $validate->uri($args[0])) { $uri = $args[0]; $oprofile = Ostatus_profile::getKV('uri', $uri); diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index 964d098030..5237cc81ea 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -1,25 +1,25 @@ . +// This file is part of GNU social - https://www.gnu.org/software/social +// +// GNU social is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GNU social is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with GNU social. If not, see . + +/** + * @copyright 2008, 2009 StatusNet, Inc. + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -if (!defined('STATUSNET')) { - exit(1); -} +defined('GNUSOCIAL') || die(); require_once('Auth/OpenID.php'); require_once('Auth/OpenID/Consumer.php'); @@ -49,19 +49,19 @@ function oid_store() } $db = DB::connect($dsn, $options); - if (PEAR::isError($db)) { + if ((new PEAR)->isError($db)) { throw new ServerException($db->getMessage()); } switch (common_config('db', 'type')) { + case 'pgsql': + $store = new Auth_OpenID_PostgreSQLStore($db); + break; case 'mysql': $store = new Auth_OpenID_MySQLStore($db); break; - case 'postgresql': - $store = new Auth_OpenID_PostgreSQLStore($db); - break; default: - throw new ServerException(_m('Unknown DB type for OpenID.')); + throw new ServerException('Unknown DB type selected.'); } } return $store; @@ -90,9 +90,11 @@ function oid_clear_last() function oid_set_last($openid_url) { - common_set_cookie(OPENID_COOKIE_KEY, - $openid_url, - time() + OPENID_COOKIE_EXPIRY); + common_set_cookie( + OPENID_COOKIE_KEY, + $openid_url, + time() + OPENID_COOKIE_EXPIRY + ); } function oid_get_last() @@ -119,7 +121,7 @@ function oid_link_user($id, $canonical, $display) $oid->created = common_sql_now(); if (!$oid->insert()) { - $err = &$_PEAR->getStaticProperty('DB_DataObject','lastError'); + $err = &$_PEAR->getStaticProperty('DB_DataObject', 'lastError'); return false; } @@ -149,9 +151,11 @@ function oid_check_immediate($openid_url, $backto=null) $_SESSION['openid_immediate_backto'] = $backto; - oid_authenticate($openid_url, - 'finishimmediate', - true); + oid_authenticate( + $openid_url, + 'finishimmediate', + true + ); } function oid_authenticate($openid_url, $returnto, $immediate=false) @@ -177,23 +181,27 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) common_log(LOG_ERR, __METHOD__ . ": mystery fail contacting $openid_url"); // TRANS: OpenID plugin message. Given when an OpenID is not valid. throw new ServerException(_m('Not a valid OpenID.')); - } else if (Auth_OpenID::isFailure($auth_request)) { + } elseif (Auth_OpenID::isFailure($auth_request)) { common_log(LOG_ERR, __METHOD__ . ": OpenID fail to $openid_url: $auth_request->message"); // TRANS: OpenID plugin server error. Given when the OpenID authentication request fails. // TRANS: %s is the failure message. throw new ServerException(sprintf(_m('OpenID failure: %s.'), $auth_request->message)); } - $sreg_request = Auth_OpenID_SRegRequest::build(// Required - array(), - // Optional - array('nickname', - 'email', - 'fullname', - 'language', - 'timezone', - 'postcode', - 'country')); + $sreg_request = Auth_OpenID_SRegRequest::build( + // Required + [], + // Optional + [ + 'nickname', + 'email', + 'fullname', + 'language', + 'timezone', + 'postcode', + 'country', + ] + ); if ($sreg_request) { $auth_request->addExtension($sreg_request); @@ -224,9 +232,11 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) // autosubmitter for now. // //if ($auth_request->shouldSendRedirect()) { - $redirect_url = $auth_request->redirectURL($trust_root, - $process_url, - $immediate); + $redirect_url = $auth_request->redirectURL( + $trust_root, + $process_url, + $immediate + ); if (Auth_OpenID::isFailure($redirect_url)) { // TRANS: OpenID plugin server error. Given when the OpenID authentication request cannot be redirected. // TRANS: %s is the failure message. @@ -266,11 +276,14 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) function _oid_print_instructions() { - common_element('div', 'instructions', - // TRANS: OpenID plugin user instructions. - _m('This form should automatically submit itself. '. - 'If not, click the submit button to go to your '. - 'OpenID provider.')); + common_element( + 'div', + 'instructions', + // TRANS: OpenID plugin user instructions. + _m('This form should automatically submit itself. '. + 'If not, click the submit button to go to your '. + 'OpenID provider.') + ); } /** @@ -382,22 +395,22 @@ function oid_check_teams($response) class AutosubmitAction extends Action { - var $form_html = null; - var $form_id = null; + public $form_html = null; + public $form_id = null; - function handle() + public function handle() { parent::handle(); $this->showPage(); } - function title() + public function title() { // TRANS: Title return _m('OpenID Login Submission'); } - function showContent() + public function showContent() { $this->raw('

'); // @todo FIXME: This would be better using standard CSS class, but the present theme's a bit scary. @@ -414,10 +427,13 @@ class AutosubmitAction extends Action $this->raw($this->form_html); } - function showScripts() + public function showScripts() { parent::showScripts(); - $this->element('script', null, - 'document.getElementById(\'' . $this->form_id . '\').submit();'); + $this->element( + 'script', + null, + 'document.getElementById(\'' . $this->form_id . '\').submit();' + ); } } diff --git a/plugins/SphinxSearch/README b/plugins/SphinxSearch/README index 873a8cf692..a8e0d20d8b 100644 --- a/plugins/SphinxSearch/README +++ b/plugins/SphinxSearch/README @@ -22,7 +22,7 @@ client side, which itself depends on the sphinx development files. "pecl install sphinx" should take care of that. Add "extension=sphinx.so" to your php.ini and reload apache to enable it. -You can update your MySQL or Postgresql databases to drop their fulltext +You can update your MariaDB or PostgreSQL databases to drop their fulltext search indexes, since they're now provided by sphinx. diff --git a/scripts/upgrade.php b/scripts/upgrade.php index d60e072781..1178d59769 100755 --- a/scripts/upgrade.php +++ b/scripts/upgrade.php @@ -605,14 +605,26 @@ function fixupFileThumbnailUrlhash() { printfnq("Setting urlhash for File_thumbnail entries: "); + switch (common_config('db', 'type')) { + case 'pgsql': + $url_sha256 = 'encode(sha256(CAST("url" AS bytea)), \'hex\')'; + break; + case 'mysql': + $url_sha256 = 'sha2(`url`, 256)'; + break; + default: + throw new Exception('Unknown DB type selected.'); + } + $thumb = new File_thumbnail(); $thumb->query(sprintf( 'UPDATE %1$s ' . - 'SET urlhash = sha2(url, 256) ' . + 'SET urlhash = %2$s ' . 'WHERE url IS NOT NULL ' . // find all entries with a url value "AND url <> '' " . // precaution against non-null empty strings 'AND urlhash IS NULL', // but don't touch those we've already calculated - $thumb->escapedTableName() + $thumb->escapedTableName(), + $url_sha256 )); printfnq("DONE.\n");