<?php
// $Id: database.pdo_pgsql.inc,v 1.3 2007/12/11 07:21:32 hswong3i Exp $

/**
 * @file
 * Database interface code for PostgreSQL database servers.
 */

/**
 * @ingroup database
 * @{
 */

// Include functions shared between pgsql and pdo_pgsql.
require_once './includes/common.postgresql.inc';

// Include schema API shared between pgsql and pdo_pgsql.
require_once './includes/schema.postgresql.inc';

/**
 * Initialize a database connection.
 */
function db_connect($url) {
  // Check if PDO PostgreSQL support is present in PHP.
  if (!(class_exists('PDO') && extension_loaded('pdo_pgsql'))) {
    _db_error_page('Unable to use the PostgreSQL database because the pdo_pgsql extension for PHP is not installed. Check your php.ini to see how you can enable it.');
  }

  $url = parse_url($url);

  // Decode url-encoded information in the db connection string.
  $username = isset($url['user']) ? urldecode($url['user']) : NULL;
  $password = isset($url['pass']) ? urldecode($url['pass']) : NULL;

  // Build pdo_pgsql connection string and allow for non-standard PostgreSQL port.
  $dsn[] = isset($url['host']) ? 'host='. urldecode($url['host']) : NULL;
  $dsn[] = isset($url['path']) ? 'dbname='. substr(urldecode($url['path']), 1) : NULL;
  $dsn[] = isset($url['port']) ? 'port='. urldecode($url['port']) : NULL;
  $dsn = 'pgsql:'. implode(' ', $dsn);

  $driver_options = array(
    // Throw a PDOException and set its properties to reflect the error code
    // and error information.
    PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING,
    //  Leave column names as returned by the database driver.
    PDO::ATTR_CASE => PDO::CASE_NATURAL,
  );

  try {
    $connection = new PDO($dsn, $username, $password, $driver_options);
  } catch (PDOExecption $e) {
    _db_error_page($e->getMessage());
  }

  return $connection;
}

/**
 * Runs a basic query in the active database.
 *
 * User-supplied arguments to the query should be passed in as separate
 * parameters so that they can be properly escaped to avoid SQL injection
 * attacks.
 *
 * @param $query
 *   A string containing an SQL query.
 * @param ...
 *   A variable number of arguments which are substituted into the query
 *   using printf() syntax. Instead of a variable number of query arguments,
 *   you may also pass a single array containing the query arguments.
 *
 *   Valid %-modifiers are: %s, %d, %f and %b (binary data, do not enclose
 *   in '').
 *
 *   NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
 *   and TRUE values to decimal 1.
 *
 * @return
 *   A database query result resource, or FALSE if the query was not
 *   executed correctly.
 */
function db_query($query) {
  $args = func_get_args();
  array_shift($args);
  $query = db_prefix_tables($query);
  $query = db_escape_quote($query);
  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
    $args = $args[0];
  }
  _db_query_callback(array('query' => $query, 'args' => $args), TRUE);
  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
  $args = _db_query_callback(NULL, TRUE);
  return _db_query(array('query' => $query, 'args' => $args));
}

/**
 * Helper function for db_query().
 */
function _db_query($query, $debug = 0) {
  global $active_db, $last_result, $queries, $user;

  // Expand $query if it is in array format.
  $args = array();
  if (is_array($query)) {
    $args = $query['args'];
    $query = $query['query'];

    // Check if locator exists in query, or else unset it.
    foreach ($args as $key => $value) {
      if (!is_array($args[$key]) || !isset($args[$key]['locator']) || !strstr($query, $args[$key]['locator'])) {
        unset($args[$key]);
      }
    }
  }

  if (variable_get('dev_query', 0)) {
    list($usec, $sec) = explode(' ', microtime());
    $timer = (float)$usec + (float)$sec;
    // If devel.module query logging is enabled, prepend a comment with the username and calling function
    // to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact
    // code is issueing the slow query.
    $bt = debug_backtrace();
    // t() may not be available yet so we don't wrap 'Anonymous'.
    $name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous');
    // str_replace() to prevent SQL injection via username or anonymous name.
    $name = str_replace(array('*', '/'), '', $name);
    $query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query;
  }

  $stmt = $active_db->prepare($query);
  if ($stmt === FALSE) {
    $error = $active_db->errorInfo();
    if (is_array($error) && $error[0] != 0 && isset($error[2])) {
      // Indicate to drupal_error_handler that this is a database error.
      ${DB_ERROR} = TRUE;
      trigger_error(check_plain($error[2] ."\nquery: ". $query), E_USER_WARNING);
    }
    return FALSE;
  }

  if (count($args)) {
    foreach ($args as $key => $value) {
      if (isset($args[$key]['type'])) {
        $stmt->bindValue($args[$key]['locator'], $args[$key]['data'], $args[$key]['type']);
      }
      else {
        $stmt->bindValue($args[$key]['locator'], $args[$key]['data']);
      }
    }
  }

  // Execute the statement.
  $result = $stmt->execute();
  if ($result === FALSE) {
    $error = $stmt->errorInfo();
    if (is_array($error) && $error[0] != 0 && isset($error[2])) {
      // Indicate to drupal_error_handler that this is a database error.
      ${DB_ERROR} = TRUE;
      trigger_error(check_plain($error[2] ."\nquery: ". $query), E_USER_WARNING);
    }
    return FALSE;
  }

  if (variable_get('dev_query', 0)) {
    $query = $bt[2]['function'] ."\n". $query;
    list($usec, $sec) = explode(' ', microtime());
    $stop = (float)$usec + (float)$sec;
    $diff = $stop - $timer;
    $queries[] = array($query, $diff);
  }

  if ($debug) {
    $error = $active_db->errorInfo();
    print '<p>query: '. $query .'<br />error:'. $error[2] .'</p>';
  }

  $last_result = $stmt;
  return $last_result;
}

