improved database backend support for larger files (100 KiB - 16 MiB),
introduced database versioning to reduce amount of checks done per request
This commit is contained in:
parent
9d27e7a65d
commit
42a9c92b5e
3 changed files with 281 additions and 81 deletions
|
@ -22,7 +22,7 @@ class zerobin
|
||||||
*
|
*
|
||||||
* @const string
|
* @const string
|
||||||
*/
|
*/
|
||||||
const VERSION = '0.21.1';
|
const VERSION = '0.22';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the same error message if the paste expired or does not exist
|
* show the same error message if the paste expired or does not exist
|
||||||
|
|
|
@ -84,102 +84,54 @@ class zerobin_db extends zerobin_abstract
|
||||||
$options['opt'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
|
$options['opt'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
|
||||||
$options['opt'][PDO::ATTR_EMULATE_PREPARES] = false;
|
$options['opt'][PDO::ATTR_EMULATE_PREPARES] = false;
|
||||||
$options['opt'][PDO::ATTR_PERSISTENT] = true;
|
$options['opt'][PDO::ATTR_PERSISTENT] = true;
|
||||||
|
$db_tables_exist = true;
|
||||||
|
|
||||||
// check if the database contains the required tables
|
// setup type and dabase connection
|
||||||
self::$_type = strtolower(
|
self::$_type = strtolower(
|
||||||
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
|
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
|
||||||
);
|
);
|
||||||
switch(self::$_type)
|
$tableQuery = self::_getTableQuery(self::$_type);
|
||||||
{
|
|
||||||
case 'ibm':
|
|
||||||
$sql = 'SELECT tabname FROM SYSCAT.TABLES ';
|
|
||||||
break;
|
|
||||||
case 'informix':
|
|
||||||
$sql = 'SELECT tabname FROM systables ';
|
|
||||||
break;
|
|
||||||
case 'mssql':
|
|
||||||
$sql = "SELECT name FROM sysobjects "
|
|
||||||
. "WHERE type = 'U' ORDER BY name";
|
|
||||||
break;
|
|
||||||
case 'mysql':
|
|
||||||
$sql = 'SHOW TABLES';
|
|
||||||
break;
|
|
||||||
case 'oci':
|
|
||||||
$sql = 'SELECT table_name FROM all_tables';
|
|
||||||
break;
|
|
||||||
case 'pgsql':
|
|
||||||
$sql = "SELECT c.relname AS table_name "
|
|
||||||
. "FROM pg_class c, pg_user u "
|
|
||||||
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
|
|
||||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
|
||||||
. "AND c.relname !~ '^(pg_|sql_)' "
|
|
||||||
. "UNION "
|
|
||||||
. "SELECT c.relname AS table_name "
|
|
||||||
. "FROM pg_class c "
|
|
||||||
. "WHERE c.relkind = 'r' "
|
|
||||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
|
||||||
. "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
|
|
||||||
. "AND c.relname !~ '^pg_'";
|
|
||||||
break;
|
|
||||||
case 'sqlite':
|
|
||||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
|
||||||
. "UNION ALL SELECT name FROM sqlite_temp_master "
|
|
||||||
. "WHERE type='table' ORDER BY name";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Exception(
|
|
||||||
'PDO type ' .
|
|
||||||
self::$_type .
|
|
||||||
' is currently not supported.',
|
|
||||||
5
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self::$_db = new PDO(
|
self::$_db = new PDO(
|
||||||
$options['dsn'],
|
$options['dsn'],
|
||||||
$options['usr'],
|
$options['usr'],
|
||||||
$options['pwd'],
|
$options['pwd'],
|
||||||
$options['opt']
|
$options['opt']
|
||||||
);
|
);
|
||||||
$statement = self::$_db->query($sql);
|
|
||||||
$tables = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
|
|
||||||
|
|
||||||
// create paste table if needed
|
// check if the database contains the required tables
|
||||||
|
$tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
// create paste table if necessary
|
||||||
if (!in_array(self::$_prefix . 'paste', $tables))
|
if (!in_array(self::$_prefix . 'paste', $tables))
|
||||||
{
|
{
|
||||||
self::$_db->exec(
|
self::_createPasteTable();
|
||||||
'CREATE TABLE ' . self::$_prefix . 'paste ( ' .
|
$db_tables_exist = false;
|
||||||
'dataid CHAR(16), ' .
|
|
||||||
'data TEXT, ' .
|
|
||||||
'postdate INT, ' .
|
|
||||||
'expiredate INT, ' .
|
|
||||||
'opendiscussion INT, ' .
|
|
||||||
'burnafterreading INT, ' .
|
|
||||||
'meta TEXT );'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// check if the meta column exists
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
self::_exec('SELECT meta FROM ' . self::$_prefix . 'paste LIMIT 1;', array());
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
self::$_db->exec('ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN meta TEXT;');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create comment table if needed
|
// create comment table if necessary
|
||||||
if (!in_array(self::$_prefix . 'comment', $tables))
|
if (!in_array(self::$_prefix . 'comment', $tables))
|
||||||
{
|
{
|
||||||
self::$_db->exec(
|
self::_createCommentTable();
|
||||||
'CREATE TABLE ' . self::$_prefix . 'comment ( ' .
|
$db_tables_exist = false;
|
||||||
'dataid CHAR(16), ' .
|
}
|
||||||
'pasteid CHAR(16), ' .
|
|
||||||
'parentid CHAR(16), ' .
|
// create config table if necessary
|
||||||
'data TEXT, ' .
|
$db_version = zerobin::VERSION;
|
||||||
'nickname VARCHAR(255), ' .
|
if (!in_array(self::$_prefix . 'config', $tables))
|
||||||
'vizhash TEXT, ' .
|
{
|
||||||
'postdate INT );'
|
self::_createConfigTable();
|
||||||
);
|
// if we only needed to create the config table, the DB is older then 0.22
|
||||||
|
if ($db_tables_exist) $db_version = '0.21';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$db_version = self::_getConfig('VERSION');
|
||||||
|
}
|
||||||
|
|
||||||
|
// update database structure if necessary
|
||||||
|
if (version_compare($db_version, zerobin::VERSION, '<'))
|
||||||
|
{
|
||||||
|
self::_upgradeDatabase($db_version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,6 +160,7 @@ class zerobin_db extends zerobin_abstract
|
||||||
}
|
}
|
||||||
|
|
||||||
$opendiscussion = $burnafterreading = false;
|
$opendiscussion = $burnafterreading = false;
|
||||||
|
$attachment = $attachmentname = '';
|
||||||
$meta = $paste['meta'];
|
$meta = $paste['meta'];
|
||||||
unset($meta['postdate']);
|
unset($meta['postdate']);
|
||||||
$expire_date = 0;
|
$expire_date = 0;
|
||||||
|
@ -226,8 +179,18 @@ class zerobin_db extends zerobin_abstract
|
||||||
$burnafterreading = (bool) $paste['meta']['burnafterreading'];
|
$burnafterreading = (bool) $paste['meta']['burnafterreading'];
|
||||||
unset($meta['burnafterreading']);
|
unset($meta['burnafterreading']);
|
||||||
}
|
}
|
||||||
|
if (array_key_exists('attachment', $paste['meta']))
|
||||||
|
{
|
||||||
|
$attachment = $paste['meta']['attachment'];
|
||||||
|
unset($meta['attachment']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('attachmentname', $paste['meta']))
|
||||||
|
{
|
||||||
|
$attachmentname = $paste['meta']['attachmentname'];
|
||||||
|
unset($meta['attachmentname']);
|
||||||
|
}
|
||||||
return self::_exec(
|
return self::_exec(
|
||||||
'INSERT INTO ' . self::$_prefix . 'paste VALUES(?,?,?,?,?,?,?)',
|
'INSERT INTO ' . self::$_prefix . 'paste VALUES(?,?,?,?,?,?,?,?,?)',
|
||||||
array(
|
array(
|
||||||
$pasteid,
|
$pasteid,
|
||||||
$paste['data'],
|
$paste['data'],
|
||||||
|
@ -236,6 +199,8 @@ class zerobin_db extends zerobin_abstract
|
||||||
(int) $opendiscussion,
|
(int) $opendiscussion,
|
||||||
(int) $burnafterreading,
|
(int) $burnafterreading,
|
||||||
json_encode($meta),
|
json_encode($meta),
|
||||||
|
$attachment,
|
||||||
|
$attachmentname,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -265,6 +230,8 @@ class zerobin_db extends zerobin_abstract
|
||||||
|
|
||||||
$meta = json_decode($paste['meta']);
|
$meta = json_decode($paste['meta']);
|
||||||
if (!is_object($meta)) $meta = new stdClass;
|
if (!is_object($meta)) $meta = new stdClass;
|
||||||
|
|
||||||
|
// support older attachments
|
||||||
if (property_exists($meta, 'attachment'))
|
if (property_exists($meta, 'attachment'))
|
||||||
{
|
{
|
||||||
self::$_cache[$pasteid]->attachment = $meta->attachment;
|
self::$_cache[$pasteid]->attachment = $meta->attachment;
|
||||||
|
@ -275,6 +242,15 @@ class zerobin_db extends zerobin_abstract
|
||||||
unset($meta->attachmentname);
|
unset($meta->attachmentname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// support current attachments
|
||||||
|
elseif (array_key_exists('attachment', $paste) && strlen($paste['attachment']))
|
||||||
|
{
|
||||||
|
self::$_cache[$pasteid]->attachment = $paste['attachment'];
|
||||||
|
if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname']))
|
||||||
|
{
|
||||||
|
self::$_cache[$pasteid]->attachmentname = $paste['attachmentname'];
|
||||||
|
}
|
||||||
|
}
|
||||||
self::$_cache[$pasteid]->meta = $meta;
|
self::$_cache[$pasteid]->meta = $meta;
|
||||||
self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
|
self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
|
||||||
$expire_date = (int) $paste['expiredate'];
|
$expire_date = (int) $paste['expiredate'];
|
||||||
|
@ -450,4 +426,227 @@ class zerobin_db extends zerobin_abstract
|
||||||
$statement->closeCursor();
|
$statement->closeCursor();
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get table list query, depending on the database type
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $type
|
||||||
|
* @throws Exception
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function _getTableQuery($type)
|
||||||
|
{
|
||||||
|
switch($type)
|
||||||
|
{
|
||||||
|
case 'ibm':
|
||||||
|
$sql = 'SELECT tabname FROM SYSCAT.TABLES ';
|
||||||
|
break;
|
||||||
|
case 'informix':
|
||||||
|
$sql = 'SELECT tabname FROM systables ';
|
||||||
|
break;
|
||||||
|
case 'mssql':
|
||||||
|
$sql = "SELECT name FROM sysobjects "
|
||||||
|
. "WHERE type = 'U' ORDER BY name";
|
||||||
|
break;
|
||||||
|
case 'mysql':
|
||||||
|
$sql = 'SHOW TABLES';
|
||||||
|
break;
|
||||||
|
case 'oci':
|
||||||
|
$sql = 'SELECT table_name FROM all_tables';
|
||||||
|
break;
|
||||||
|
case 'pgsql':
|
||||||
|
$sql = "SELECT c.relname AS table_name "
|
||||||
|
. "FROM pg_class c, pg_user u "
|
||||||
|
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
|
||||||
|
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||||
|
. "AND c.relname !~ '^(pg_|sql_)' "
|
||||||
|
. "UNION "
|
||||||
|
. "SELECT c.relname AS table_name "
|
||||||
|
. "FROM pg_class c "
|
||||||
|
. "WHERE c.relkind = 'r' "
|
||||||
|
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||||
|
. "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
|
||||||
|
. "AND c.relname !~ '^pg_'";
|
||||||
|
break;
|
||||||
|
case 'sqlite':
|
||||||
|
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||||
|
. "UNION ALL SELECT name FROM sqlite_temp_master "
|
||||||
|
. "WHERE type='table' ORDER BY name";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception(
|
||||||
|
"PDO type $type is currently not supported.", 5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a value by key from the config table
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $key
|
||||||
|
* @throws PDOException
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function _getConfig($key)
|
||||||
|
{
|
||||||
|
$row = self::_select(
|
||||||
|
'SELECT value FROM ' . self::$_prefix . 'config WHERE id = ?',
|
||||||
|
array($key), true
|
||||||
|
);
|
||||||
|
return $row['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the primary key clauses, depending on the database driver
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $key
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function _getPrimaryKeyClauses($key = 'dataid')
|
||||||
|
{
|
||||||
|
$main_key = $after_key = '';
|
||||||
|
if (self::$_type === 'mysql')
|
||||||
|
{
|
||||||
|
$after_key = ", PRIMARY KEY ($key)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$main_key = ' PRIMARY KEY';
|
||||||
|
}
|
||||||
|
return array($main_key, $after_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the paste table
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function _createPasteTable()
|
||||||
|
{
|
||||||
|
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE TABLE ' . self::$_prefix . 'paste ( ' .
|
||||||
|
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||||
|
'data BLOB, ' .
|
||||||
|
'postdate INT, ' .
|
||||||
|
'expiredate INT, ' .
|
||||||
|
'opendiscussion INT, ' .
|
||||||
|
'burnafterreading INT, ' .
|
||||||
|
'meta TEXT, ' .
|
||||||
|
'attachment MEDIUMBLOB, ' .
|
||||||
|
"attachmentname BLOB$after_key );"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the paste table
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function _createCommentTable()
|
||||||
|
{
|
||||||
|
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE TABLE ' . self::$_prefix . 'comment ( ' .
|
||||||
|
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||||
|
'pasteid CHAR(16), ' .
|
||||||
|
'parentid CHAR(16), ' .
|
||||||
|
'data BLOB, ' .
|
||||||
|
'nickname BLOB, ' .
|
||||||
|
'vizhash BLOB, ' .
|
||||||
|
"postdate INT$after_key );"
|
||||||
|
);
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE INDEX parent ON ' . self::$_prefix . 'comment(pasteid);'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the paste table
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function _createConfigTable()
|
||||||
|
{
|
||||||
|
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE TABLE ' . self::$_prefix . 'config ( ' .
|
||||||
|
"id CHAR(16) NOT NULL$main_key, value TEXT$after_key );"
|
||||||
|
);
|
||||||
|
self::_exec(
|
||||||
|
'INSERT INTO ' . self::$_prefix . 'config VALUES(?,?)',
|
||||||
|
array('VERSION', zerobin::VERSION)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* upgrade the database schema from an old version
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @param string $oldversion
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function _upgradeDatabase($oldversion)
|
||||||
|
{
|
||||||
|
switch ($oldversion)
|
||||||
|
{
|
||||||
|
case '0.21':
|
||||||
|
// create the meta column if necessary (pre 0.21 change)
|
||||||
|
try {
|
||||||
|
self::$_db->exec('SELECT meta FROM ' . self::$_prefix . 'paste LIMIT 1;', array());
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
self::$_db->exec('ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN meta TEXT;');
|
||||||
|
}
|
||||||
|
// SQLite only allows one ALTER statement at a time...
|
||||||
|
self::$_db->exec(
|
||||||
|
'ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN attachment MEDIUMBLOB;'
|
||||||
|
);
|
||||||
|
self::$_db->exec(
|
||||||
|
'ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN attachmentname BLOB;'
|
||||||
|
);
|
||||||
|
// SQLite doesn't support MODIFY, but it allows TEXT of similar
|
||||||
|
// size as BLOB, so there is no need to change it there
|
||||||
|
if (self::$_type !== 'sqlite')
|
||||||
|
{
|
||||||
|
self::$_db->exec(
|
||||||
|
'ALTER TABLE ' . self::$_prefix . 'paste ' .
|
||||||
|
'ADD PRIMARY KEY (dataid),' .
|
||||||
|
'MODIFY COLUMN data BLOB;'
|
||||||
|
);
|
||||||
|
self::$_db->exec(
|
||||||
|
'ALTER TABLE ' . self::$_prefix . 'comment ' .
|
||||||
|
'ADD PRIMARY KEY (dataid),' .
|
||||||
|
'MODIFY COLUMN data BLOB, ' .
|
||||||
|
'MODIFY COLUMN nickname BLOB, ' .
|
||||||
|
'MODIFY COLUMN vizhash BLOB;'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE UNIQUE INDEX primary ON ' . self::$_prefix . 'paste(dataid);'
|
||||||
|
);
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE UNIQUE INDEX primary ON ' . self::$_prefix . 'comment(dataid);'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self::$_db->exec(
|
||||||
|
'CREATE INDEX parent ON ' . self::$_prefix . 'comment(pasteid);'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||||
$this->reset();
|
$this->reset();
|
||||||
$options = parse_ini_file(CONF, true);
|
$options = parse_ini_file(CONF, true);
|
||||||
$options['traffic']['header'] = 'X_FORWARDED_FOR';
|
$options['traffic']['header'] = 'X_FORWARDED_FOR';
|
||||||
|
$options['traffic']['limit'] = 100;
|
||||||
helper::confBackup();
|
helper::confBackup();
|
||||||
helper::createIniFile(CONF, $options);
|
helper::createIniFile(CONF, $options);
|
||||||
$_POST = helper::getPaste();
|
$_POST = helper::getPaste();
|
||||||
|
|
Loading…
Reference in a new issue