From 75efa8074f8ce73ffa239f3d0ad218b41bb809e7 Mon Sep 17 00:00:00 2001 From: garvinhicking Date: Tue, 6 Feb 2007 08:27:29 +0000 Subject: [PATCH] Added PDO::Postgres DB-layer support by Theo Schlossnagle --- docs/NEWS | 2 + include/db/db.inc.php | 29 ++- include/db/pdo-postgres.inc.php | 322 ++++++++++++++++++++++++++++ include/functions.inc.php | 3 +- include/functions_comments.inc.php | 6 +- include/functions_config.inc.php | 4 + include/functions_entries.inc.php | 9 +- include/functions_images.inc.php | 6 +- include/functions_installer.inc.php | 4 + include/functions_upgrader.inc.php | 1 + 10 files changed, 373 insertions(+), 13 deletions(-) create mode 100644 include/db/pdo-postgres.inc.php diff --git a/docs/NEWS b/docs/NEWS index e0b2a26..270ee5d 100644 --- a/docs/NEWS +++ b/docs/NEWS @@ -3,6 +3,8 @@ Version 1.2 () ------------------------------------------------------------------------ + * Added PDO:PostgreSQL support (Theo Schlossnagle) + * Dutch translation update with many fixes by Sam Geeraerts * Improve Livesearch-Plugin to abort search on outside-of-box diff --git a/include/db/db.inc.php b/include/db/db.inc.php index c891c40..afa8791 100644 --- a/include/db/db.inc.php +++ b/include/db/db.inc.php @@ -2,7 +2,12 @@ # Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team) # All rights reserved. See LICENSE file for licensing details -if (@include_once(S9Y_INCLUDE_PATH . "include/db/{$serendipity['dbType']}.inc.php")) { +if (defined('S9Y_FRAMEWORK_DB')) { + return; +} +@define('S9Y_FRAMEWORK_DB', true); + +if (@include(S9Y_INCLUDE_PATH . "include/db/{$serendipity['dbType']}.inc.php")) { @define('S9Y_DB_INCLUDED', TRUE); } @@ -16,9 +21,10 @@ if (@include_once(S9Y_INCLUDE_PATH . "include/db/{$serendipity['dbType']}.inc.ph * @param string Name of the DB table * @param array Input array that controls the "WHERE" condition part. Pass it an associative array like array('key1' => 'value1', 'key2' => 'value2') to get a statement like "WHERE key1 = value1 AND key2 = value2". Escaping is done automatically in this function. * @param array Input array that controls the "SET" condition part. Pass it an associative array like array('key1' => 'value1', 'key2' => 'value2') to get a statement like "SET key1 = value1, key2 = value2". Escaping is done automatically in this function. + * @param string What do do with the SQL query (execute, display) * @return array Returns the result of the SQL query */ -function serendipity_db_update($table, $keys, $values) +function serendipity_db_update($table, $keys, $values, $action = 'execute') { global $serendipity; @@ -41,7 +47,12 @@ function serendipity_db_update($table, $keys, $values) $where = " WHERE $where"; } - return serendipity_db_query("UPDATE {$serendipity['dbPrefix']}$table SET $set $where"); + $q = "UPDATE {$serendipity['dbPrefix']}$table SET $set $where"; + if ($action == 'execute') { + return serendipity_db_query($q); + } else { + return $q; + } } /** @@ -52,9 +63,10 @@ function serendipity_db_update($table, $keys, $values) * @access public * @param string Name of the SQL table * @param array Associative array of keys/values to insert into the table. Escaping is done automatically. + * @param string What do do with the SQL query (execute, display) * @return array Returns the result of the SQL query */ -function serendipity_db_insert($table, $values) +function serendipity_db_insert($table, $values, $action = 'execute') { global $serendipity; @@ -67,7 +79,13 @@ function serendipity_db_insert($table, $values) $vals .= '\'' . serendipity_db_escape_string($v) . '\''; } - return serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}$table ($names) values ($vals)"); + $q = "INSERT INTO {$serendipity['dbPrefix']}$table ($names) values ($vals)"; + + if ($action == 'execute') { + return serendipity_db_query($q); + } else { + return $q; + } } /** @@ -109,6 +127,7 @@ function serendipity_db_get_interval($val, $ival = 900) { $ts = time(); break; + case 'pdo-postgres': case 'postgres': $interval = "interval '$ival'"; $ts = 'NOW()'; diff --git a/include/db/pdo-postgres.inc.php b/include/db/pdo-postgres.inc.php new file mode 100644 index 0000000..97fbce8 --- /dev/null +++ b/include/db/pdo-postgres.inc.php @@ -0,0 +1,322 @@ +beginTransaction(); +} + +/** + * Tells the DB Layer to end a DB transaction. + * + * @access public + * @param boolean If true, perform the query. If false, rollback. + */ +function serendipity_db_end_transaction($commit){ + if ($commit){ + $serendipity['dbConn']->commit(); + }else{ + $serendipity['dbConn']->rollback(); + } +} + +/** + * Assemble and return SQL condition for a "IN (...)" clause + * + * @access public + * @param string table column name + * @param array referenced array of values to search for in the "IN (...)" clause + * @param string condition of how to associate the different input values of the $search_ids parameter + * @return string resulting SQL string + */ +function serendipity_db_in_sql($col, &$search_ids, $type = ' OR ') { + return $col . " IN (" . implode(', ', $search_ids) . ")"; +} + +/** + * Connect to the configured Database + * + * @access public + * @return ressource connection handle + */ +function serendipity_db_connect() { + global $serendipity; + + $serendipity['dbConn'] = new PDO( + sprintf( + 'pgsql:%sdbname=%s', + strlen($serendipity['dbHost']) ? ('host=' . $serendipity['dbHost'] . ';') : '', + $serendipity['dbName'] + ), + $serendipity['dbUser'], + $serendipity['dbPass'] + ); + + return $serendipity['dbConn']; +} + +function serendipity_db_reconnect() { +} + +/** + * Returns a escaped string, so that it can be safely included in a SQL string encapsulated within quotes, without allowing SQL injection. + * + * @access public + * @param string input string + * @return string output string + */ +function serendipity_db_escape_string($string) { + global $serendipity; + return substr($serendipity['dbConn']->quote($string), 1, -1); +} + +/** + * Returns the option to a LIMIT SQL statement, because it varies accross DB systems + * + * @access public + * @param int Number of the first row to return data from + * @param int Number of rows to return + * @return string SQL string to pass to a LIMIT statement + */ +function serendipity_db_limit($start, $offset) { + return $offset . ', ' . $start; +} + +/** + * Return a LIMIT SQL option to the DB Layer as a full LIMIT statement + * + * @access public + * @param SQL string of a LIMIT option + * @return SQL string containing a full LIMIT statement + */ +function serendipity_db_limit_sql($limitstring) { + $limit_split = split(',', $limitstring); + if (count($limit_split) > 1) { + $limit = ' LIMIT ' . $limit_split[0] . ' OFFSET ' . $limit_split[1]; + } else { + $limit = ' LIMIT ' . $limit_split[0]; + } + return $limit; +} + +/** + * Returns the number of affected rows of a SQL query + * + * @access public + * @return int Number of affected rows + */ +function serendipity_db_affected_rows() { + global $serendipity; + return $serendipity['dbSth']->rowCount(); +} + +/** + * Returns the number of updated rows in a SQL query + * + * @access public + * @return int Number of updated rows + */ +function serendipity_db_updated_rows() { + global $serendipity; + // it is unknown whether pg_affected_rows returns number of rows + // UPDATED or MATCHED on an UPDATE statement. + return $serendipity['dbSth']->rowCount(); +} + +/** + * Returns the number of matched rows in a SQL query + * + * @access public + * @return int Number of matched rows + */ +function serendipity_db_matched_rows() { + global $serendipity; + // it is unknown whether pg_affected_rows returns number of rows + // UPDATED or MATCHED on an UPDATE statement. + return $serendipity['dbSth']->rowCount(); +} + +/** + * Returns the latest INSERT_ID of an SQL INSERT INTO command, for auto-increment columns + * + * @access public + * @param string Name of the table to get a INSERT ID for + * @param string Name of the column to get a INSERT ID for + * @return int Value of the auto-increment column + */ +function serendipity_db_insert_id($table = '', $id = '') { + global $serendipity; + if (empty($table) || empty($id)) { + // BC - will/should never be called with empty parameters! + return $serendipity['dbConn']->lastInsertId(); + } else { + $query = "SELECT currval('{$serendipity['dbPrefix']}{$table}_{$id}_seq'::text) AS {$id}"; + $res = $serendipity['dbConn']->prepare($query); + $res->execute(); + foreach($res->fetchAll(PDO::FETCH_ASSOC) as $row) { + return $row[$id]; + } + return $serendipity['dbConn']->lastInsertId(); + } +} + +/** + * Perform a DB Layer SQL query. + * + * This function returns values dependin on the input parameters and the result of the query. + * It can return: + * false if there was an error, + * true if the query succeeded but did not generate any rows + * array of field values if it returned a single row and $single is true + * array of array of field values if it returned row(s) [stacked array] + * + * @access public + * @param string SQL query to execute + * @param boolean Toggle whether the expected result is a single row (TRUE) or multiple rows (FALSE). This affects whether the returned array is 1 or 2 dimensional! + * @param string Result type of the array indexing. Can be one of "assoc" (associative), "num" (numerical), "both" (numerical and associative, default) + * @param boolean If true, errors will be reported. If false, errors will be ignored. + * @param string A possible array key name, so that you can control the multi-dimensional mapping of an array by the key column + * @param string A possible array field name, so that you can control the multi-dimensional mapping of an array by the key column and the field value. + * @param boolean If true, the executed SQL error is known to fail, and should be disregarded (errors can be ignroed on DUPLICATE INDEX queries and the likes) + * @return mixed Returns the result of the SQL query, depending on the input parameters + */ +function &serendipity_db_query($sql, $single = false, $result_type = "both", $reportErr = false, $assocKey = false, $assocVal = false, $expectError = false) { + global $serendipity; + static $type_map = array( + 'assoc' => PDO::FETCH_ASSOC, + 'num' => PDO::FETCH_NUM, + 'both' => PDO::FETCH_BOTH, + 'true' => true, + 'false' => false + ); + + if (!$expectError && ($reportErr || !$serendipity['production'])) { + $serendipity['dbSth'] = $serendipity['dbConn']->prepare($sql); + } else { + $serendipity['dbSth'] = $serendipity['dbConn']->prepare($sql); + } + + if (!$serendipity['dbSth']) { + if (!$expectError && !$serendipity['production']) { + print "Error in $sql
\n"; + print $serendipity['dbConn']->errorInfo() . "
\n"; + if (function_exists('debug_backtrace')) { + highlight_string(var_export(debug_backtrace(), 1)); + } + print "
$sql\n"; + } + return $type_map['false']; + } + + $serendipity['dbSth']->execute(); + + if ($serendipity['dbSth'] === true) { + return $type_map['true']; + } + + $result_type = $type_map[$result_type]; + + $n = 0; + + $rows = array(); + foreach($serendipity['dbSth']->fetchAll($result_type) as $row) { + if (!empty($assocKey)) { + // You can fetch a key-associated array via the two function parameters assocKey and assocVal + if (empty($assocVal)) { + $rows[$row[$assocKey]] = $row; + } else { + $rows[$row[$assocKey]] = $row[$assocVal]; + } + } else { + $rows[] = $row; + } + } + if(count($rows) == 0) { + if ($single) { + return $type_map['false']; + } + return $type_map['true']; + } + if(count($rows) == 1 && $single) { + return $rows[0]; + } + return $rows; +} + +/** + * Prepares a Serendipty query input to fully valid SQL. Replaces certain "template" variables. + * + * @access public + * @param string SQL query with template variables to convert + * @return ressource SQL ressource handle of the executed query + */ +function serendipity_db_schema_import($query) { + static $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', + '{FULLTEXT}', '{BOOLEAN}', 'int(1)', 'int(10)', 'int(11)', 'int(4)', '{UTF_8}'); + static $replace = array('SERIAL', 'primary key', '', '', 'BOOLEAN NOT NULL', 'int2', + 'int4', 'int4', 'int4', ''); + + if (stristr($query, '{FULLTEXT_MYSQL}')) { + return true; + } + + $query = trim(str_replace($search, $replace, $query)); + if ($query{0} == '@') { + // Errors are expected to happen (like duplicate index creation) + return serendipity_db_query(substr($query, 1), false, 'both', false, false, false, true); + } else { + return serendipity_db_query($query); + } +} + +/** + * Try to connect to the configured Database (during installation) + * + * @access public + * @param array input configuration array, holding the connection info + * @param array referenced array which holds the errors that might be encountered + * @return boolean return true on success, false on error + */ +function serendipity_db_probe($hash, &$errs) { + global $serendipity; + + if(!in_array('pgsql', PDO::getAvailableDrivers())) { + $errs[] = 'PDO_PGSQL driver not avialable'; + return false; + } + + $serendipity['dbConn'] = new PDO( + sprintf( + 'pgsql:%sdbname=%s', + strlen($hash['dbHost']) ? ('host=' . $hash['dbHost'] . ';') : '', + $hash['dbName'] + ), + $hash['dbUser'], + $hash['dbPass'] + ); + + if (!$serendipity['dbConn']) { + $errs[] = 'Could not connect to database; check your settings.'; + return false; + } + + return true; +} + +/** + * Returns the SQL code used for concatenating strings + * + * @access public + * @param string Input string/column to concatenate + * @return string SQL parameter + */ +function serendipity_db_concat($string) { + return '(' . str_replace(', ', '||', $string) . ')'; +} + +/* vim: set sts=4 ts=4 expandtab : */ diff --git a/include/functions.inc.php b/include/functions.inc.php index 24aeba5..99d7252 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -367,7 +367,8 @@ function serendipity_fetchUsers($user = '', $group = null, $is_count = false) { } if ($is_count || $group != null) { - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { // Why does PostgreSQL keep doing this to us? :-) $query_group = 'GROUP BY a.authorid, a.realname, a.username, a.password, a.mail_comments, a.mail_trackbacks, a.email, a.userlevel, a.right_publish'; $query_distinct = 'DISTINCT'; diff --git a/include/functions_comments.inc.php b/include/functions_comments.inc.php index 2e64a53..166a880 100644 --- a/include/functions_comments.inc.php +++ b/include/functions_comments.inc.php @@ -149,7 +149,8 @@ function serendipity_fetchComments($id, $limit = null, $order = '', $showAll = f $and .= $where; - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $group = ''; $distinct = 'DISTINCT'; } else { @@ -616,7 +617,8 @@ function serendipity_mailSubscribers($entry_id, $poster, $posterMail, $title, $f $pgsql_insert = ''; $mysql_insert = ''; - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $pgsql_insert = 'DISTINCT ON (email)'; } else { $mysql_insert = 'GROUP BY email'; diff --git a/include/functions_config.inc.php b/include/functions_config.inc.php index 5e69e48..456c59e 100644 --- a/include/functions_config.inc.php +++ b/include/functions_config.inc.php @@ -739,6 +739,10 @@ function serendipity_probeInstallation($item) { if (extension_loaded('mysql')) { $res['mysql'] = 'MySQL'; } + if (extension_loaded('PDO') && + in_array('pgsql', PDO::getAvailableDrivers())) { + $res['pdo-postgres'] = 'PDO::PostgreSQL'; + } if (extension_loaded('pgsql')) { $res['postgres'] = 'PostgreSQL'; } diff --git a/include/functions_entries.inc.php b/include/functions_entries.inc.php index 2acf7b3..3747295 100644 --- a/include/functions_entries.inc.php +++ b/include/functions_entries.inc.php @@ -327,7 +327,8 @@ function &serendipity_fetchEntries($range = null, $full = true, $limit = '', $fe serendipity_plugin_api::hook_event('frontend_fetchentries', $cond, array('noCache' => $noCache, 'noSticky' => $noSticky, 'source' => 'entries')); - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $group = ''; $distinct = 'DISTINCT'; } else { @@ -613,7 +614,8 @@ function &serendipity_fetchCategories($authorid = null, $name = null, $order = n $where .= " c.category_name = '" . serendipity_db_escape_string($name) . "'"; } - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $group = ''; $distinct = 'DISTINCT'; } else { @@ -702,7 +704,8 @@ function &serendipity_searchEntries($term, $limit = '') { $term = serendipity_db_escape_string($term); $cond = array(); - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $group = ''; $distinct = 'DISTINCT'; $cond['find_part'] = "(title ILIKE '%$term%' OR body ILIKE '%$term%' OR extended ILIKE '%$term%')"; diff --git a/include/functions_images.inc.php b/include/functions_images.inc.php index f29d50c..db034ca 100644 --- a/include/functions_images.inc.php +++ b/include/functions_images.inc.php @@ -185,7 +185,8 @@ function serendipity_fetchImagesFromDatabase($start=0, $limit=0, &$total, $order ON (hp.mediaid = i.id AND hp.property_group = 'base_hidden')\n"; } - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $cond['group'] = ''; $cond['distinct'] = 'DISTINCT'; } else { @@ -252,7 +253,8 @@ function serendipity_fetchImageFromDatabase($id, $mode = 'read') { $assocVal = false; } - if ($serendipity['dbType'] == 'postgres') { + if ($serendipity['dbType'] == 'postgres' || + $serendipity['dbType'] == 'pdo-postgres') { $cond['group'] = ''; $cond['distinct'] = 'DISTINCT'; } else { diff --git a/include/functions_installer.inc.php b/include/functions_installer.inc.php index ecdbb20..c90d1d6 100644 --- a/include/functions_installer.inc.php +++ b/include/functions_installer.inc.php @@ -182,6 +182,10 @@ function serendipity_query_default($optname, $default, $usertemplate = false, $t if (extension_loaded('mysqli')) { $type = 'mysqli'; } + if (extension_loaded('PDO') && + in_array('pgsql', PDO::getAvailableDrivers())) { + $type = 'pdo-postgres'; + } if (extension_loaded('pgsql')) { $type = 'postgres'; } diff --git a/include/functions_upgrader.inc.php b/include/functions_upgrader.inc.php index 804f5a5..34def66 100644 --- a/include/functions_upgrader.inc.php +++ b/include/functions_upgrader.inc.php @@ -46,6 +46,7 @@ $obsolete_files = array( 'serendipity_db_mysql.inc.php', 'serendipity_db_mysqli.inc.php', 'serendipity_db_postgres.inc.php', + 'serendipity_db_pdo-postgres.inc.php', 'serendipity_db_sqlite.inc.php', 'htaccess.cgi.errordocs.tpl', 'htaccess.cgi.normal.tpl', -- 2.39.5