/**
 * Fetch one result row from the previous query as an object.
 *
 * @param $result
 *   A database query result resource, as returned from db_query().
 * @return
 *   An object representing the next row of the result, or FALSE. The attributes
 *   of this object are the table fields selected by the query.
 */
function db_fetch_object($result) {
  if ($result) {
    $object = $result->fetch(PDO::FETCH_OBJ);
    return isset($object) ? $object : FALSE;
  }
  return FALSE;
}

/**
 * Fetch one result row from the previous query as an array.
 *
 * @param $result
 *   A database query result resource, as returned from db_query().
 * @return
 *   An associative array representing the next row of the result, or FALSE.
 *   The keys of this object are the names of the table fields selected by the
 *   query, and the values are the field values for this result row.
 */
function db_fetch_array($result) {
  if ($result) {
    $array = $result->fetch(PDO::FETCH_ASSOC);
    return isset($array) ? $array : FALSE;
  }
  return FALSE;
}

/**
 * Return an individual result field from the previous query.
 *
 * Only use this function if exactly one field is being selected; otherwise,
 * use db_fetch_object() or db_fetch_array().
 *
 * @param $result
 *   A database query result resource, as returned from db_query().
 * @return
 *   The resulting field or FALSE.
 */
function db_result($result) {
  if ($result) {
    $array = $result->fetch(PDO::FETCH_NUM);
    return isset($array[0]) ? $array[0] : FALSE;
  }
  return FALSE;
}

/**
 * Determine whether the previous query caused an error.
 */
function db_error() {
  global $last_result;
  $error = $last_result->errorInfo();
  if (is_array($error) && isset($error[2])) {
    return $error[2];
  }
}

/**
 * Determine the number of rows changed by the preceding query.
 */
function db_affected_rows() {
  global $last_result;
  return $last_result->rowCount();
}

/*
 * Prepare user input for use in a database query, preventing SQL injection attacks.
 *
 * @param $data
 *   Data to encode. Return with processed array pattern for variable binding.
 * @param $locator
 *   Locator for variable binding.
 * @return
 *   Locator for variable binding.
 */
function db_escape_decimal(&$data, $locator) {
  $data = array(
    'locator' => $locator,
    'data' => (int) $data,
  );
  return $locator;
}

/**
 * Prepare user input for use in a database query, preventing SQL injection attacks.
 *
 * @param $data
 *   Data to encode. Return with processed array pattern for variable binding.
 * @param $locator
 *   Locator for variable binding.
 * @return
 *   Locator for variable binding.
 */
function db_escape_float(&$data, $locator) {
  $data = array(
    'locator' => $locator,
    'data' => (float) $data,
  );
  return $locator;
}

/**
 * Prepare user input for use in a database query, preventing SQL injection attacks.
 *
 * @param $data
 *   Data to encode. Return with processed array pattern for variable binding.
 * @param $locator
 *   Locator for variable binding.
 * @return
 *   Locator for variable binding.
 */
function db_escape_string(&$data, $locator) {
  $data = array(
    'locator' => $locator,
    'data' => $data,
    'type' => PDO::PARAM_STR,
  );
  return $locator;
}

/**
 * Returns a properly formatted Binary Large OBject value.
 *
 * @param $data
 *   Data to encode. Return with processed array pattern for variable binding.
 * @param $locator
 *   Locator for variable binding.
 * @return
 *   Locator for variable binding.
 */
function db_encode_blob(&$data, $locator) {
  // LOB for PgSQL must define by PDO::PARAM_LOB, or else will case error
  // during INSER/UPDATE. So we need to use Oracle style locator-data-type
  // variable binding for pdo_pgsql.
  $data = array(
    'locator' => $locator,
    'data' => $data,
    'type' => PDO::PARAM_LOB,
  );
  return $locator;
}

/**
 * Returns text from a Binary Large OBject value.
 *
 * @param $data
 *   Data to decode.
 * @return
 *   Decoded data.
 */
function db_decode_blob($data) {
  // pdo_pgsql return bytea as stream, so we just need to use Streams API.
  // Check http://bugs.php.net/bug.php?id=37124 for more information.
  return stream_get_contents($data);
}

/**
 * @} End of "ingroup database".
 */