From: garvinhicking Date: Fri, 12 Oct 2007 09:12:48 +0000 (+0000) Subject: Add SQLRelay DB layer, by Dante Mason X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=49687af87cefc1c3a0585a9d5f4bbbeebdcae9ca;p=s9y.git Add SQLRelay DB layer, by Dante Mason --- diff --git a/docs/NEWS b/docs/NEWS index 51ebd0f..75f7148 100644 --- a/docs/NEWS +++ b/docs/NEWS @@ -3,6 +3,9 @@ Version 1.3 () ------------------------------------------------------------------------ + * Add experimental DB layer for "SQLRelay" database proxy extension, + by Dante Mason + * Highlighting searched words in entries now uses str_replace instead of slower and possibly insecure preg_replace(). Thanks to Dietrich Raisin! diff --git a/include/db/sqlrelay.inc.php b/include/db/sqlrelay.inc.php new file mode 100644 index 0000000..dd80e7e --- /dev/null +++ b/include/db/sqlrelay.inc.php @@ -0,0 +1,559 @@ + sqlr_ASSOC, + 'num' => sqlr_NUM, + 'both' => sqlr_BOTH, + 'true' => true, + 'false' => false + ); + static $benchmark = false; + + // highlight_string(var_export($sql, 1)); + + if (!is_resource($serendipity['dbConn'])) { + return false; + } + + $cur = sqlrcur_alloc($serendipity['dbConn']); + $serendipity['dbCursor'] = $cur; + + if ($benchmark) { + $start = microtime_float(); + } + + if ($expectError) { + $c = sqlrcur_sendQuery($cur, $sql); + } else { + $c = sqlrcur_sendQuery($cur, $sql); + } + + if ($benchmark) { + $end = microtime_float(); + + $cur = sqlrcur_alloc($serendipity['dbConn']); + $sql_b="INSERT INTO BLOGLOG (request, timestamp, sql, exec_time, ip) VALUES ('" . serendipity_db_escape_string($_SERVER['REQUEST_URI']) . "', NOW(), '" . serendipity_db_escape_string($sql) . "', '" . (number_format($end-$start, 10)) . "', '" . serendipity_db_escape_string($_SERVER['REMOTE_ADDR']) . "')"; + $c = sqlrcur_sendQuery($cur, $sql_b); + + $psql = $sql; + $psql = preg_replace('@[0-9]{10}@', 'TIMESTAMP', $psql); + $sql_U="UPDATE BLOGLOG_TOTAL SET counter = counter + 1 WHERE sql = '" . serendipity_db_escape_string($psql) . "'"; + $c = sqlrcur_sendQuery($cur, $sql_U); + if (sqlrcur_affectedRows() < 1) { + $sql_i="INSERT INTO BLOGLOG_TOTAL (sql, counter) VALUES ('" . serendipity_db_escape_string($psql) . "', 1)"; + $c = sqlrcur_sendQuery($cur,$sql_i); + } + } + + if (!$expectError && sqlrcur_errorMessage($cur) != '') { + $msg = '
' . $sql . '
/ ' . sqlrcur_errorMessage($cur); + return $msg; + } + + if (!$c) { + if (!$expectError && !$serendipity['production']) { + print '
' . $sql . '
/ ' . sqlrcur_errorMessage($cur); + if (function_exists('debug_backtrace') && $reportErr == true) { + highlight_string(var_export(debug_backtrace(), 1)); + } + } + + return $type_map['false']; + } + + if ($c === true) { + return $type_map['true']; + } + + $result_type = $type_map[$result_type]; + + switch(sqlrcur_rowCount($cur)) { + case 0: + if ($single) { + return $type_map['false']; + } + return $type_map['true']; + + case 1: + if ($single) { + $row = generate_resultset($cur, $result_type); + if ($result_type != 'sqlr_ASSOC') { + $row=$row[0]; + } + return $row; + } + + default: + if ($single) { + return generate_resultset($cur, $result_type); + } + + $row=generate_resultset($cur); + $rows=array(); + + for($idx=0, $idxc = count($row); $idx < $idxc ; $idx++) { + if (!empty($assocKey)) { + // You can fetch a key-associated array via the two function parameters assocKey and assocVal + if (empty($assocVal)) { + $rows[$assocKey] = $row[$idx][$assocKey]; + } else { + $rows[$row[$idx][$assocKey]] = $row[$idx][$assocVal]; + } + } else { + $rows = $row; + } + } + + return $rows; + + } +} + +/** + * Returns the latest INSERT_ID of an SQL INSERT INTO command, for auto-increment columns + * + * @access public + * @return int Value of the auto-increment column + * + * If you insert multiple rows using a single INSERT statement, + * LAST_INSERT_ID() returns the value generated for the first inserted row only. + */ +function serendipity_db_insert_id($table = '', $id = '') { + global $serendipity; + + $type_of_database = sqlrcon_identify($serendipity['dbConn']); + switch($type_of_database) { + case 'mysql': + $sqlstr='SELECT LAST_INSERT_ID()'; + break; + + case 'postgresql': + $sqlstr = "SELECT currval('{$serendipity['dbPrefix']}{$table}_{$id}_seq'::text) AS {$id}"; + break; + + case 'sqlite': + $sqlstr = 'SELECT last_insert_rowid()'; + break; + + default: + $sqlstr = 'SELECT LAST_INSERT_ID()'; + } + + $row = serendipity_db_query($sqlstr, true); + + return $row[0]; +} + +/** + * 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; + + /* int sqlrcur_affectedRows(int sqlrcurref) + * Returns the number of rows that were updated, inserted or deleted by the query. + * Not all databases support this call. + * Don't use it for applications which are designed to be portable across databases. + * -1 is returned by databases which don't support this option. + */ + + return sqlrcur_affectedRows($serendipity['dbCursor']); +} + +/** + * Returns the number of updated rows in a SQL query + * + * @access public + * @return int Number of updated rows + */ +function serendipity_db_updated_rows() { + /* + + preg_match( + "/^[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)/", + mysql_info(), + $arr); + // mysql_affected_rows returns 0 if rows were matched but not changed. + // mysql_info returns rows matched AND rows changed + return $arr[2]; + */ + return serendipity_db_affected_rows(); +} + +/** + * Returns the number of matched rows in a SQL query + * + * @access public + * @return int Number of matched rows + */ +function serendipity_db_matched_rows() { + /* + + preg_match( + "/^[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)/", + mysql_info(), + $arr); + // mysql_affected_rows returns 0 if rows were matched but not changed. + // mysql_info returns rows matched AND rows changed + return $arr[1]; + */ + return serendipity_db_affected_rows(); +} + +/** + * 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($str) { + global $serendipity; + static $search = array("\x00", '%', "'", '\"'); + static $replace = array('%00', '%25', "''", '\\\"'); + + $type_of_database = sqlrcon_identify($serendipity['dbConn']); + switch($type_of_database) { + case 'mysql': + if (function_exists('mysql_real_escape_string')) { + $rtnstr = mysql_real_escape_string($str, $serendipity['dbConn']); + } else { + $rtnstr = addslashes($str); + } + break; + + case 'postgresql': + if (function_exists('pg_escape_string')) { + $rtnstr = pg_escape_string($str); + } else { + $rtnstr = addslashes($str); + } + break; + + case 'sqlite': + $rtnstr = str_replace($search, $replace, $str); + break; + + default: + $rtnstr = addslashes($str); + } + + return $rtnstr; +} + +/** + * 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 $start . ', ' . $offset; +} + +/** + * 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) { + global $serendipity; + + $type_of_database = sqlrcon_identify($serendipity['dbConn']); + switch($type_of_database) { + case "mysql": + return ' LIMIT ' . $limitstring; + case "postgresql": + $limit; + $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; + default: + return ' LIMIT ' . $limitstring; + } +} + +/** + * Connect to the configured Database + * + * @access public + * @return ressource connection handle + */ +function serendipity_db_connect() { + global $serendipity; + + if (isset($serendipity['dbConn'])) { + return $serendipity['dbConn']; + } + + if (isset($serendipity['dbPersistent']) && $serendipity['dbPersistent']) { + $function = 'sqlrcon_alloc'; + } else { + $function = 'sqlrcon_alloc'; + } + + #$serendipity['dbHost']="localhost:9000" + $dbHostPort=explode(":", $serendipity['dbHost']); + $serendipity['dbConn'] = $function($dbHostPort[0], $dbHostPort[1], "", $serendipity['dbUser'], $serendipity['dbPass'], 0, 1); + sqlrcon_debugOff($serendipity['dbConn']); + + if( sqlrcon_identify($serendipity['dbConn']) == "mysql") { + serendipity_db_reconnect(); + } + + return $serendipity['dbConn']; +} + +function serendipity_db_reconnect() { + global $serendipity; + + if (isset($serendipity['dbCharset'])) { + serendipity_db_query("SET NAMES " . $serendipity['dbCharset']); + define('SQL_CHARSET_INIT', true); + } elseif (defined('SQL_CHARSET') && $serendipity['dbNames'] && !defined('SQL_CHARSET_INIT')) { + serendipity_db_query("SET NAMES " . SQL_CHARSET); + } +} + +/** + * 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) { + global $serendipity; + + $type_of_database = sqlrcon_identify($serendipity['dbConn']); + switch($type_of_database) { + case "mysql": + static $search = array('{AUTOINCREMENT}', '{PRIMARY}', + '{UNSIGNED}', '{FULLTEXT}', '{FULLTEXT_MYSQL}', '{BOOLEAN}'); + static $replace = array('int(11) not null auto_increment', 'primary key', + 'unsigned' , 'FULLTEXT', 'FULLTEXT', 'enum (\'true\', \'false\') NOT NULL default \'true\''); + static $is_utf8 = null; + + if ($is_utf8 === null) { + $search[] = '{UTF_8}'; + if ((isset($_POST['charset']) && $_POST['charset'] == 'UTF-8/') || + $serendipity['charset'] == 'UTF-8/' || + $serendipity['POST']['charset'] == 'UTF-8/' || + LANG_CHARSET == 'UTF-8' ) { + $replace[] = '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'; + } else { + $replace[] = ''; + } + } + break; + + case "postgresql": + static $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', + '{FULLTEXT}', '{FULLTEXT_MYSQL}', '{BOOLEAN}', 'int(1)', 'int(10)', 'int(11)', 'int(4)', '{UTF_8}'); + static $replace = array('SERIAL', 'primary key', '', + '', '', 'BOOLEAN NOT NULL', 'int2', 'int4', 'int4', 'int4', ''); + break; + + case "sqlite": + static $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', '{FULLTEXT_MYSQL}', '{BOOLEAN}', '{UTF_8}'); + static $replace = array('INTEGER', 'PRIMARY KEY', '', '', '', 'BOOLEAN NOT NULL', ''); + break; + + default: + 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; + $c; + + if (!function_exists('sqlrcon_alloc')) { + $errs[] = 'No sql_relay extension found. Please check your webserver installation or contact your systems administrator regarding this problem.'; + return false; + } + + if (!($c=@sqlrcon_alloc($hash['dbHost'], '', '', $hash['dbUser'], $hash['dbPass'], 0, 5))) { + $errs[] = 'Could not connect to database; check your settings.'; + return false; + } + + + $serendipity['dbConn'] = $c; + + /* which db should be used is defined in sqlrelay.conf */ + + 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) { + global $serendipity; + + $type_of_database = sqlrcon_identify($serendipity['dbConn']); + switch($type_of_database) { + case 'mysql': + return 'concat(' . $string . ')'; + + case 'postgresql': + return '(' . str_replace(', ', '||', $string) . ')'; + + case 'sqlite': + return 'concat(' . $string . ')'; + + default: + return 'concat(' . $string . ')'; + } +} + +/* vim: set sts=4 ts=4 expandtab : */ diff --git a/include/functions_config.inc.php b/include/functions_config.inc.php index a133583..b6a1a57 100644 --- a/include/functions_config.inc.php +++ b/include/functions_config.inc.php @@ -820,6 +820,9 @@ function serendipity_probeInstallation($item) { if (extension_loaded('SQLITE3')) { $res['sqlite3'] = 'SQLite3'; } + if (function_exists('sqlrcon_alloc')) { + $res['sqlrelay'] = 'SQLRelay'; + } break; case 'rewrite' :