<?php
// $Id: common.postgresql.inc,v 1.2 2007/12/11 07:21:32 hswong3i Exp $

/**
 * @file
 * Functions shared between pgsql and pdo_pgsql database engines.
 */

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

/**
 * Indicates the string to use to quote identifiers and names.
 */
define('DB_QUOTE_OPERATOR', '"');

/**
 * Indicates the concatenation operator.
 */
define('DB_CONCAT_OPERATOR', '||');

/**
 * Indicates the name of the SQL strtoupper function.
 */
define('DB_UPPER', 'UPPER');

/**
 * Indicates the SQL to generate a random number between 0.00 and 1.00.
 */
define('DB_RAND', 'RANDOM()');

/**
 * Indicates the name of the SQL strlen function.
 */
define('DB_STRLEN', 'LENGTH');

/**
 * Indicates the name of the SQL substr function.
 */
define('DB_SUBSTR', 'SUBSTRING');

/**
 * Replace all escape characters in a query, based on the database specific
 * implementation.
 *
 * Queries sent to Drupal should wrap all identifiers and names in square
 * brackets. This function will search for this syntax and replace it as
 * corresponding escape characters, based on the database specific
 * requirement.
 */
function db_escape_quote($sql) {
  return preg_replace('/\[([A-Za-z0-9_]+)\]/', DB_QUOTE_OPERATOR .'\1'. DB_QUOTE_OPERATOR, $sql);
}

/**
 * Return a portably concatenate strings.
 *
 * @param ...
 *   Variable number of string parameters.
 * @return
 *   Portably concatenate strings.
 */
function db_concat() {
  $args = func_get_args();
  return implode(DB_CONCAT_OPERATOR, $args);
}

/**
 * Returns a string that is the equivalent of MySQL IFNULL or Oracle NVL.
 *
 * @return
 *   If $expr1 is not NULL, returns $expr1; otherwise it returns $expr2.
 */
function db_if_null($expr1, $expr2) {
  return " COALESCE($expr1, $expr2) ";
}

/**
 * Report database status.
 */
function db_status_report() {
  $t = get_t();

  $version = db_version();

  $form['postgresql'] = array(
    'title' => $t('PostgreSQL database'),
    'value' => $version,
  );

  if (version_compare($version, DRUPAL_MINIMUM_POSTGRESQL) < 0) {
    $form['postgresql']['severity'] = REQUIREMENT_ERROR;
    $form['postgresql']['description'] = $t('Your PostgreSQL Server is too old. Drupal requires at least PostgreSQL %version.', array('%version' => DRUPAL_MINIMUM_POSTGRESQL));
  }

  return $form;
}

/**
 * Returns the version of the database server currently in use.
 *
 * @return Database server version
 */
function db_version() {
  return db_result(db_query("SHOW SERVER_VERSION"));
}

/**
 * Insert a row of record into database.
 *
 * @param $table
 *   Table to insert.
 * @param $values
 *   An array containing the insert values. Each element of the array
 *   should be an associatie array with the following keys:
 *   - "field": The database field represented in the table column.
 *   - "placeholder": The placeholder of the table column, using printf()
 *     syntax. Valid %-modifiers are: %d, %f, %s and %b.
 *   - "data": The data to insert into the table column.
 * @return
 *   A database query result resource, or FALSE if the query was not
 *   executed correctly.
 */
function db_query_insert($table, $values) {
  $fields = array();
  $placeholders = array();
  $data = array();
  foreach ($values as $value) {
    $fields[] = $value['field'];
    $placeholders[] = $value['placeholder'];
    $data[] = $value['data'];
  }

  if (!count($fields)) {
    return FALSE;
  }

  $query = "INSERT INTO [{". $table ."}] ([". implode('], [', $fields) ."]) VALUES (". implode(', ', $placeholders) .")";
  return db_query($query, $data);
}

/**
 * Update a row of record in database.
 *
 * @param $table
 *   Table to update.
 * @param $values
 *   An array containing the update values. Each element of the array
 *   should be an associatie array with the following keys:
 *   - "field": The database field represented in the table column.
 *   - "placeholder": The placeholder of the table column, using printf()
 *     syntax. Valid %-modifiers are: %d, %f, %s and %b.
 *   - "data": The data to insert into the table column.
 * @param $where_clause
 *   A string containing an update condition query (where clause).
 * @param ...
 *   A variable number of arguments which are substituted into the query
 *   WHERE condition, 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: %d, %f and %s.
 *
 *   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_update($table, $values, $where_clause = NULL) {
  $args = func_get_args();
  $args = array_slice($args, 3);
  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
    $args = $args[0];
  }

  $fields = array();
  $data = array();
  foreach ($values as $value) {
    $fields[] = '['. $value['field'] .'] = '. $value['placeholder'];
    $data[] = $value['data'];
  }

  if (!count($fields)) {
    return FALSE;
  }

  $query = "UPDATE [{". $table ."}] SET ". implode(', ', $fields);
  if ($where_clause) {
    $query .= " WHERE ". $where_clause;
    $data = array_merge($data, $args);
  }
  return db_query($query, $data);
}

/**
 * Delete a row of record from database.
 *
 * @param $table
 *   Table to delete.
 * @param $where_clause
 *   A string containing an update condition query (where clause).
 * @param ...
 *   A variable number of arguments which are substituted into the query
 *   WHERE condition, 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: %d, %f and %s.
 *
 *   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_delete($table, $where_clause = NULL) {
  $args = func_get_args();
  $args = array_slice($args, 2);
  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
    $args = $args[0];
  }

  $data = array();

  $query = "DELETE FROM [{". $table ."}]";
  if ($where_clause) {
    $query .= " WHERE ". $where_clause;
    $data = $args;
  }
  return db_query($query, $data);
}

/**
 * Returns the last insert id. This function is thread safe.
 *
 * @param $table
 *   The name of the table you inserted into.
 * @param $field
 *   The name of the autoincrement field.
 */
