[DATABASE] Re-introduce PostgreSQL support

This commit is contained in:
Alexei Sorokin 2019-09-11 14:14:40 +03:00
parent 644b417f6c
commit ee7f0a2016
22 changed files with 606 additions and 410 deletions

View File

@ -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

View File

@ -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');

View File

@ -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...";

View File

@ -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...";

View File

@ -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 {

View File

@ -1,23 +1,25 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* 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 <http://www.gnu.org/licenses/>.
// 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 <http://www.gnu.org/licenses/>.
/**
* @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) {

View File

@ -1,32 +1,32 @@
<?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 <http://www.gnu.org/licenses/>.
/**
* 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 <http://www.gnu.org/licenses/>.
*
* @category Data
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @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 <diogo@fc.up.pt>
* @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) {

View File

@ -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;

View File

@ -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}'");

View File

@ -1,32 +1,31 @@
<?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 <http://www.gnu.org/licenses/>.
/**
* 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 <http://www.gnu.org/licenses/>.
*
* @category Data
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @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) {

View File

@ -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'])) {

View File

@ -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;

View File

@ -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