// Copyright: Copyright (C) 2004-2008 Pristine Communications // Set the include path if (!defined("INCPATH_SET")) { require_once dirname(__FILE__) . "/incpath.inc.php"; } // Referenced subroutines with high precedence require_once "monica/sqlconst.inc.php"; // Referenced subroutines require_once "monica/errhndl.inc.php"; require_once "monica/getlang.inc.php"; require_once "monica/lninfo.inc.php"; require_once "monica/requri.inc.php"; require_once "monica/sqllogin.inc.php"; // Settings $_PG_CONN = null; // xpg_connect: Connect to the PostgreSQL database function xpg_connect($database = null) { global $_PG_CONN; // Connected if (!is_null($_PG_CONN)) { return; } // Obtain the SQL log-in information if (is_null($database)) { if (getenv("PGDATABASE") !== false) { $database = getenv("PGDATABASE"); } elseif (defined("PACKAGE")) { $database = PACKAGE; } } if (getenv("PGHOST") !== false) { $host = getenv("PGHOST"); } else { $host = null; } // Login with from SQLLOGIN environment variable as a web application // php-cgi does not have STDIN and STDERR even on console if (PHP_SAPI != "cli") { $r = get_sql_login_info(SQL_POSTGRESQL, $database, $host); // Connect it $_PG_CONN = pg_connect(sprintf("host=%s dbname=%s user=%s password=%s", $r["PGHOST"], $r["PGDATABASE"], $r["PGUSER"], $r["PGPASSWORD"])); if ($_PG_CONN === false) { $_PG_CONN = null; trigger_error("Failed connecting to the PostgreSQL server.\n" . $GLOBALS["php_errormsg"], E_USER_ERROR); } // Ask the password from the console } else { $user = null; $subseq = false; set_error_handler("null_error_handler"); $_PG_CONN = pg_connect("dbname=$database"); restore_error_handler(); while ($_PG_CONN === false) { global $php_errormsg; if ($subseq) { fprintf(STDERR, "%s\n", $GLOBALS["php_errormsg"]); sleep(5); } $subseq = true; // Obtain the current login user if ( is_null($user) && preg_match("/ failed for user \"(.+?)\"/", $GLOBALS["php_errormsg"], $m)) { $user = $m[1]; } // Disable console echo system("/bin/stty -echo"); fprintf(STDERR, !is_null($user)? "PostgreSQL password for $user: ": "PostgreSQL password: "); $passwd = fgets(STDIN); fprintf(STDERR, "\n"); // Restore console echo status system("/bin/stty echo"); // STDIN is not available if ($passwd === false) { die(THIS_FILE . ": Failed connecting to the PostgreSQL server\n"); } $passwd = trim($passwd); set_error_handler("null_error_handler"); $_PG_CONN = pg_connect("dbname=$database password=$passwd"); restore_error_handler(); } } // Set the client encoding $chaset = defined("SQL_CHARSET")? SQL_CHARSET: "utf8"; $result = pg_set_client_encoding($_PG_CONN, $chaset); if ($result == -1) { trigger_error("Failed setting client encoding to $chaset.\n" . (pg_last_error()? pg_last_error(): $GLOBALS["php_errormsg"]), E_USER_ERROR); } return; } // xpg_close: Disconnect from the PostgreSQL database function xpg_close() { global $_PG_CONN; if (is_null($_PG_CONN)) { return; } $result = pg_close($_PG_CONN); if ($result !== true) { trigger_error("Failed disconnecting from the PostgreSQL server.\n" . (pg_last_error()? pg_last_error(): $GLOBALS["php_errormsg"]), E_USER_ERROR); } $_PG_CONN = null; return; } ///////////////////////// // Concurrency Control: Transactions and Locks ///////////////////////// $_PG_IN_TRANSACTION = false; // pg_begin: Begin a PostgreSQL transaction function pg_begin() { if (!$GLOBALS["_PG_IN_TRANSACTION"]) { $begin = "START TRANSACTION;\n"; xpg_query($begin); $GLOBALS["_PG_IN_TRANSACTION"] = true; } } // pg_commit: Commit a PostgreSQL transaction function pg_commit() { if ($GLOBALS["_PG_IN_TRANSACTION"]) { $commit = "COMMIT;\n"; xpg_query($commit); $GLOBALS["_PG_IN_TRANSACTION"] = false; } } // pg_rollback: Rollback a PostgreSQL transaction function pg_rollback() { if ($GLOBALS["_PG_IN_TRANSACTION"]) { $rollback = "ROLLBACK;\n"; xpg_query($rollback); $GLOBALS["_PG_IN_TRANSACTION"] = false; } } // pg_lock: PostgreSQL table-locking handler // PostgreSQL has no unlock // Input: An associative array, where its keys are the tables to lock, // and its values can be one of the following: // LOCK_SH: Request a read lock // LOCK_EX: Request a write lock // LOCK_UN: No effect // null has no effect here. // Return: None. Errors are directed to error handlers function pg_lock($locks = null) { // Bounce for nothing if (is_null($locks) || count($locks) == 0) { return; } // Remove the table aliases - compatibility with stupid MySQL $abslocks = array(); foreach (array_keys($locks) as $origtable) { // Remove the table aliases $abstable = preg_replace("/\s+AS\s+.+?$/i", "", $origtable); // No override previous write lock if ( array_key_exists($abstable, $abslocks) && $abslocks[$abstable] == LOCK_EX) { continue; } // Set the lock $abslocks[$abstable] = $locks[$origtable]; } $locks = $abslocks; // Split into different lock modes $reads = array(); $writes = array(); foreach (array_keys($locks) as $table) { switch ($locks[$table]) { case LOCK_SH: $reads[] = $table; break; case LOCK_EX: $writes[] = $table; break; default: trigger_error("Bad SQL lock request: \"$locks[$table]\" on table \"$table\".", E_USER_ERROR); } } // Start transaction if not started yet. Table locks cannot // live outside of transactions at all. if (!$GLOBALS["_PG_IN_TRANSACTION"]) { pg_begin(); } // Request the locks if (count($reads) > 0) { $locktable = "LOCK TABLE " . implode(", ", $reads) . " IN SHARE MODE;\n"; xpg_query($locktable); } if (count($writes) > 0) { $locktable = "LOCK TABLE " . implode(", ", $writes) . " IN ACCESS EXCLUSIVE MODE;\n"; xpg_query($locktable); } return; } // xpg_query: Do a PostgreSQL query and report the error function xpg_query($query) { set_error_handler("null_error_handler"); $result = pg_query($query); restore_error_handler(); if ($result === false) { trigger_error("Failed pg_query().\n$query\n" . (pg_last_error()? pg_last_error(): $GLOBALS["php_errormsg"]), E_USER_ERROR); } return $result; } // pg_seek: Move the PostgreSQL result pointer function pg_seek($result, $offset) { $result = pg_result_seek($result, $offset); if ($result === false) { trigger_error("Failed pg_result_seek().\n" . (pg_last_error()? pg_last_error(): $GLOBALS["php_errormsg"]), E_USER_ERROR); } return $result; } // xpg_fetch_assoc: Return a PostgreSQL row as an associative array function xpg_fetch_assoc($result) { // Fetch the raw data now $row = pg_fetch_assoc($result); // Return the error if (!is_array($row)) { return $row; } // Adjust the boolean columns foreach (pg_cols_of_type($result, SQL_TYPE_BOOLEAN, SQL_FETCH_ASSOC) as $col) { if (!is_null($row[$col])) { $row[$col] = ($row[$col] == "t"); } } // Adjust the bytea columns foreach (pg_cols_of_type($result, SQL_TYPE_BLOB, SQL_FETCH_ASSOC) as $col) { if (!is_null($row[$col])) { $row[$col] = pg_unescape_bytea($row[$col]); } } // Adjust the integer columns foreach (pg_cols_of_type($result, SQL_TYPE_INTEGER, SQL_FETCH_ASSOC) as $col) { if (!is_null($row[$col])) { settype($row[$col], "integer"); } } // Adjust the big integer columns foreach (pg_cols_of_type($result, SQL_TYPE_BIGINT, SQL_FETCH_ASSOC) as $col) { if ( !is_null($row[$col]) && $row[$col] >= -2147483647 && $row[$col] <= 2147483647) { settype($row[$col], "integer"); } } // Adjust the float columns foreach (pg_cols_of_type($result, SQL_TYPE_FLOAT, SQL_FETCH_ASSOC) as $col) { if (!is_null($row[$col])) { settype($row[$col], "float"); } } return $row; } // xpg_fetch_row: Return a PostgreSQL row as an numeric array function xpg_fetch_row($result) { // Fetch the raw data now $row = pg_fetch_row($result); // Return the error if (!is_array($row)) { return $row; } // Adjust the boolean columns foreach (pg_cols_of_type($result, SQL_TYPE_BOOLEAN, SQL_FETCH_ROW) as $col) { if (!is_null($row[$col])) { $row[$col] = ($row[$col] == "t"); } } // Adjust the bytea columns foreach (pg_cols_of_type($result, SQL_TYPE_BLOB, SQL_FETCH_ROW) as $col) { if (!is_null($row[$col])) { $row[$col] = pg_unescape_bytea($row[$col]); } } // Adjust the integer columns foreach (pg_cols_of_type($result, SQL_TYPE_INTEGER, SQL_FETCH_ROW) as $col) { if (!is_null($row[$col])) { settype($row[$col], "integer"); } } // Adjust the big integer columns foreach (pg_cols_of_type($result, SQL_TYPE_BIGINT, SQL_FETCH_ROW) as $col) { if ( !is_null($row[$col]) && $row[$col] >= -2147483647 && $row[$col] <= 2147483647) { settype($row[$col], "integer"); } } // Adjust the float columns foreach (pg_cols_of_type($result, SQL_TYPE_FLOAT, SQL_FETCH_ROW) as $col) { if (!is_null($row[$col])) { settype($row[$col], "float"); } } return $row; } // pg_tables: Obtain a list of available PostgreSQL tables // and report the error function pg_tables($schema = null) { // Default to the current schema if (is_null($schema)) { $schema = _pg_current_schema(); } $select = "SELECT tablename FROM pg_tables" . " WHERE schemaname='" . pg_escape_string($schema) . "'" . " ORDER BY tablename;\n"; $result = xpg_query($select); $count = pg_num_rows($result); for ($i = 0, $tables = array(); $i < $count; $i++) { $row = pg_fetch_row($result); $tables[] = $row[0]; } return $tables; } // pg_cols: Obtain the column list of a PostgreSQL table function pg_cols($table) { // Cache the result static $cache = array(); // Return the cache if (array_key_exists($table, $cache)) { return $cache[$table]; } global $_PG_CONN; // Use pg_meta_data(). This is marked as experimental in PHP documentation yet. $result = pg_meta_data($_PG_CONN, $table); if ($result === false) { trigger_error("Failed pg_meta_data(\$PG_CONN, \"$table\").\n" . (pg_last_error()? pg_last_error(): $GLOBALS["php_errormsg"]), E_USER_ERROR); } $cache[$table] = array_keys($result); return $cache[$table]; } // pg_cols_ml: Return a list of multi-lingual columns in a PostgreSQL table function pg_cols_ml($table) { // Cache the result static $cache = array(); // Return the cache if (array_key_exists($table, $cache)) { return $cache[$table]; } // Get the columns that have language variants $cols = pg_cols($table); $cache[$table] = array(); $suffix = "_" . getlang(LN_DATABASE); $len = strlen($suffix); for ($i = 0; $i < count($cols); $i++) { // It has a language suffix if (substr($cols[$i], -$len) == $suffix) { $cache[$table][] = substr($cols[$i], 0, -$len); } } return $cache[$table]; } // pg_cols_nl: Return a list of columns without their multi-lingual // deviants in a PostgreSQL table function pg_cols_nl($table) { // Cache the result static $cache = array(); // Return the cache if (array_key_exists($table, $cache)) { return $cache[$table]; } // Get the columns that have language variants $cols = pg_cols($table); $langcols = pg_cols_ml($table); // Remove those language variants $cache[$table] = array(); for ($i = 0; $i < count($cols); $i++) { $pos = strrpos($cols[$i], "_"); // No suffix if ($pos === false) { $cache[$table][] = $cols[$i]; // Check the prefix } else { $prefix = substr($cols[$i], 0, $pos); // The prefix is one of the language columns if (in_array($prefix, $langcols)) { // Not counted yet if (!in_array($prefix, $cache[$table])) { $cache[$table][] = $prefix; } // An ordinary prefix } else { $cache[$table][] = $cols[$i]; } } } return $cache[$table]; } // pg_cols_of_type: Return the columns in a certain data type function pg_cols_of_type($result, $type, $format = SQL_FETCH_ASSOC) { $result_key = _pg_result_hashkey($result); // Cache the result static $cache = array(); // Return the cache if (array_key_exists($result_key, $cache)) { return $cache[$result_key][$type][$format]; } // Check each field type $count = pg_num_fields($result); $cols = array( SQL_TYPE_BOOLEAN => array( SQL_FETCH_ASSOC => array(), SQL_FETCH_ROW => array(), ), SQL_TYPE_BLOB => array( SQL_FETCH_ASSOC => array(), SQL_FETCH_ROW => array(), ), SQL_TYPE_INTEGER => array( SQL_FETCH_ASSOC => array(), SQL_FETCH_ROW => array(), ), SQL_TYPE_BIGINT => array( SQL_FETCH_ASSOC => array(), SQL_FETCH_ROW => array(), ), SQL_TYPE_FLOAT => array( SQL_FETCH_ASSOC => array(), SQL_FETCH_ROW => array(), ), ); for ($i = 0; $i < $count; $i++) { $coltype = pg_field_type($result, $i); $colname = pg_field_name($result, $i); switch ($coltype) { case "bool": $cols[SQL_TYPE_BOOLEAN][SQL_FETCH_ROW][] = $i; $cols[SQL_TYPE_BOOLEAN][SQL_FETCH_ASSOC][] = $colname; break; case "bytea": $cols[SQL_TYPE_BLOB][SQL_FETCH_ROW][] = $i; $cols[SQL_TYPE_BLOB][SQL_FETCH_ASSOC][] = $colname; break; case "int2": case "int4": $cols[SQL_TYPE_INTEGER][SQL_FETCH_ROW][] = $i; $cols[SQL_TYPE_INTEGER][SQL_FETCH_ASSOC][] = $colname; break; case "int8": $cols[SQL_TYPE_BIGINT][SQL_FETCH_ROW][] = $i; $cols[SQL_TYPE_BIGINT][SQL_FETCH_ASSOC][] = $colname; break; case "float4": case "float8": $cols[SQL_TYPE_FLOAT][SQL_FETCH_ROW][] = $i; $cols[SQL_TYPE_FLOAT][SQL_FETCH_ASSOC][] = $colname; break; } } // Cache it $cache[$result_key] = $cols; return $cols[$type][$format]; } // pg_col_lens: Obtain the column lengths of a PostgreSQL table function pg_col_lens($table) { // Cache the result static $cache = array(); // Return the cache if (array_key_exists($table, $cache)) { return $cache[$table]; } $schema = _pg_current_schema(); $select = "SELECT pg_attribute.attname AS col," . " pg_type.typname AS type," . " pg_attribute.attlen AS len," . " pg_attribute.atttypmod AS typmod" . " FROM pg_attribute" . " INNER JOIN pg_class ON pg_attribute.attrelid=pg_class.oid" . " INNER JOIN pg_type ON pg_attribute.atttypid=pg_type.oid" . " INNER JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid" . " WHERE pg_namespace.nspname='" . pg_escape_string($schema) . "'" . " AND pg_class.relname='" . pg_escape_string($table) . "'" . " AND pg_class.relkind='r'" . " AND pg_attribute.attnum>0" . " ORDER BY pg_attribute.attnum;\n"; $result = xpg_query($select); $count = pg_num_rows($result); for ($i = 0, $cache[$table] = array(); $i < $count; $i++) { $row = pg_fetch_assoc($result); switch ($row["type"]) { // Integer -- Digits of the largest number - 1 case "int2": case "int4": case "int8": $cache[$table][$row["col"]] = floor(log10(pow(256, $row["len"]))); settype($cache[$table][$row["col"]], "integer"); break; // Refer to typmod for char and varchar case "varchar": case "bpchar": $cache[$table][$row["col"]] = $row["typmod"] - 4; break; // Set text and bytea to 4294967296 (2^32) (infinite actually) case "text": case "bytea": $cache[$table][$row["col"]] = 4294967296; break; // Set timestamp to 19 case "timestamp": $cache[$table][$row["col"]] = 26; break; // Set date to 10 case "date": $cache[$table][$row["col"]] = 10; break; // Set time to 8 case "time": $cache[$table][$row["col"]] = 8; break; // Set numeric to precision + 1 decimal point // Refer to http://archives.postgresql.org/pgsql-hackers/1999-01/msg00127.php case "numeric": $typmod = $row["typmod"] - 4; $scale = $typmod & 0xFFFF; $precision = $typmod >> 16; $cache[$table][$row["col"]] = $precision + 1; break; // Set boolean to 1 case "bool": $cache[$table][$row["col"]] = 1; break; // Set inet to 18 (nnn.nnn.nnn.nnn/nn) case "inet": $cache[$table][$row["col"]] = 18; break; // Bounce for other columns, so that we know to fix it here default: trigger_error("Unknown column type " . $row["type"] . " for table $table", E_USER_ERROR); break; } } // Hash the multi-lingual columns $lndb = getlang(LN_DATABASE); foreach (pg_cols_ml($table) as $col) { $cache[$table][$col] = $cache[$table][$col . "_" . $lndb]; } return $cache[$table]; } // pg_strcat: Concatenate strings in PostgreSQL // PostgreSQL uses the || operator to concatenate strings function pg_strcat() { $strs = func_get_args(); return implode(" || ", $strs); } // pg_lastupd: Obtain the last updated time of a list of tables function pg_lastupd($tables) { // Bounce if no tables supplied if (is_null($tables) || count($tables) == 0) { return; } // Remove the table aliases for ($i = 0; $i < count($tables); $i++) { $tables[$i] = preg_replace("/\s+AS\s+.+?$/i", "", $tables[$i]); } // Remove duplicates $tables = array_values(array_unique($tables)); // Query $conds = array(); foreach ($tables as $table) { $conds[] = "tabname='" . pg_escape_string($table) . "'"; } $select = "SELECT mtime FROM mtime" . " WHERE " . implode(" OR ", $conds) . " ORDER BY mtime DESC LIMIT 1;\n"; $result = xpg_query($select); // Bounce if no data found if (pg_num_rows($result) != 1) { return; } // Return the result $row = xpg_fetch_assoc($result); return $row["mtime"]; } // pg_dbsize: Obtain the size of the database function pg_dbsize() { $select = "SELECT pg_database_size(datname) FROM pg_database" . " WHERE datname=current_database();\n"; $result = xpg_query($select); $row = xpg_fetch_row($result); return $row[0]; } // pg_date: Return date in a predefined format format function pg_date($expr, $format) { switch ($format) { case SQL_YYYYMMDD: return "to_char($expr, 'YYYYMMDD')"; case SQL_YYYY_YYYYMMDD: return "to_char($expr, 'YYYY/YYYYMMDD')"; case SQL_MM_DD: return "to_char($expr, 'MM-DD')"; case SQL_M_D_EN: return "to_char($expr, 'FMMM/FMDD')"; case SQL_M_D_ZHTW: return "to_char($expr, 'FMMM月FMDD日')"; case SQL_M_D_DE: return "to_char($expr, 'FMDD.FMMM')"; case SQL_HH_MM: return "to_char($expr, 'HH24:MI')"; } } // pg_re: Return the PostgreSQL regular expression operator function pg_re() { return "~"; } // _pg_result_hashkey: Generate a hash key from a PostgreSQL query result function _pg_result_hashkey($result) { // Use the output of var_dump ob_start(); var_dump($result); $key = ob_get_contents(); ob_end_clean(); return $key; } // _pg_current_schema: Obtain the current schema function _pg_current_schema() { // Cache the result static $cache; // Return the cache if (isset($cache)) { return $cache; } $select = "SELECT current_schema();\n"; $result = xpg_query($select); $row = pg_fetch_row($result); $cache = $row[0]; return $cache; } ?>