function db_last_insert_id($table, $field) {
  return db_result(db_query("SELECT CURRVAL('{". db_escape_table($table) ."}_". db_escape_table($field) ."_seq')"));
}

/**
 * Runs a limited-range query in the active database.
 *
 * Use this as a substitute for db_query() when a subset of the query
 * is to be returned.
 * 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.
 *
 * @param $from
 *   The first result row to return.
 * @param $count
 *   The maximum number of result rows to return.
 * @return
 *   A database query result resource, or FALSE if the query was not executed
 *   correctly.
 */
function db_query_range($query) {
  $args = func_get_args();
  $count = array_pop($args);
  $from = array_pop($args);
  array_shift($args);

  $query .= ' LIMIT '. (int) $count .' OFFSET '. (int) $from;
  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
    $args = $args[0];
  }
  return db_query($query, $args);
}

/**
 * Runs a SELECT query and stores its results in a temporary table.
 *
 * Use this as a substitute for db_query() when the results need to stored
 * in a temporary table. Temporary tables exist for the duration of the page
 * request.
 * 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.
 *
 * Note that if you need to know how many results were returned, you should do
 * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
 * not give consistent result across different database types in this case.
 *
 * @param $query
 *   A string containing a normal SELECT SQL query.
 * @param ...
 *   A variable number of arguments which are substituted into the query
 *   using printf() syntax. The query arguments can be enclosed in one
 *   array instead.
 *   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.
 *
 * @param $table
 *   The name of the temporary table to select into. This name will not be
 *   prefixed as there is no risk of collision.
 * @return
 *   A database query result resource, or FALSE if the query was not executed
 *   correctly.
 */
function db_query_temporary($query) {
  $args = func_get_args();
  $tablename = array_pop($args);
  array_shift($args);

  $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' AS SELECT', $query);
  if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
    $args = $args[0];
  }
  return db_query($query, $args);
}

/**
 * Lock a table.
 * This function automatically starts a transaction.
 */
function db_lock_table($table) {
  db_query('LOCK TABLE [{'. db_escape_table($table) .'}] IN EXCLUSIVE MODE');
}

/**
 * Unlock all locked tables.
 * This function automatically commits a transaction.
 */
function db_unlock_tables() {
  db_query('COMMIT');
}

/**
 * Check if a table exists.
 */
function db_table_exists($table) {
  return (bool) db_result(db_query("SELECT COUNT(*) FROM [pg_class] WHERE [relname] = '{". db_escape_table($table) ."}'"));
}

/**
 * Check if a column exists in the given table.
 */
function db_column_exists($table, $column) {
  return (bool) db_result(db_query("SELECT COUNT([pg_attribute].[attname]) FROM [pg_class], [pg_attribute] WHERE [pg_attribute].[attrelid] = [pg_class].[oid] AND [pg_class].[relname] = '{". db_escape_table($table) ."}' AND [attname] = '". db_escape_table($column) ."'"));
}

/**
 * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to
 * the SELECT list entry of the given query and the resulting query is returned.
 * This function only applies the wrapper if a DISTINCT doesn't already exist in
 * the query.
 *
 * @param $table Table containing the field to set as DISTINCT
 * @param $field Field to set as DISTINCT
 * @param $query Query to apply the wrapper to
 * @return SQL query with the DISTINCT wrapper surrounding the given table.field.
 */
function db_distinct_field($table, $field, $query) {
  $field_to_select = 'DISTINCT ON (['. $table .'].['. $field ."]) [$table].[$field]";
  // (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
  $query = preg_replace('/(SELECT.*)(?:\['. $table .'\]\.|\s)(?<!DISTINCT\()(?<!DISTINCT\(\['. $table .'\]\.)\['. $field .'\](.*FROM )/AUsi', '\1 '. $field_to_select .'\2', $query);
  $query = preg_replace('/(ORDER BY )(?!\['. $table .'\]\.\['. $field .'\])/', '\1'."[$table].[$field], ", $query);
  return $query;
}

/**
 * Verify if the database is set up correctly.
 */
function db_check_setup() {
  $t = get_t();

  $encoding = db_result(db_query('SHOW server_encoding'));
  if (!in_array(strtolower($encoding), array('unicode', 'utf8'))) {
    drupal_set_message($t('Your PostgreSQL database is set up with the wrong character encoding (%encoding). It is possible it will not work as expected. It is advised to recreate it with UTF-8/Unicode encoding. More information can be found in the <a href="@url">PostgreSQL documentation</a>.', array('%encoding' => $encoding, '@url' => 'http://www.postgresql.org/docs/7.4/interactive/multibyte.html')), 'status');
  }
}

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