<?php
// include library db.php
// database abstraction layer, supports multiple databases

/*
  usage:
  - db_setdatabase will be called automatically each time you use db_sql (it will guarantee database availability)
  - you may call db_unsetdabatase at the end of your work (e.g. at end of script)
  - in normal operation use control constants in your main code and just call db_sql and similar functions
  
  control constants:
  - SERVER_DB_TYPE declares database type, e.g. "POSTGRESQL", "ODBC", "MYSQL" or others
  each database type needs some more special constants:
  POSTGRESQL:
  - SERVER_DB_HOST, e.g. "localhost"
  - SERVER_DB_PORT, e.g. 5432
  - SERVER_DB_NAME, e.g. "testdatabase"
  - SERVER_DB_USER, e.g. "testuser"
  - SERVER_DB_PASS, e.g. "testpass"
  ODBC:
  - SERVER_DB_NAME, e.g. "testdatabase"
  - SERVER_DB_USER, e.g. "testuser"
  - SERVER_DB_PASS, e.g. "testpass"

  available functions:
  - db_setdb = checks/initializes database, will be called automatically
  - db_unsetdb = closes database manually, will be done automatically at the end of the parent script
  - db_sql = executes SQL statement and returns resultset
  - db_row = delivers a specific row from resultset, uses constants DB_ROW_*
  - db_fields = delivers field list from resultset, uses constants DB_FIELD_*
*/


define( "DB_TYPE_POSTGRESQL", "POSTGRESQL" );
define( "DB_TYPE_ODBC", "ODBC" );

define( "DB_ROW_FIRST", 0 );
define( "DB_ROW_NEXT", -1 );
define( "DB_ROW_LAST", -2 );

define( "DB_FIELD_NAME", 0 );
define( "DB_FIELD_TYPE", 1 );
define( "DB_FIELD_LEN", 2 );


// **********************************************************************
function db_setdb ( &$intdbconnid, &$strdberror )
// checks/opens database connection
// updates parameter intdbconnid and strdberror
// returns true if database had already been opened or has been opened successfully,
// otherwise returns false and sets strdberror
{
  $bolretcode = true;
  $strdberror = '';
  
  if ( $intdbconnid == 0 )
  {
    // database is dead or closed -> (re)open it
    switch ( trim(constant("SERVER_DB_TYPE")) )
    {
      case constant("DB_TYPE_POSTGRESQL"):
        $intdbconnid = pg_connect ("host=" . trim(constant("SERVER_DB_HOST")) . " port=" . trim(strval(constant("SERVER_DB_PORT"))) . " dbname=" . trim(constant("SERVER_DB_NAME")) . " user=" . trim(constant("SERVER_DB_USER")) . " password=" . trim(constant("SERVER_DB_PASS")));
        $strdberror = pg_last_error( $intdbconnid );
        break;

      case constant("DB_TYPE_ODBC"):
        $intdbconnid = odbc_connect ( trim(constant("SERVER_DB_NAME")), trim(constant("SERVER_DB_USER")), trim(constant("SERVER_DB_PASS")), SQL_CUR_USE_ODBC );
        $strdberror = odbc_errormsg( $intdbconnid );
        break;

      default:
        // SERVER_DB_TYPE not supported!
        $intdbconnid = 0;
        $strdberror = 'Sorry, database of type "' . trim(constant("SERVER_DB_TYPE")) . '" is not supported.';
        break;
    }

    // set return code on success
    $bolretcode = ( $intdbconnid != 0 );
  }
  
  return $bolretcode;
}

// **********************************************************************
function db_unsetdb ( &$intdbconnid, &$strdberror )
// checks/closes database connection
// updates parameter intdbconnid and strdberror
// returns true if database had already been closed or has been closed successfully,
// otherwise returns false and sets strdberror
{
  $bolretcode = true;

  if ( $intdbconnid != 0 )
  {
    // database is still opened -> close it
    switch ( constant("SERVER_DB_TYPE") )
    {
      case constant("DB_TYPE_POSTGRESQL"):
        $bolretcode = pg_close ( $intdbconnid );
        $strdberror = pg_last_error( $intdbconnid );
        break;

      case constant("DB_TYPE_ODBC"):
        $bolretcode = odbc_close ( $intdbconnid );
        $strdberror = odbc_errormsg( $intdbconnid );
        break;

      default:
        // SERVER_DB_TYPE not supported!
        $bolretcode = false;
        $strdberror = 'Sorry, database of type "' . constant("SERVER_DB_TYPE") . '" is not supported.';
        break;
    }
    
    // clear database connection id on success
    if ( $bolretcode )
    {
      $intdbconnid = 0;
    }
  }
  
  return $bolretcode;
}

