Initial commit.

This commit is contained in:
2026-03-10 21:25:26 +08:00
commit 78739bf725
3089 changed files with 472990 additions and 0 deletions

View File

@@ -0,0 +1,708 @@
<?php
// File name: postgres.inc.php
// Description: PHP subroutines to handle PostgreSQL operation
// Date: 2004-05-22
// Author: imacat <imacat@pristine.com.tw>
// 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;
}
?>