diff --git a/wp-content/db.php b/wp-content/db.php index 1e0f8d3e98..6f553752a8 100644 --- a/wp-content/db.php +++ b/wp-content/db.php @@ -1,28 +1,9 @@ '', '?>' => '', ); +// Ensure class uses the replaced mysql_ functions rather than mysqli_ +define( 'WP_USE_EXT_MYSQL', true); eval( str_replace( array_keys($replaces), array_values($replaces), file_get_contents(ABSPATH.'/wp-includes/wp-db.php'))); // Create wpdb object if not already done -if (! isset($wpdb)) +if (! isset($wpdb) && defined('DB_USER')) $wpdb = new wpdb2( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST ); diff --git a/wp-content/pg4wp/db.php b/wp-content/pg4wp/db.php index 20be4267a6..6f553752a8 100644 --- a/wp-content/pg4wp/db.php +++ b/wp-content/pg4wp/db.php @@ -3,7 +3,7 @@ Plugin Name: PostgreSQL for WordPress (PG4WP) Plugin URI: http://www.hawkix.net Description: PG4WP is a special 'plugin' enabling WordPress to use a PostgreSQL database. -Version: 1.3.1 +Version: 1.3.1+ Author: Hawk__ Author URI: http://www.hawkix.net License: GPLv2 or newer. @@ -18,7 +18,7 @@ define( 'PG4WP_DEBUG', false); // If you just want to log queries that generate errors, leave PG4WP_DEBUG to "false" // and set this to true -define( 'PG4WP_LOG_ERRORS', false); +define( 'PG4WP_LOG_ERRORS', true); // If you want to allow insecure configuration (from the author point of view) to work with PG4WP, // change this to true diff --git a/wp-content/pg4wp/driver_pgsql.php b/wp-content/pg4wp/driver_pgsql.php index 4546397bfe..0536b95801 100644 --- a/wp-content/pg4wp/driver_pgsql.php +++ b/wp-content/pg4wp/driver_pgsql.php @@ -25,7 +25,9 @@ $GLOBALS['pg4wp_last_insert'] = ''; $GLOBALS['pg4wp_connstr'] = ''; $GLOBALS['pg4wp_conn'] = false; - + + function wpsql_ping($conn) + { return pg_ping($conn); } function wpsql_num_rows($result) { return pg_num_rows($result); } function wpsql_numrows($result) @@ -61,7 +63,6 @@ function wpsql_result($result, $i, $fieldname) { return pg_fetch_result($result, $i, $fieldname); } ****/ - // This is a fake connection except during installation function wpsql_connect($dbserver, $dbuser, $dbpass) { $GLOBALS['pg4wp_connstr'] = ''; @@ -77,12 +78,11 @@ function wpsql_connect($dbserver, $dbuser, $dbpass) elseif( !PG4WP_INSECURE) wp_die( 'Connecting to your PostgreSQL database without a password is considered insecure.
If you want to do it anyway, please set "PG4WP_INSECURE" to true in your "db.php" file.' ); - - // While installing, we test the connection to 'template1' (as we don't know the effective dbname yet) - if( defined('WP_INSTALLING') && WP_INSTALLING) - return wpsql_select_db(DB_NAME); // Heroku Postgres 9.1 does not allow connection to 'template1' - - return 1; + + // PostgreSQL must connect to a specific database (unlike MySQL) + // Guess at one here and reconnect as required in wpsql_select_db + $dbname = defined('DB_NAME') && DB_NAME ? DB_NAME : 'template1'; + return pg_connect( $GLOBALS['pg4wp_connstr'].' dbname='.$dbname); } // The effective connection happens here @@ -90,6 +90,7 @@ function wpsql_select_db($dbname, $connection_id = 0) { $pg_connstr = $GLOBALS['pg4wp_connstr'].' dbname='.$dbname; + // Note: pg_connect returns existing connection for same connstr $GLOBALS['pg4wp_conn'] = pg_connect($pg_connstr); if( $GLOBALS['pg4wp_conn']) @@ -183,7 +184,9 @@ function wpsql_insert_id($lnk = NULL) elseif( PG4WP_DEBUG || PG4WP_ERROR_LOG) { $log = '['.microtime(true)."] wpsql_insert_id() was called with '$table' and '$ins_field'". - " and generated an error. The latest INSERT query was :\n'$lastq'\n"; + " and returned the error:\n".pg_last_error(). + "\nFor the query:\n".$sql. + "\nThe latest INSERT query was :\n'$lastq'\n"; error_log( $log, 3, PG4WP_LOG.'pg4wp_errors.log'); } } @@ -195,6 +198,7 @@ function wpsql_insert_id($lnk = NULL) function pg4wp_rewrite( $sql) { + // Note: Can be called from constructor before $wpdb is set global $wpdb; $logto = 'queries'; @@ -226,9 +230,34 @@ function pg4wp_rewrite( $sql) $pattern = '/SELECT\s+([^\s]+)\s+(FROM.+)/'; $sql = preg_replace( $pattern, 'SELECT COUNT($1) $2', $sql); } + + // Ensure that ORDER BY column appears in SELECT DISTINCT fields + $pattern = '/^SELECT DISTINCT.*ORDER BY\s+(\S+)/'; + if( preg_match( $pattern, $sql, $matches) && + strpos( $sql, $matches[1]) > strpos( $sql, 'ORDER BY') && + false === strpos( $sql, '*')) + { + if( false !== strpos( $sql, 'GROUP BY')) + { + $pattern = '/ FROM /'; + $sql = preg_replace( $pattern, ', MIN('.$matches[1].') AS '.$matches[1].' FROM ', $sql, 1); + } + else + { + $pattern = '/ FROM /'; + $sql = preg_replace( $pattern, ', '.$matches[1].' FROM ', $sql, 1); + } + } + + // Convert CONVERT to CAST + $pattern = '/CONVERT\(([^()]*(\(((?>[^()]+)|(?-2))*\))?[^()]*),\s*([^\s]+)\)/x'; + $sql = preg_replace( $pattern, 'CAST($1 AS $4)', $sql); // Handle CAST( ... AS CHAR) $sql = preg_replace( '/CAST\((.+) AS CHAR\)/', 'CAST($1 AS TEXT)', $sql); + + // Handle CAST( ... AS SIGNED) + $sql = preg_replace( '/CAST\((.+) AS SIGNED\)/', 'CAST($1 AS INTEGER)', $sql); // Handle COUNT(*)...ORDER BY... $sql = preg_replace( '/COUNT(.+)ORDER BY.+/s', 'COUNT$1', $sql); @@ -249,6 +278,9 @@ function pg4wp_rewrite( $sql) $pattern = '/DATE_ADD[ ]*\(([^,]+),([^\)]+)\)/'; $sql = preg_replace( $pattern, '($1 + $2)', $sql); + + $pattern = '/GROUP_CONCAT\(([^()]*(\(((?>[^()]+)|(?-2))*\))?[^()]*)\)/x'; + $sql = preg_replace( $pattern, "string_agg($1, ',')", $sql); // UNIX_TIMESTAMP in MYSQL returns an integer $pattern = '/UNIX_TIMESTAMP\(([^\)]+)\)/'; @@ -270,8 +302,15 @@ function pg4wp_rewrite( $sql) // MySQL 'IF' conversion - Note : NULLIF doesn't need to be corrected $pattern = '/ (?prefix.'posts.ID', '' , $sql); + if( isset($wpdb)) + { + $sql = str_replace('GROUP BY '.$wpdb->prefix.'posts.ID', '' , $sql); + } $sql = str_replace("!= ''", '<> 0', $sql); // MySQL 'LIKE' is case insensitive by default, whereas PostgreSQL 'LIKE' is @@ -285,9 +324,28 @@ function pg4wp_rewrite( $sql) $sql = str_replace( 'post_date_gmt > 1970', 'post_date_gmt > to_timestamp (\'1970\')', $sql); // Akismet sometimes doesn't write 'comment_ID' with 'ID' in capitals where needed ... - if( false !== strpos( $sql, $wpdb->comments)) + if( isset($wpdb) && false !== strpos( $sql, $wpdb->comments)) $sql = str_replace(' comment_id ', ' comment_ID ', $sql); - + + // MySQL allows integers to be used as boolean expressions + // where 0 is false and all other values are true. + // + // Although this could occur anywhere with any number, so far it + // has only been observed as top-level expressions in the WHERE + // clause and only with 0. For performance, limit current + // replacements to that. + $pattern_after_where = '(?:\s*$|\s+(GROUP|HAVING|ORDER|LIMIT|PROCEDURE|INTO|FOR|LOCK))'; + $pattern = '/(WHERE\s+)0(\s+AND|\s+OR|' . $pattern_after_where . ')/'; + $sql = preg_replace( $pattern, '$1false$2', $sql); + + $pattern = '/(AND\s+|OR\s+)0(' . $pattern_after_where . ')/'; + $sql = preg_replace( $pattern, '$1false$2', $sql); + + // MySQL supports strings as names, PostgreSQL needs identifiers. + // Limit to after closing parenthesis to reduce false-positives + // Currently only an issue for nextgen-gallery plugin + $pattern = '/\) AS \'([^\']+)\'/'; + $sql = preg_replace( $pattern, ') AS "$1"', $sql); } // SELECT elseif( 0 === strpos($sql, 'UPDATE')) { @@ -299,9 +357,12 @@ function pg4wp_rewrite( $sql) $pattern = '/[ ]*`([^` ]+)`[ ]*=/'; $sql = preg_replace( $pattern, ' $1 =', $sql); + // Those are used when we need to set the date to now() in gmt time + $sql = str_replace( "'0000-00-00 00:00:00'", 'now() AT TIME ZONE \'gmt\'', $sql); + // For correct ID quoting - $pattern = '/[ ]*([^ ]*ID[^ ]*)[ ]*=/'; - $sql = preg_replace( $pattern, ' "$1" =', $sql); + $pattern = '/(,|\s)[ ]*([^ \']*ID[^ \']*)[ ]*=/'; + $sql = preg_replace( $pattern, '$1 "$2" =', $sql); // This will avoid modifications to anything following ' SET ' list($sql,$end) = explode( ' SET ', $sql, 2); @@ -348,6 +409,13 @@ function pg4wp_rewrite( $sql) $sql = 'DELETE FROM '.$table.' WHERE '.$matches[2].' = '.$matches[3].';'.$sql; } } + elseif( 0 === strpos($sql, 'INSERT IGNORE')) + { + // Note: Requires PostgreSQL 9.0 and USAGE privilege. + // Could do query-specific rewrite using SELECT without FROM + // as in http://stackoverflow.com/a/13342031 + $sql = 'DO $$BEGIN INSERT'.substr($sql, 13).'; EXCEPTION WHEN unique_violation THEN END;$$;'; + } // To avoid Encoding errors when inserting data coming from outside if( preg_match('/^.{1}/us',$sql,$ar) != 1) @@ -365,6 +433,15 @@ function pg4wp_rewrite( $sql) elseif( 0 === strpos( $sql, 'DELETE' )) { $logto = 'DELETE'; + + // ORDER BY is not supported in DELETE queries, and not required + // when LIMIT is not present + if( false !== strpos( $sql, 'ORDER BY') && false === strpos( $sql, 'LIMIT')) + { + $pattern = '/ORDER BY \S+ (ASC|DESC)?/'; + $sql = preg_replace( $pattern, '', $sql); + } + // LIMIT is not allowed in DELETE queries $sql = str_replace( 'LIMIT 1', '', $sql); $sql = str_replace( ' REGEXP ', ' ~ ', $sql); @@ -375,6 +452,18 @@ function pg4wp_rewrite( $sql) "(SELECT o1.option_id FROM $wpdb->options AS o1, $wpdb->options AS o2 " . "WHERE o1.option_name = o2.option_name " . "AND o1.option_id < o2.option_id)"; + // Rewrite _transient_timeout multi-table delete query + elseif( 0 === strpos( $sql, 'DELETE a, b FROM wp_options a, wp_options b')) + { + $where = substr( $sql, strpos($sql, 'WHERE ') + 6); + $where = rtrim( $where, " \t\n\r;"); + // Fix string/number comparison by adding check and cast + $where = str_replace( 'AND b.option_value', 'AND b.option_value ~ \'^[0-9]+$\' AND CAST(b.option_value AS BIGINT)', $where); + // Mirror WHERE clause to delete both sides of self-join. + $where2 = strtr( $where, array('a.' => 'b.', 'b.' => 'a.')); + $sql = 'DELETE FROM wp_options a USING wp_options b WHERE '. + '('.$where.') OR ('.$where2.');'; + } // Akismet sometimes doesn't write 'comment_ID' with 'ID' in capitals where needed ... if( false !== strpos( $sql, $wpdb->comments)) @@ -399,7 +488,7 @@ function pg4wp_rewrite( $sql) $sql = "SET NAMES 'utf8'"; } // Load up upgrade and install functions as required - $begin = substr( $sql, 0, 3); + $begin = strtoupper( substr( $sql, 0, 3)); $search = array( 'SHO', 'ALT', 'DES', 'CRE', 'DRO'); if( in_array($begin, $search)) { @@ -455,7 +544,7 @@ function pg4wp_rewrite( $sql) $pattern = '/INSERT INTO (\w+)\s+\([ a-zA-Z_"]+/'; preg_match($pattern, $sql, $matches); $GLOBALS['pg4wp_ins_table'] = $matches[1]; - $match_list = split(' ', $matches[0]); + $match_list = explode(' ', $matches[0]); if( $GLOBALS['pg4wp_ins_table']) { $GLOBALS['pg4wp_ins_field'] = trim($match_list[3],' () '); diff --git a/wp-content/pg4wp/driver_pgsql_install.php b/wp-content/pg4wp/driver_pgsql_install.php index ceb3e18b9c..f03c257822 100644 --- a/wp-content/pg4wp/driver_pgsql_install.php +++ b/wp-content/pg4wp/driver_pgsql_install.php @@ -20,6 +20,7 @@ 'unsigned' => '', 'gmt datetime NOT NULL default \'0000-00-00 00:00:00\'' => 'gmt timestamp NOT NULL DEFAULT timezone(\'gmt\'::text, now())', 'default \'0000-00-00 00:00:00\'' => 'DEFAULT now()', + '\'0000-00-00 00:00:00\'' => 'now()', 'datetime' => 'timestamp', 'DEFAULT CHARACTER SET utf8' => '', @@ -31,19 +32,57 @@ 'tinyint(1)' => 'smallint', "enum('0','1')" => 'smallint', 'COLLATE utf8_general_ci' => '', + + // For flash-album-gallery plugin + 'tinyint' => 'smallint', ); function pg4wp_installing( $sql, &$logto) { global $wpdb; - // SHOW INDEX emulation - if( 0 === strpos( $sql, 'SHOW INDEX')) + // Emulate SHOW commands + if( 0 === strpos( $sql, 'SHOW') || 0 === strpos( $sql, 'show')) { - $logto = 'SHOWINDEX'; - $pattern = '/SHOW INDEX FROM\s+(\w+)/'; - preg_match( $pattern, $sql, $matches); - $table = $matches[1]; + // SHOW VARIABLES LIKE emulation for sql_mode + // Used by nextgen-gallery plugin + if( 0 === strpos( $sql, "SHOW VARIABLES LIKE 'sql_mode'")) + { + // Act like MySQL default configuration, where sql_mode is "" + $sql = 'SELECT \'sql_mode\' AS "Variable_name", \'\' AS "Value";'; + } + // SHOW COLUMNS emulation + elseif( preg_match('/SHOW\s+(FULL\s+)?COLUMNS\s+(?:FROM\s+|IN\s+)`?(\w+)`?(?:\s+LIKE\s+(.+)|\s+WHERE\s+(.+))?/i', $sql, $matches)) + { + $logto = 'SHOWCOLUMN'; + $full = $matches[1]; + $table = $matches[2]; + $like = isset($matches[3]) ? $matches[3] : FALSE; + $where = isset($matches[4]) ? $matches[4] : FALSE; +// Wrap as sub-query to emulate WHERE behavior +$sql = ($where ? 'SELECT * FROM (' : ''). +'SELECT column_name as "Field", + data_type as "Type",'.($full ? ' + NULL as "Collation",' : '').' + is_nullable as "Null", + \'\' as "Key", + column_default as "Default", + \'\' as "Extra"'.($full ? ', + \'select,insert,update,references\' as "Privileges", + \'\' as "Comment"' : '').' +FROM information_schema.columns +WHERE table_name = \''.$table.'\''.($like ? ' + AND column_name LIKE '.$like : '').($where ? ') AS columns +WHERE '.$where : '').';'; + } + // SHOW INDEX emulation + elseif( 0 === strpos( $sql, 'SHOW INDEX')) + { + $logto = 'SHOWINDEX'; + $pattern = '/SHOW INDEX FROM\s+(\w+)/'; + preg_match( $pattern, $sql, $matches); + $table = $matches[1]; + // Note: Row order must be in column index position order $sql = 'SELECT bc.relname AS "Table", CASE WHEN i.indisunique THEN \'0\' ELSE \'1\' END AS "Non_unique", CASE WHEN i.indisprimary THEN \'PRIMARY\' WHEN bc.relname LIKE \'%usermeta\' AND ic.relname = \'umeta_key\' @@ -56,19 +95,44 @@ function pg4wp_installing( $sql, &$logto) AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) AND a.attrelid = bc.oid AND bc.relname = \''.$table.'\' - ORDER BY a.attname;'; + ORDER BY "Key_name", CASE a.attnum + WHEN i.indkey[0] THEN 0 + WHEN i.indkey[1] THEN 1 + WHEN i.indkey[2] THEN 2 + WHEN i.indkey[3] THEN 3 + WHEN i.indkey[4] THEN 4 + WHEN i.indkey[5] THEN 5 + WHEN i.indkey[6] THEN 6 + WHEN i.indkey[7] THEN 7 + END;'; + } + // SHOW TABLES emulation + elseif( preg_match('/SHOW\s+(FULL\s+)?TABLES\s+(?:LIKE\s+(.+)|WHERE\s+(.+))?/i', $sql, $matches)) + { + $logto = 'SHOWTABLES'; + $full = $matches[1]; + $like = $matches[2]; + $where = $matches[3]; +// Wrap as sub-query to emulate WHERE behavior +$sql = ($where ? 'SELECT * FROM (' : ''). +'SELECT table_name as "Tables_in_'.$wpdb->dbname.'"'.($full ? ', + table_type AS "Table_type"' : '').' +FROM information_schema.tables'.($like ? ' +WHERE table_name LIKE '.$like : '').($where ? ') AS tables +WHERE '.$where : '').';'; + } } // Table alteration elseif( 0 === strpos( $sql, 'ALTER TABLE')) { $logto = 'ALTER'; - $pattern = '/ALTER TABLE\s+(\w+)\s+CHANGE COLUMN\s+([^\s]+)\s+([^\s]+)\s+([^ ]+)( unsigned|)\s+(NOT NULL|)\s*(default (.+)|)/'; + $pattern = '/ALTER TABLE\s+(\w+)\s+CHANGE COLUMN\s+([^\s]+)\s+([^\s]+)\s+([^ ]+)( unsigned|)\s*(NOT NULL|)\s*(default (.+)|)/'; if( 1 === preg_match( $pattern, $sql, $matches)) { $table = $matches[1]; $col = $matches[2]; $newname = $matches[3]; - $type = $matches[4]; + $type = strtolower($matches[4]); if( isset($GLOBALS['pg4wp_ttr'][$type])) $type = $GLOBALS['pg4wp_ttr'][$type]; $unsigned = $matches[5]; @@ -86,12 +150,19 @@ function pg4wp_installing( $sql, &$logto) $newq .= ";ALTER TABLE $table RENAME COLUMN $col TO $newcol;"; $sql = $newq; } + $pattern = '/ALTER TABLE\s+(\w+)\s+ALTER COLUMN\s+/'; + if( 1 === preg_match( $pattern, $sql)) + { + // Translate default values + $sql = str_replace( + array_keys($GLOBALS['pg4wp_ttr']), array_values($GLOBALS['pg4wp_ttr']), $sql); + } $pattern = '/ALTER TABLE\s+(\w+)\s+ADD COLUMN\s+([^\s]+)\s+([^ ]+)( unsigned|)\s+(NOT NULL|)\s*(default (.+)|)/'; if( 1 === preg_match( $pattern, $sql, $matches)) { $table = $matches[1]; $col = $matches[2]; - $type = $matches[3]; + $type = strtolower($matches[3]); if( isset($GLOBALS['pg4wp_ttr'][$type])) $type = $GLOBALS['pg4wp_ttr'][$type]; $unsigned = $matches[4]; @@ -107,13 +178,18 @@ function pg4wp_installing( $sql, &$logto) $newq .= " NOT NULL"; $sql = $newq; } - $pattern = '/ALTER TABLE\s+(\w+)\s+ADD (UNIQUE |)KEY\s+([^\s]+)\s+\(([^\)]+)\)/'; + $pattern = '/ALTER TABLE\s+(\w+)\s+ADD (UNIQUE |)KEY\s+([^\s]+)\s+\(((?:[^\(\)]+|\([^\(\)]+\))+)\)/'; if( 1 === preg_match( $pattern, $sql, $matches)) { $table = $matches[1]; $unique = $matches[2]; $index = $matches[3]; $columns = $matches[4]; + + // Remove prefix indexing + // Rarely used and apparently unnecessary for current uses + $columns = preg_replace( '/\([^\)]*\)/', '', $columns); + // Workaround for index name duplicate $index = $table.'_'.$index; $sql = "CREATE {$unique}INDEX $index ON $table ($columns)";