New onsite patient portal, take 4.
[openemr.git] / portal / patient / fwk / libs / verysimple / Phreeze / DataAdapter.php
blob250fb43f87cbb9c8814492c2d5c810fd20be6579
1 <?php
2 /** @package verysimple::Phreeze */
4 /**
5 * import supporting libraries
6 */
7 require_once ("IObservable.php");
8 require_once ("ConnectionSetting.php");
9 require_once ("verysimple/DB/DataDriver/IDataDriver.php");
11 /**
12 * DataAdapter abstracts and provides access to the data store
14 * @package verysimple::Phreeze
15 * @author VerySimple Inc. <noreply@verysimple.com>
16 * @copyright 1997-2005 VerySimple Inc.
17 * @license http://www.gnu.org/licenses/lgpl.html LGPL
18 * @version 2.2
20 class DataAdapter implements IObservable {
22 /**
24 * @var ConnectionSetting
26 public $ConnectionSetting;
27 private $_observers = Array ();
28 private $_dbconn;
29 private $_dbopen;
30 private $_driver;
31 private $_label;
32 private $_transactionInProgress;
33 private $_masterAdapter;
35 /** @var used internally to keep track of communication error re-tries */
36 private $_num_retries = 0;
38 /** @var static singleton instance of the data adapter */
39 static $ADAPTER_INSTANCE = null;
41 /** @var instance of the driver class, used for escaping */
42 static $DRIVER_INSTANCE = null;
44 /** @var bool if true the data adapter attempt one retry when a communication error occurs */
45 static $RETRY_ON_COMMUNICATION_ERROR = false;
47 /**
48 * Contructor initializes the object
50 * @access public
51 * @param ConnectionSetting $csetting
52 * @param Observable $listener
53 * @param
54 * IDataDriver (optional) if not provided, then DataAdapter will attempt to instantiate one based on ConnectionSetting->Type
55 * @param
56 * string (optional) a label for the DataAdapter used in debug messages (if empty a random label will be generated)
58 function __construct($csetting, $listener = null, IDataDriver $driver = null, $label = null) {
59 $this->_driver = $driver;
60 if ($this->_driver)
61 DataAdapter::$DRIVER_INSTANCE = $this->_driver;
63 $this->_label = $label ? $label : 'db-' . mt_rand ( 10000, 99999 );
65 $this->ConnectionSetting = & $csetting;
67 if ($listener)
68 $this->AttachObserver ( $listener );
69 $this->Observe ( "DataAdapter ($this->_label) Instantiated", OBSERVE_DEBUG );
71 // set the singleton reference
72 DataAdapter::$ADAPTER_INSTANCE = $this;
75 /**
76 * Destructor closes the db connection.
78 * @access public
80 function __destruct() {
81 $this->Observe ( "DataAdapter ($this->_label) Destructor Firing...", OBSERVE_DEBUG );
82 $this->Close ();
85 /**
86 * Load the data driver
88 * @throws Exception
90 public function LoadDriver() {
91 if ($this->_driver == null) {
92 require_once ("verysimple/IO/Includer.php");
94 // the driver was not explicitly provided so we will try to create one from
95 // the connection setting based on the database types that we do know about
96 switch ($this->ConnectionSetting->Type) {
97 case "mysql" :
98 include_once ("verysimple/DB/DataDriver/MySQL.php");
99 $this->_driver = new DataDriverMySQL ();
100 break;
101 case "mysqli" :
102 include_once ("verysimple/DB/DataDriver/MySQLi.php");
103 $this->_driver = new DataDriverMySQLi ();
104 break;
105 case "sqlite" :
106 include_once ("verysimple/DB/DataDriver/SQLite.php");
107 $this->_driver = new DataDriverSQLite ();
108 break;
109 default :
110 try {
111 Includer::IncludeFile ( "verysimple/DB/DataDriver/" . $this->ConnectionSetting->Type . ".php" );
112 $classname = "DataDriver" . $this->ConnectionSetting->Type;
113 $this->_driver = new $classname ();
114 } catch ( IncludeException $ex ) {
115 throw new Exception ( 'Unknown DataDriver "' . $this->ConnectionSetting->Type . '" specified in connection settings' );
117 break;
120 DataAdapter::$DRIVER_INSTANCE = $this->_driver;
125 * Returns name of the DB currently in use
127 * @access public
128 * @return string
130 function GetDBName() {
131 return $this->ConnectionSetting->DBName;
135 * Opens a connection to the data server and selects the specified database
137 * @access public
139 function Open() {
140 $this->Observe ( "DataAdapter ($this->_label) Opening Connection...", OBSERVE_DEBUG );
142 if ($this->_dbopen) {
143 $this->Observe ( "DataAdapter ($this->_label) Connection Already Open", OBSERVE_WARN );
144 } else {
145 if (! $this->_driver)
146 $this->LoadDriver ();
148 try {
149 $this->_dbconn = $this->_driver->Open ( $this->ConnectionSetting->ConnectionString, $this->ConnectionSetting->DBName, $this->ConnectionSetting->Username, $this->ConnectionSetting->Password, $this->ConnectionSetting->Charset, $this->ConnectionSetting->BootstrapSQL );
151 $this->_num_retries = 0;
152 } catch ( Exception $ex ) {
153 // retry one time a communication error occurs
154 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError ( $ex )) {
155 $this->_num_retries ++;
156 $this->Observe ( "DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN );
157 sleep ( 2 ); // slight delay to prevent throttling
158 return $this->Open ();
161 $msg = "DataAdapter ($this->_label) Error Opening DB: " . $ex->getMessage () . ' (retry attempts: ' . $this->_num_retries . ')';
163 $this->Observe ( $msg, OBSERVE_FATAL );
164 throw new Exception ( $msg, $ex->getCode () );
167 $this->_dbopen = true;
168 $this->Observe ( "DataAdapter ($this->_label) Connection Open", OBSERVE_DEBUG );
173 * Closing the connection to the data Server
175 * @access public
177 function Close() {
178 $this->Observe ( "DataAdapter ($this->_label) Closing Connection...", OBSERVE_DEBUG );
180 if ($this->_dbopen) {
181 $this->_driver->Close ( $this->_dbconn ); // ignore warnings
182 $this->_dbopen = false;
183 $this->Observe ( "DataAdapter ($this->_label) Connection Closed", OBSERVE_DEBUG );
184 } else {
185 $this->Observe ( "DataAdapter ($this->_label) Connection Not Open", OBSERVE_DEBUG );
190 * Checks that the connection is open and if not, crashes
192 * @access public
193 * @param bool $auto
194 * Automatically try to connect if connection isn't already open
196 private function RequireConnection($auto = false) {
197 if ($this->_dbopen) {
198 // $this->_driver->Ping($this->_dbconn);
199 } else {
200 if ($auto) {
201 $this->Open ();
202 } else {
203 $this->Observe ( "DataAdapter ($this->_label) DB is not connected. Please call DBConnection->Open() first.", OBSERVE_FATAL );
204 throw new Exception ( "DataAdapter ($this->_label) DB is not connected. Please call DBConnection->Open() first." );
210 * Executes a SQL select statement and returns a resultset that can be read
211 * using Fetch
213 * @access public
214 * @param string $sql
215 * @return resultset (dependent on the type of driver used)
217 function Select($sql) {
218 $this->RequireConnection ( true );
219 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.Select) " . $sql, OBSERVE_QUERY );
221 try {
222 $rs = $this->_driver->Query ( $this->_dbconn, $sql );
223 $this->_num_retries = 0;
224 } catch ( Exception $ex ) {
225 // retry one time a communication error occurs
226 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError ( $ex )) {
227 $this->_num_retries ++;
228 $this->Observe ( "DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN );
229 sleep ( 2 ); // slight delay to prevent throttling
230 return $this->Select ( $sql );
233 $msg = "DataAdapter ($this->_label)" . ' Error Selecting SQL: ' . $ex->getMessage () . ' (retry attempts: ' . $this->_num_retries . ')';
235 $this->Observe ( $msg, OBSERVE_FATAL );
236 throw new Exception ( $msg, $ex->getCode () );
239 return $rs;
243 * Executes a SQL query that does not return a resultset
245 * @access public
246 * @param string $sql
247 * @return int number of records affected
249 function Execute($sql) {
250 $result = null;
252 if ($this->ConnectionSetting->IsReadOnlySlave) {
254 // this is a read-only slave connection attempting a write operation. we
255 // will only proceed if the connection specifies a "master" delegate connection
256 if (! $this->_masterAdapter) {
258 if ($this->ConnectionSetting->MasterConnectionDelegate) {
260 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.Execute) Delegating write operation from Slave to Master Connection", OBSERVE_INFO );
262 $this->_masterAdapter = new DataAdapter ( $this->ConnectionSetting->MasterConnectionDelegate );
264 foreach ( $this->_observers as $observer ) {
265 $this->_masterAdapter->AttachObserver ( $observer );
267 } else {
268 throw new Exception ( 'DB Write operation was attempted on a read-only slave connection' );
272 // we have a master connection initialized and ready to use
273 $result = $this->_masterAdapter->Execute ( $sql );
274 } else {
275 $this->RequireConnection ( true );
276 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.Execute) " . $sql, OBSERVE_QUERY );
277 $result = - 1;
279 try {
280 $result = $this->_driver->Execute ( $this->_dbconn, $sql );
281 $this->_num_retries = 0;
282 } catch ( Exception $ex ) {
283 // retry one time a communication error occurs
284 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError ( $ex )) {
285 $this->_num_retries ++;
286 $this->Observe ( "DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN );
287 sleep ( 2 ); // slight delay to prevent throttling
288 return $this->Execute ( $sql );
291 $msg = "DataAdapter ($this->_label)" . ' Error Executing SQL: ' . $ex->getMessage () . ' (retry attempts: ' . $this->_num_retries . ')';
293 $this->Observe ( $msg, OBSERVE_FATAL );
294 throw new Exception ( $msg, $ex->getCode () );
297 return $result;
301 * Return true if a transaction is in progress
303 * @return boolean
305 function IsTransactionInProgress() {
306 return $this->_transactionInProgress;
310 * Start a DB transaction, disabling auto-commit if necessar)
312 * @access public
314 function StartTransaction() {
315 if ($this->IsTransactionInProgress ())
316 throw new Exception ( 'Transaction is already in progress. Commit or rollback must be called before beginning a new transaction' );
318 if ($this->ConnectionSetting->IsReadOnlySlave)
319 throw new Exception ( 'Transactions are not allowed on a read-only slave' );
321 $this->RequireConnection ( true );
322 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.StartTransaction)", OBSERVE_QUERY );
323 $this->_transactionInProgress = true;
324 return $this->_driver->StartTransaction ( $this->_dbconn );
328 * Commit the current DB transaction and re-enable auto-commit if necessary
330 * @access public
332 function CommitTransaction() {
333 if ($this->ConnectionSetting->IsReadOnlySlave)
334 throw new Exception ( 'Transactions are not allowed on a read-only slave' );
336 $this->RequireConnection ( true );
337 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.CommitTransaction)", OBSERVE_QUERY );
338 $this->_transactionInProgress = false;
339 return $this->_driver->CommitTransaction ( $this->_dbconn );
343 * Rollback the current DB transaction and re-enable auto-commit if necessary
345 * @access public
347 function RollbackTransaction() {
348 if ($this->ConnectionSetting->IsReadOnlySlave)
349 throw new Exception ( 'Transactions are not allowed on a read-only slave' );
351 $this->RequireConnection ( true );
352 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.RollbackTransaction)", OBSERVE_QUERY );
353 $this->_transactionInProgress = false;
354 return $this->_driver->RollbackTransaction ( $this->_dbconn );
358 * Return true if the error with the given message is a communication/network error
360 * @param
361 * variant string or Exception $msg
362 * @return bool
364 public function IsCommunicationError($error) {
365 $msg = is_a ( $error, 'Exception' ) ? $error->getMessage () : $error;
366 return strpos ( strtolower ( $msg ), 'lost connection' ) !== false;
370 * Returns an array of all table names in the current database
372 * @param
373 * bool true to ommit tables that are empty (default = false)
374 * @return array
376 public function GetTableNames($ommitEmptyTables = false) {
377 return $this->_driver->GetTableName ( $this->_dbconn, $this->GetDBName (), $ommitEmptyTables );
381 * Runs OPTIMIZE TABLE on all tables in the current database
383 * @return array results for each table
385 public function OptimizeTables() {
386 if ($this->ConnectionSetting->IsReadOnlySlave)
387 throw new Exception ( 'Optimizing tables is allowed on a read-only slave' );
389 $results = array ();
390 $table_names = $this->_driver->GetTableNames ( $this->_dbconn, $this->GetDBName () );
392 foreach ( $table_names as $table_name ) {
393 $results [$table_name] = $this->_driver->Optimize ( $this->_dbconn, $table_name );
396 return $results;
399 * Returns last auto-inserted Id.
400 * If this is a read-only slave and a write operation was made to a Master Delegate
401 * then the last insert id from that connection will be returned
403 * @access public
404 * @return int
406 function GetLastInsertId() {
407 $id = null;
409 if ($this->ConnectionSetting->IsReadOnlySlave && $this->_masterAdapter) {
410 $id = $this->_masterAdapter->GetLastInsertId ();
411 } else {
412 $this->RequireConnection ();
413 $this->Observe ( "DataAdapter ($this->_label) GetLastInsertId", OBSERVE_QUERY );
414 $id = $this->_driver->GetLastInsertId ( $this->_dbconn );
417 return $id;
421 * Moves the database curser forward and returns the current row as an associative array
422 * the resultset passed in must have been created by the same database driver that
423 * was connected when Select was called
425 * @access public
426 * @param resultset $rs
427 * @return Array
429 function Fetch($rs) {
430 $this->RequireConnection ();
432 $this->Observe ( "DataAdapter ($this->_label) Fetching next result as array", OBSERVE_DEBUG );
433 return $this->_driver->Fetch ( $this->_dbconn, $rs );
437 * Releases the resources for the given resultset.
438 * the resultset must have
439 * been created by the same database driver
441 * @access public
442 * @param resultset $rs
444 function Release($rs) {
445 $this->RequireConnection ();
447 $this->Observe ( "DataAdapter ($this->_label) Releasing result resources", OBSERVE_DEBUG );
448 $this->_driver->Release ( $this->_dbconn, $rs );
452 * Removes any illegal chars from a value to prepare it for use in SQL
454 * @access public
455 * @param string $val
456 * @return string
458 public static function Escape($val) {
459 if (DataAdapter::$ADAPTER_INSTANCE)
460 DataAdapter::$ADAPTER_INSTANCE->LoadDriver ();
462 // this is an unfortunate leftover from poor design of making this function static
463 // we cannon use the driver's escape method without a static reference
464 if (! DataAdapter::$DRIVER_INSTANCE)
465 throw new Exception ( "DataAdapter must be instantiated before Escape can be called" );
467 // if magic quotes are enabled, then we need to stip the slashes that php added
468 if (get_magic_quotes_runtime () || get_magic_quotes_gpc ())
469 $val = stripslashes ( $val );
471 // $driver->RequireConnection(true);
472 return DataAdapter::$DRIVER_INSTANCE->Escape ( $val );
476 * Quote and escape value to prepare it for use in SQL
478 * @access public
479 * @param string $val
480 * @return string
482 public static function GetQuotedSql($val) {
483 if (DataAdapter::$ADAPTER_INSTANCE)
484 DataAdapter::$ADAPTER_INSTANCE->LoadDriver ();
486 // this is an unfortunate leftover from poor design of making this function static
487 // we cannon use the driver's escape method without a static reference
488 if (! DataAdapter::$DRIVER_INSTANCE)
489 throw new Exception ( "DataAdapter must be instantiated before Escape can be called" );
491 // $driver->RequireConnection(true);
492 return DataAdapter::$DRIVER_INSTANCE->GetQuotedSql ( $val );
496 * Registers/attaches an IObserver to this object
498 * @access public
499 * @param IObserver $observer
501 public function AttachObserver($listener) {
502 if ($listener) {
503 $this->_observers [] = & $listener;
504 if ($this->_masterAdapter)
505 $this->_masterAdapter->AttachObserver ( $listener );
510 * Fires the Observe event on all registered observers
512 * @access public
513 * @param variant $obj
514 * the $obj or message that you want to log/listen to, etc.
515 * @param int $ltype
516 * the type/level
518 public function Observe($obj, $ltype = OBSERVE_INFO) {
519 foreach ( $this->_observers as $observer )
520 @$observer->Observe ( $obj, $ltype );