Skip to content

Commit

Permalink
Support for Oracle ETL DataEndpoint (readonly) (#34)
Browse files Browse the repository at this point in the history
* Prepare for adding Oracle support
- More flexible database parameters in .ini file (not all databases require all parameters)
- Improve handling of DB Engine subclasses (e.g., PostgresDB, MySQLDB)
- Improved parameter validation on a per-engine basis
- Added methods to iDatabase that should have been there before
- General code cleanup
- Improve comments

* Allow methods to override default schema name when checking if schema or table exists

* Add support for ORDER BY in ETL queries

* Improve debug readability and error messages

* Add modern PDO support for Oracle (PDOOCI wraps the OCI8 library)

* Support for Oracle ETL data endpoint (readonly)

* Style fixes as per phpcs

* Address style and unit test errors, rmove underscores from private class members
  • Loading branch information
smgallo authored Jan 23, 2017
1 parent 90a7ce4 commit feddfeb
Show file tree
Hide file tree
Showing 20 changed files with 1,441 additions and 715 deletions.
146 changes: 85 additions & 61 deletions classes/CCR/DB.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,75 +19,99 @@
namespace CCR;

use xd_utilities;
use \Exception;
use Exception;
use CCR\DB\iDatabase;

class DB
{
// An array (pool) of database connection handles, one per configuration file section

private static $instancePool = array();

// Ensure that this class is a singleton

private function __construct() {}

// ================================================================================
// Cleanup
// ================================================================================

public function __destruct() {}

// ================================================================================
// Create an instance of the database singleton. A single argument is
// required, which is configuration file section identifier (e.g. [datawarehouse]).
// The database connection parameters in that section will be used to create the
// instance, which will be cached for re-use by subsequent requests targeting the
// same section.
//
// @throws Exception if there is an invalid number of arguments
//
// @returns An instance of the database class
// ================================================================================

public static function factory($section, $autoConnect = true)
{
// If this section has been used before in creating a database instance (handle), then
// it will have been cached. In this case, the cached handle will be returned.

if ( array_key_exists($section, self::$instancePool) ) {
return self::$instancePool[$section];
}

$engine = xd_utilities\getConfiguration($section, 'db_engine');
$host = xd_utilities\getConfiguration($section, 'host');
$database = xd_utilities\getConfiguration($section, 'database');
$user = xd_utilities\getConfiguration($section, 'user');
$passwd = xd_utilities\getConfiguration($section, 'pass');
$port = xd_utilities\getConfiguration($section, 'port');

$engine = "CCR\\DB\\$engine";

// Ensure that the class exists before we attempt to instantiate it

if ( class_exists($engine) ) {
$db = new $engine($host, $port, $database, $user, $passwd);
} else {
$msg = "Error creating database '" . $options->name . "', class '$className' not found";
throw new Exception($msg);
// An array (pool) of database connection handles, one per configuration file section

private static $instancePool = array();

// Ensure that this class is a singleton

private function __construct()
{
}

// All database interfaces must implement iDatabase
// ================================================================================
// Cleanup
// ================================================================================

if ( ! $db instanceof iDatabase ) {
throw new Exception("$engine does not implenment interface iDatabase");
public function __destruct()
{
}

self::$instancePool[$section] = $db;
if ($autoConnect) self::$instancePool[$section]->connect();

return self::$instancePool[$section];

} // factory()
// ================================================================================
// Create an instance of the database singleton. A single argument is
// required, which is configuration file section identifier (e.g. [datawarehouse]).
// The database connection parameters in that section will be used to create the
// instance, which will be cached for re-use by subsequent requests targeting the
// same section.
//
// @param $sectionName Name of the configuration section containing database parameters
// @param $autoConnect If TRUE, connect to the database after creating the object
//
// @throws Exception if there is an invalid number of arguments
//
// @returns An instance of the database class
// ================================================================================

public static function factory($sectionName, $autoConnect = true)
{
// If this section has been used before in creating a database instance (handle), then
// it will have been cached. In this case, the cached handle will be returned.

if ( array_key_exists($sectionName, self::$instancePool) ) {
return self::$instancePool[$sectionName];
}

try {
$iniSection = xd_utilities\getConfigurationSection($sectionName, 'db_engine');
} catch (Exception $e) {
$msg = "Unable to get database configuration options: " . $e->getMessage();
throw new Exception($msg);
}

// Not all engines are required to specify all configuration options (e.g., Oracle) so
// allow NULL (empty) options. Specific engines may enforce additional requirements.

$engine = ( array_key_exists('db_engine', $iniSection) ? $iniSection['db_engine'] : null);
$database = ( array_key_exists('database', $iniSection) ? $iniSection['database'] : null);
$user = ( array_key_exists('user', $iniSection) ? $iniSection['user'] : null );

if ( null === $engine || null === $database || null === $user ) {
$msg = "Configuration section '$sectionName' missing required options (db_engine, database, user)";
throw new Exception($msg);
}

$password = ( array_key_exists('pass', $iniSection) ? $iniSection['pass'] : null );
$host = ( array_key_exists('host', $iniSection) ? $iniSection['host'] : null );
$port = ( array_key_exists('port', $iniSection) ? $iniSection['port'] : null );

$engine = "CCR\\DB\\$engine";

// Ensure that the class exists before we attempt to instantiate it

if ( class_exists($engine) ) {
$db = new $engine($host, $port, $database, $user, $password);
} else {
$msg = "Error creating database defined in section '$sectionName', class '$engine' not found";
throw new Exception($msg);
}

// All database interfaces must implement iDatabase

if ( ! $db instanceof iDatabase ) {
throw new Exception("$engine does not implenment interface iDatabase");
}

self::$instancePool[$sectionName] = $db;
if ($autoConnect) {
self::$instancePool[$sectionName]->connect();
}

return self::$instancePool[$sectionName];

} // factory()
} // class DB
32 changes: 17 additions & 15 deletions classes/CCR/DB/MySQLDB.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php
/*
/*
* @author Amin Ghadersohi
* @date 2010-Jul-07
*
* The top interface for mysql dbs using pdo driver
*
*
* Changelog
*
* 2015-12-15 Steve Gallo <smgallo@buffalo.edu>
Expand All @@ -13,18 +13,20 @@

namespace CCR\DB;

class MySQLDB
extends PDODB
implements iDatabase
use Exception;

class MySQLDB extends PDODB implements iDatabase
{
function __construct($db_host,$db_port,$db_name,$db_username,$db_password)
{
$dsn = 'mysql:host=' . $db_host . ';port=' . $db_port . ';dbname=' . $db_name . ';charset=utf8';
parent::__construct("mysql",$db_host,$db_port,$db_name,$db_username,$db_password, $dsn);
}
function __destruct()
{
parent::__destruct();
}
// ------------------------------------------------------------------------------------------
// @see iDatabase::__construct()
// ------------------------------------------------------------------------------------------

}
public function __construct($db_host, $db_port, $db_name, $db_username, $db_password, $dsn_extra = null)
{
if ( null == $db_host || null === $db_name || null === $db_username ) {
$msg = "Database engine " . __CLASS__ . " requires (host, database, username)";
throw new Exception($msg);
}
parent::__construct("mysql", $db_host, $db_port, $db_name, $db_username, $db_password, 'charset=utf8');
}
} // class MySQLDB
20 changes: 18 additions & 2 deletions classes/CCR/DB/NullDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@

class NullDB implements iDatabase
{
public function __construct()
{
}

public function __destruct()
{
}

public function connect()
{
}

public function destroy()
public function disconnect()
{
}

Expand All @@ -27,6 +35,10 @@ public function insert($statement, $params = array())
return 0;
}

public function handle()
{
}

public function query(
$query,
array $params = array(),
Expand All @@ -40,9 +52,13 @@ public function execute($query, array $params = array())
return 0;
}

public function getRowCount($schema, $table)
{
}

public function prepare($query)
{
return FALSE;
return false;
}

public function beginTransaction()
Expand Down
136 changes: 119 additions & 17 deletions classes/CCR/DB/OracleDB.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,124 @@
<?php
/*
* @author Amin Ghadersohi
* @date 2013-Jul-08
*
* The top interface for oracle dbs using pdo driver
*
*/
/* ==========================================================================================
* XDMoD driver for accessing the Oracle database. PHP built-in support for Oracle (OCI8)
* must be built against Oracle client libraries. The PDO_OCI PECL module is deprecated
* and internal PDO_OCI support must be compiled from source (and is also out of date) so
* we are using the PDOOCI wrapper from https://github.com/taq/pdooci.
*
* Support for both Easy Connect Naming and Local Naming (via tnsnames.ora) is
* supported. Note that in order to use Local Naming, the TNS_ADMIN environment variable
* must be set (this is used by the OCI driver).
*
* @author Steve Gallo
* @date 2017-01-17
* ==========================================================================================
*/

namespace CCR\DB;

use Exception;

class OracleDB extends PDODB
{
function __construct($db_host,$db_port,$db_name,$db_username,$db_password)
{
parent::__construct("oci",$db_host,$db_port,$db_name,$db_username,$db_password, "oci:dbname=$db_name");
}
function __destruct()
{
parent::__destruct();
}
}
?>
/* ------------------------------------------------------------------------------------------
* Set up the machinery. Oracle requires at minimum a database name (local naming,
* this resolves to an entry in tnsnames.org) or a name, host, and optionally a port
* (easy connect naming).
*
* Note: We do not support the $dsn_override that PDODB supports.
*
* @see PDODB::__construct()
*
* @throw Exception If minimum parameters were not provided.
*
* @see iDatabase::__construct()
* ------------------------------------------------------------------------------------------
*/

public function __construct($db_host, $db_port, $db_name, $db_username, $db_password, $dsn_extra = null)
{
// At a minimum we must have either the (db_name) or the (db_host, db_name)

if ( null === $db_name ) {
$msg = __CLASS__
. ' requires at a minimum (db_name) for Local Naming '
. ' or (db_host, db_name[, db_port]) for Easy Connect Naming';
throw new Exception($msg);
}

parent::__construct(
"oci",
$db_host,
$db_port,
$db_name,
$db_username,
$db_password
);

} // __construct()

/* ------------------------------------------------------------------------------------------
* Clean up after ourselves and close the connection. Unlike standard PDO setting the
* connection to NULL OCI has an oci_close() function.
* ------------------------------------------------------------------------------------------
*/

public function __destruct()
{
if ( null !== $this->_dbh) {
$this->_dbh->close();
}
} // __destruct()

/* ------------------------------------------------------------------------------------------
* Connect to the server.
*
* @return The database connection handle
*
* @throw Exception If there was a connection error
* ------------------------------------------------------------------------------------------
*/

public function connect()
{
if ( null !== $this->_dbh ) {
return $this->_dbh;
}

$namingMethod = null;

// If the host is not set then assume we are using Easy Connect
// https://docs.oracle.com/database/121/NETAG/naming.htm#NETAG008
// For example oci:dbname=//localhost:1521/mydb
//
// Otherwise assume Local Naming (via tnsnames.ora)
// https://docs.oracle.com/database/121/NETAG/naming.htm#NETAG081
// For example oci:dbname=mydb

if ( null === $this->_db_host ) {
$this->_dsn = 'oci:dbname=' . $this->_db_name;
$namingMethod = "Local";
} else {
$this->_dsn = 'oci:dbname=//'
. $this->_db_host
. ( null !== $this->_db_port ? ':' . $this->_db_port : '' )
. '/' . $this->_db_name;
$namingMethod = "Easy Connect";
}


try {
$this->_dbh = @new \PDOOCI\PDO($this->_dsn, $this->_db_username, $this->_db_password);
$this->_dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

} catch (\PDOException $e) {
$msg = __CLASS__
. " Error connecting to database '" . $this->_dsn . "' using $namingMethod Naming. "
. $e->getMessage();
throw new Exception($msg);
}

return $this->_dbh;

} // connect()
} // class OracleDB
Loading

0 comments on commit feddfeb

Please sign in to comment.