// **********************************************************************
function db_sql ( &$intdbconnid, $strdbsql, &$intdbresult, &$lngdbrows, &$strdberror )
// executes SQL statement on given database connection
// updates parameter intdbconnid, intdbresult, lngdbrows and strdberror
// returns true and sets intdbresult and lngdbrows if SQL statement had been executed successfully,
// otherwise returns false and sets strdberror
{
  $bolretcode = false;
  $intdbresult = 0;
  $lngdbrows = 0;
  $strdberror = '';

  if ( db_setdb( $intdbconnid, $strdberror ))
  {
    // database is ok
    
    switch ( constant("SERVER_DB_TYPE") )
    {
      case constant("DB_TYPE_POSTGRESQL"):
        $intdbresult = pg_query ( $intdbconnid, $strdbsql );
        $strdberror = pg_last_error( $intdbconnid );
        if ( $intdbresult != 0 )
        {
          $lngdbrows = pg_num_rows( $intdbresult ) + pg_affected_rows( $intdbresult );
          $bolretcode = true;
        }
        break;

      case constant("DB_TYPE_ODBC"):
        $intdbresult = odbc_exec ( $intdbconnid, $strdbsql );
        $strdberror = odbc_errormsg( $intdbconnid );
        if ( $intdbresult != 0 )
        {
          // Attention: odbc_num_rows may return wrong record count in SELECT-statements!
          // to retrieve exact number of records in SELECT statements you should walk through the recordset
          $lngdbrows = odbc_num_rows( $intdbresult );
          $bolretcode = true;
        }
        break;

      default:
        // SERVER_DB_TYPE not supported!
        $bolretcode = false;
        $strdberror = 'Sorry, database of type "' . constant("SERVER_DB_TYPE") . '" is not supported.';
        break;
    }
  }

  return $bolretcode;
}

// **********************************************************************
function db_row ( $intdbresult, &$strdbrow, $intdbrow, &$strdberror )
// fetches row on position intdbrow from intdbresult as associative array into strdbrow
// position intdbrow may be DB_RESULT_NEXTROW to fetch the next row of intdbresult
// updates parameter strdbrow, strdberror
// returns true and sets strdbrow if row has been fetched successfully,
// otherwise returns false and sets strdberror
{
  $bolretcode = false;
  $strdbrow = '';
  $strdberror = '';

  if ( $intdbresult != 0 )
  {
    // resultset is ok
    
    switch ( constant("SERVER_DB_TYPE") )
    {
      case constant("DB_TYPE_POSTGRESQL"):
        switch ( $intdbrow )
        {
          case constant("DB_ROW_FIRST"):
            $bolretcode = ( $strdbrow = pg_fetch_assoc( $intdbresult, 0 ));
            break;

          case constant("DB_ROW_NEXT"):
            $bolretcode = ( $strdbrow = pg_fetch_assoc( $intdbresult ));
            break;

          case constant("DB_ROW_LAST"):
            $bolretcode = ( $strdbrow = pg_fetch_assoc( $intdbresult, pg_num_rows( $intdbresult ) - 1 ));
            break;

          default:
            // specific $intdbrow given -> use it directly, offer start at position 1
            $bolretcode = ( $strdbrow = pg_fetch_assoc( $intdbresult, $intdbrow - 1 ));
            break;
        }
        $strdberror = pg_result_error( $intdbresult );
        break;

      case constant("DB_TYPE_ODBC"):
        switch ( $intdbrow )
        {
          case constant("DB_ROW_FIRST"):
            $bolretcode = ( $strdbrow = odbc_fetch_array( $intdbresult, 0 ));
            break;

          case constant("DB_ROW_NEXT"):
            $bolretcode = ( $strdbrow = odbc_fetch_array( $intdbresult ));
            break;

          case constant("DB_ROW_LAST"):
            // Attention: odbc_num_rows may return wrong record count in SELECT-statements!
            // to retrieve exact number of records in SELECT statements you should walk through the recordset
            $bolretcode = ( $strdbrow = odbc_fetch_array( $intdbresult, odbc_num_rows( $intdbresult ) - 1 ));
            break;

          default:
            // specific $intdbrow given -> use it directly, offer start at position 1
            $bolretcode = ( $strdbrow = odbc_fetch_array( $intdbresult, $intdbrow - 1));
            break;
        }
        // ODBC does not support error messages on resultset level
        if ( !$bolretcode )
        {
          $strdberror = 'Sorry, could not read result row on position ' . $intdbrow ;
        }
        break;

      default:
        // SERVER_DB_TYPE not supported!
        $bolretcode = false;
        $strdberror = 'Sorry, database of type "' . constant("SERVER_DB_TYPE") . '" is not supported.';
        break;
    }
  }
  else
  {
    $bolretcode = false;
    $strdberror = 'Sorry, no data available to read.';
  }

  return $bolretcode;
}


