psr12 fixes for new PHP_CodeSniffer (#4795)
[openemr.git] / portal / patient / fwk / libs / verysimple / Phreeze / DataAdapter.php
blob108e04e83d3373464d29b8ce2944c4a78a4db6a5
1 <?php
3 /** @package verysimple::Phreeze */
5 /**
6 * import supporting libraries
7 */
8 require_once("IObservable.php");
9 require_once("ConnectionSetting.php");
10 require_once("verysimple/DB/DataDriver/IDataDriver.php");
12 /**
13 * DataAdapter abstracts and provides access to the data store
15 * @package verysimple::Phreeze
16 * @author VerySimple Inc. <noreply@verysimple.com>
17 * @copyright 1997-2005 VerySimple Inc.
18 * @license http://www.gnu.org/licenses/lgpl.html LGPL
19 * @version 2.2
21 class DataAdapter implements IObservable
23 /**
25 * @var ConnectionSetting
27 public $ConnectionSetting;
28 private $_observers = array ();
29 private $_dbconn;
30 private $_dbopen;
31 private $_driver;
32 private $_label;
33 private $_transactionInProgress;
34 private $_masterAdapter;
36 /** @var used internally to keep track of communication error re-tries */
37 private $_num_retries = 0;
39 /** @var static singleton instance of the data adapter */
40 static $ADAPTER_INSTANCE = null;
42 /** @var instance of the driver class, used for escaping */
43 static $DRIVER_INSTANCE = null;
45 /** @var bool if true the data adapter attempt one retry when a communication error occurs */
46 static $RETRY_ON_COMMUNICATION_ERROR = false;
48 /**
49 * Contructor initializes the object
51 * @access public
52 * @param ConnectionSetting $csetting
53 * @param Observable $listener
54 * @param
55 * IDataDriver (optional) if not provided, then DataAdapter will attempt to instantiate one based on ConnectionSetting->Type
56 * @param
57 * string (optional) a label for the DataAdapter used in debug messages (if empty a random label will be generated)
59 function __construct($csetting, $listener = null, IDataDriver $driver = null, $label = null)
61 $this->_driver = $driver;
62 if ($this->_driver) {
63 DataAdapter::$DRIVER_INSTANCE = $this->_driver;
66 $this->_label = $label ? $label : 'db-' . mt_rand(10000, 99999);
68 $this->ConnectionSetting = & $csetting;
70 if ($listener) {
71 $this->AttachObserver($listener);
74 $this->Observe("DataAdapter ($this->_label) Instantiated", OBSERVE_DEBUG);
76 // set the singleton reference
77 DataAdapter::$ADAPTER_INSTANCE = $this;
80 /**
81 * Destructor closes the db connection.
83 * @access public
85 function __destruct()
87 $this->Observe("DataAdapter ($this->_label) Destructor Firing...", OBSERVE_DEBUG);
88 $this->Close();
91 /**
92 * Load the data driver
94 * @throws Exception
96 public function LoadDriver()
98 if ($this->_driver == null) {
99 require_once("verysimple/IO/Includer.php");
101 // the driver was not explicitly provided so we will try to create one from
102 // the connection setting based on the database types that we do know about
103 switch ($this->ConnectionSetting->Type) {
104 case "mysql":
105 include_once("verysimple/DB/DataDriver/MySQL.php");
106 $this->_driver = new DataDriverMySQL();
107 break;
108 case "mysqli":
109 include_once("verysimple/DB/DataDriver/MySQLi.php");
110 $this->_driver = new DataDriverMySQLi();
111 break;
112 case "sqlite":
113 include_once("verysimple/DB/DataDriver/SQLite.php");
114 $this->_driver = new DataDriverSQLite();
115 break;
116 default:
117 try {
118 Includer::IncludeFile("verysimple/DB/DataDriver/" . $this->ConnectionSetting->Type . ".php");
119 $classname = "DataDriver" . $this->ConnectionSetting->Type;
120 $this->_driver = new $classname();
121 } catch (IncludeException $ex) {
122 throw new Exception('Unknown DataDriver "' . $this->ConnectionSetting->Type . '" specified in connection settings');
124 break;
127 DataAdapter::$DRIVER_INSTANCE = $this->_driver;
132 * Returns name of the DB currently in use
134 * @access public
135 * @return string
137 function GetDBName()
139 return $this->ConnectionSetting->DBName;
143 * Opens a connection to the data server and selects the specified database
145 * @access public
147 function Open()
149 $this->Observe("DataAdapter ($this->_label) Opening Connection...", OBSERVE_DEBUG);
151 if ($this->_dbopen) {
152 $this->Observe("DataAdapter ($this->_label) Connection Already Open", OBSERVE_WARN);
153 } else {
154 if (! $this->_driver) {
155 $this->LoadDriver();
158 try {
159 $this->_dbconn = $this->_driver->Open($this->ConnectionSetting->ConnectionString, $this->ConnectionSetting->DBName, $this->ConnectionSetting->Username, $this->ConnectionSetting->Password, $this->ConnectionSetting->Charset, $this->ConnectionSetting->BootstrapSQL);
161 $this->_num_retries = 0;
162 } catch (Exception $ex) {
163 // retry one time a communication error occurs
164 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex)) {
165 $this->_num_retries ++;
166 $this->Observe("DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN);
167 sleep(2); // slight delay to prevent throttling
168 return $this->Open();
171 $msg = "DataAdapter ($this->_label) Error Opening DB: " . $ex->getMessage() . ' (retry attempts: ' . $this->_num_retries . ')';
173 $this->Observe($msg, OBSERVE_FATAL);
174 throw new Exception($msg, $ex->getCode());
177 $this->_dbopen = true;
178 $this->Observe("DataAdapter ($this->_label) Connection Open", OBSERVE_DEBUG);
183 * Closing the connection to the data Server
185 * @access public
187 function Close()
189 $this->Observe("DataAdapter ($this->_label) Closing Connection...", OBSERVE_DEBUG);
191 if ($this->_dbopen) {
192 $this->_driver->Close($this->_dbconn); // ignore warnings
193 $this->_dbopen = false;
194 $this->Observe("DataAdapter ($this->_label) Connection Closed", OBSERVE_DEBUG);
195 } else {
196 $this->Observe("DataAdapter ($this->_label) Connection Not Open", OBSERVE_DEBUG);
201 * Checks that the connection is open and if not, crashes
203 * @access public
204 * @param bool $auto
205 * Automatically try to connect if connection isn't already open
207 private function RequireConnection($auto = false)
209 if ($this->_dbopen) {
210 // $this->_driver->Ping($this->_dbconn);
211 } else {
212 if ($auto) {
213 $this->Open();
214 } else {
215 $this->Observe("DataAdapter ($this->_label) DB is not connected. Please call DBConnection->Open() first.", OBSERVE_FATAL);
216 throw new Exception("DataAdapter ($this->_label) DB is not connected. Please call DBConnection->Open() first.");
222 * Executes a SQL select statement and returns a resultset that can be read
223 * using Fetch
225 * @access public
226 * @param string $sql
227 * @return resultset (dependent on the type of driver used)
229 function Select($sql)
231 $this->RequireConnection(true);
232 $this->Observe("DataAdapter ($this->_label) (DataAdapter.Select) " . $sql, OBSERVE_QUERY);
234 try {
235 $rs = $this->_driver->Query($this->_dbconn, $sql);
236 $this->_num_retries = 0;
237 } catch (Exception $ex) {
238 // retry one time a communication error occurs
239 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex)) {
240 $this->_num_retries ++;
241 $this->Observe("DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN);
242 sleep(2); // slight delay to prevent throttling
243 return $this->Select($sql);
246 $msg = "DataAdapter ($this->_label)" . ' Error Selecting SQL: ' . $ex->getMessage() . ' (retry attempts: ' . $this->_num_retries . ')';
248 $this->Observe($msg, OBSERVE_FATAL);
249 throw new Exception($msg, $ex->getCode());
252 return $rs;
256 * Executes a SQL query that does not return a resultset
258 * @access public
259 * @param string $sql
260 * @return int number of records affected
262 function Execute($sql)
264 $result = null;
266 if ($this->ConnectionSetting->IsReadOnlySlave) {
267 // this is a read-only slave connection attempting a write operation. we
268 // will only proceed if the connection specifies a "master" delegate connection
269 if (! $this->_masterAdapter) {
270 if ($this->ConnectionSetting->MasterConnectionDelegate) {
271 $this->Observe("DataAdapter ($this->_label) (DataAdapter.Execute) Delegating write operation from Slave to Master Connection", OBSERVE_INFO);
273 $this->_masterAdapter = new DataAdapter($this->ConnectionSetting->MasterConnectionDelegate);
275 foreach ($this->_observers as $observer) {
276 $this->_masterAdapter->AttachObserver($observer);
278 } else {
279 throw new Exception('DB Write operation was attempted on a read-only slave connection');
283 // we have a master connection initialized and ready to use
284 $result = $this->_masterAdapter->Execute($sql);
285 } else {
286 $this->RequireConnection(true);
287 $this->Observe("DataAdapter ($this->_label) (DataAdapter.Execute) " . $sql, OBSERVE_QUERY);
288 $result = - 1;
290 try {
291 $result = $this->_driver->Execute($this->_dbconn, $sql);
292 $this->_num_retries = 0;
293 } catch (Exception $ex) {
294 // retry one time a communication error occurs
295 if ($this->_num_retries == 0 && DataAdapter::$RETRY_ON_COMMUNICATION_ERROR && $this->IsCommunicationError($ex)) {
296 $this->_num_retries ++;
297 $this->Observe("DataAdapter ($this->_label) Communication error. Retry attempt " . $this->_num_retries, OBSERVE_WARN);
298 sleep(2); // slight delay to prevent throttling
299 return $this->Execute($sql);
302 $msg = "DataAdapter ($this->_label)" . ' Error Executing SQL: ' . $ex->getMessage() . ' (retry attempts: ' . $this->_num_retries . ')';
304 $this->Observe($msg, OBSERVE_FATAL);
305 throw new Exception($msg, $ex->getCode());
309 return $result;
313 * Return true if a transaction is in progress
315 * @return boolean
317 function IsTransactionInProgress()
319 return $this->_transactionInProgress;
323 * Start a DB transaction, disabling auto-commit if necessar)
325 * @access public
327 function StartTransaction()
329 if ($this->IsTransactionInProgress()) {
330 throw new Exception('Transaction is already in progress. Commit or rollback must be called before beginning a new transaction');
333 if ($this->ConnectionSetting->IsReadOnlySlave) {
334 throw new Exception('Transactions are not allowed on a read-only slave');
337 $this->RequireConnection(true);
338 $this->Observe("DataAdapter ($this->_label) (DataAdapter.StartTransaction)", OBSERVE_QUERY);
339 $this->_transactionInProgress = true;
340 return $this->_driver->StartTransaction($this->_dbconn);
344 * Commit the current DB transaction and re-enable auto-commit if necessary
346 * @access public
348 function CommitTransaction()
350 if ($this->ConnectionSetting->IsReadOnlySlave) {
351 throw new Exception('Transactions are not allowed on a read-only slave');
354 $this->RequireConnection(true);
355 $this->Observe("DataAdapter ($this->_label) (DataAdapter.CommitTransaction)", OBSERVE_QUERY);
356 $this->_transactionInProgress = false;
357 return $this->_driver->CommitTransaction($this->_dbconn);
361 * Rollback the current DB transaction and re-enable auto-commit if necessary
363 * @access public
365 function RollbackTransaction()
367 if ($this->ConnectionSetting->IsReadOnlySlave) {
368 throw new Exception('Transactions are not allowed on a read-only slave');
371 $this->RequireConnection(true);
372 $this->Observe("DataAdapter ($this->_label) (DataAdapter.RollbackTransaction)", OBSERVE_QUERY);
373 $this->_transactionInProgress = false;
374 return $this->_driver->RollbackTransaction($this->_dbconn);
378 * Return true if the error with the given message is a communication/network error
380 * @param
381 * variant string or Exception $msg
382 * @return bool
384 public function IsCommunicationError($error)
386 $msg = is_a($error, 'Exception') ? $error->getMessage() : $error;
387 return strpos(strtolower($msg), 'lost connection') !== false;
391 * Returns an array of all table names in the current database
393 * @param
394 * bool true to ommit tables that are empty (default = false)
395 * @return array
397 public function GetTableNames($ommitEmptyTables = false)
399 return $this->_driver->GetTableName($this->_dbconn, $this->GetDBName(), $ommitEmptyTables);
403 * Runs OPTIMIZE TABLE on all tables in the current database
405 * @return array results for each table
407 public function OptimizeTables()
409 if ($this->ConnectionSetting->IsReadOnlySlave) {
410 throw new Exception('Optimizing tables is allowed on a read-only slave');
413 $results = array ();
414 $table_names = $this->_driver->GetTableNames($this->_dbconn, $this->GetDBName());
416 foreach ($table_names as $table_name) {
417 $results [$table_name] = $this->_driver->Optimize($this->_dbconn, $table_name);
420 return $results;
423 * Returns last auto-inserted Id.
424 * If this is a read-only slave and a write operation was made to a Master Delegate
425 * then the last insert id from that connection will be returned
427 * @access public
428 * @return int
430 function GetLastInsertId()
432 $id = null;
434 if ($this->ConnectionSetting->IsReadOnlySlave && $this->_masterAdapter) {
435 $id = $this->_masterAdapter->GetLastInsertId();
436 } else {
437 $this->RequireConnection();
438 $this->Observe("DataAdapter ($this->_label) GetLastInsertId", OBSERVE_QUERY);
439 $id = $this->_driver->GetLastInsertId($this->_dbconn);
442 return $id;
446 * Moves the database curser forward and returns the current row as an associative array
447 * the resultset passed in must have been created by the same database driver that
448 * was connected when Select was called
450 * @access public
451 * @param resultset $rs
452 * @return Array
454 function Fetch($rs)
456 $this->RequireConnection();
458 $this->Observe("DataAdapter ($this->_label) Fetching next result as array", OBSERVE_DEBUG);
459 return $this->_driver->Fetch($this->_dbconn, $rs);
463 * Releases the resources for the given resultset.
464 * the resultset must have
465 * been created by the same database driver
467 * @access public
468 * @param resultset $rs
470 function Release($rs)
472 $this->RequireConnection();
474 $this->Observe("DataAdapter ($this->_label) Releasing result resources", OBSERVE_DEBUG);
475 $this->_driver->Release($this->_dbconn, $rs);
479 * Removes any illegal chars from a value to prepare it for use in SQL
481 * @access public
482 * @param string $val
483 * @return string
485 public static function Escape($val)
487 if (DataAdapter::$ADAPTER_INSTANCE) {
488 DataAdapter::$ADAPTER_INSTANCE->LoadDriver();
491 // this is an unfortunate leftover from poor design of making this function static
492 // we cannon use the driver's escape method without a static reference
493 if (! DataAdapter::$DRIVER_INSTANCE) {
494 throw new Exception("DataAdapter must be instantiated before Escape can be called");
497 // $driver->RequireConnection(true);
498 return DataAdapter::$DRIVER_INSTANCE->Escape($val);
502 * Quote and escape value to prepare it for use in SQL
504 * @access public
505 * @param string $val
506 * @return string
508 public static function GetQuotedSql($val)
510 if (DataAdapter::$ADAPTER_INSTANCE) {
511 DataAdapter::$ADAPTER_INSTANCE->LoadDriver();
514 // this is an unfortunate leftover from poor design of making this function static
515 // we cannon use the driver's escape method without a static reference
516 if (! DataAdapter::$DRIVER_INSTANCE) {
517 throw new Exception("DataAdapter must be instantiated before Escape can be called");
520 // $driver->RequireConnection(true);
521 return DataAdapter::$DRIVER_INSTANCE->GetQuotedSql($val);
525 * Registers/attaches an IObserver to this object
527 * @access public
528 * @param IObserver $observer
530 public function AttachObserver($listener)
532 if ($listener) {
533 $this->_observers [] = & $listener;
534 if ($this->_masterAdapter) {
535 $this->_masterAdapter->AttachObserver($listener);
541 * Fires the Observe event on all registered observers
543 * @access public
544 * @param variant $obj
545 * the $obj or message that you want to log/listen to, etc.
546 * @param int $ltype
547 * the type/level
549 public function Observe($obj, $ltype = OBSERVE_INFO)
551 foreach ($this->_observers as $observer) {
552 @$observer->Observe($obj, $ltype);