// **********************************************************************
function db_fields ( $intdbresult, &$strdbfields, &$strdberror )
// returns list of field names, field type and field length from intdbresult as associative arrays into strdbfields
// strdbfields will contain following partial arrays: DB_FIELD_NAME, DB_FIELD_TYPE, DB_FIELD_LEN
// use as following: $strdbfields[constant("DB_FIELD_*")][$intdbfield or $strdbfieldname]
// example: $intdbfieldtype[$strdbfieldname] = $strdbfields[constant("DB_FIELD_TYPE")][$strdbfieldname]
// updates parameter strdbfields, strdberror
// returns true and sets strdbfields if field names have been retrieved successfully,
// otherwise returns false and sets strdberror
{
  $bolretcode = false;
  $strdbfields = array (constant("DB_FIELD_NAME") => array(),
                        constant("DB_FIELD_TYPE") => array(),
                        constant("DB_FIELD_LEN") => array());
  $strdberror = '';
  $intindex = 0;

  if ( $intdbresult != 0 )
  {
    // resultset is ok

    //no errors expected at all
    $bolretcode = true;

    switch ( constant("SERVER_DB_TYPE") )
    {
      case constant("DB_TYPE_POSTGRESQL"):
        // field numeration in PostgreSQL starts with 0
        $intindex = 0;
        while ( $intindex < pg_num_fields( $intdbresult ))
        {
          array_push ( $strdbfields[constant("DB_FIELD_NAME")], pg_field_name( $intdbresult, $intindex ));
          array_push ( $strdbfields[constant("DB_FIELD_TYPE")], pg_field_type( $intdbresult, $intindex ));
          array_push ( $strdbfields[constant("DB_FIELD_LEN")], pg_field_prtlen( $intdbresult, $intindex ));
          $intindex++;
        }
        $strdberror = pg_result_error( $intdbresult );
        break;

      case constant("DB_TYPE_ODBC"):
        // field numeration in ODBC starts with 1
        $intindex = 1;
        while ( $intindex <= odbc_num_fields( $intdbresult ))
        {
          $strfieldname = odbc_field_name( $intdbresult, $intindex );
          array_push ( $strdbfields[constant("DB_FIELD_NAME")], $strfieldname );
          array_push ( $strdbfields[constant("DB_FIELD_TYPE")], odbc_field_type( $intdbresult, $intindex ));
          array_push ( $strdbfields[constant("DB_FIELD_LEN")], odbc_field_len( $intdbresult, $intindex ));
          $intindex++;
        }
        // ODBC does not support error messages on resultset level
        if ( !$bolretcode )
        {
          $strdberror = 'Sorry, could not read field names.';
        }
        break;

      default:
        // SERVER_DB_TYPE not supported!
        $bolretcode = false;
        $strdberror = 'Sorry, database of type "' . constant("SERVER_DB_TYPE") . '" is not supported.';
        break;
    }
  }
  else
  {
    $bolretcode = false;
    $strdberror = 'Sorry, no data available to read.';
  }

  return $bolretcode;
}

?>