2 /** @package verysimple::Phreeze */
5 * import supporting libraries
7 require_once ("IObservable.php");
8 require_once ("ConnectionSetting.php");
9 require_once ("verysimple/DB/DataDriver/IDataDriver.php");
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
20 class DataAdapter
implements IObservable
{
24 * @var ConnectionSetting
26 public $ConnectionSetting;
27 private $_observers = Array ();
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;
48 * Contructor initializes the object
51 * @param ConnectionSetting $csetting
52 * @param Observable $listener
54 * IDataDriver (optional) if not provided, then DataAdapter will attempt to instantiate one based on ConnectionSetting->Type
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;
61 DataAdapter
::$DRIVER_INSTANCE = $this->_driver
;
63 $this->_label
= $label ?
$label : 'db-' . mt_rand ( 10000, 99999 );
65 $this->ConnectionSetting
= & $csetting;
68 $this->AttachObserver ( $listener );
69 $this->Observe ( "DataAdapter ($this->_label) Instantiated", OBSERVE_DEBUG
);
71 // set the singleton reference
72 DataAdapter
::$ADAPTER_INSTANCE = $this;
76 * Destructor closes the db connection.
80 function __destruct() {
81 $this->Observe ( "DataAdapter ($this->_label) Destructor Firing...", OBSERVE_DEBUG
);
86 * Load the data driver
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
) {
98 include_once ("verysimple/DB/DataDriver/MySQL.php");
99 $this->_driver
= new DataDriverMySQL ();
102 include_once ("verysimple/DB/DataDriver/MySQLi.php");
103 $this->_driver
= new DataDriverMySQLi ();
106 include_once ("verysimple/DB/DataDriver/SQLite.php");
107 $this->_driver
= new DataDriverSQLite ();
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' );
120 DataAdapter
::$DRIVER_INSTANCE = $this->_driver
;
125 * Returns name of the DB currently in use
130 function GetDBName() {
131 return $this->ConnectionSetting
->DBName
;
135 * Opens a connection to the data server and selects the specified database
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
);
145 if (! $this->_driver
)
146 $this->LoadDriver ();
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
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
);
185 $this->Observe ( "DataAdapter ($this->_label) Connection Not Open", OBSERVE_DEBUG
);
190 * Checks that the connection is open and if not, crashes
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);
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
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
);
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 () );
243 * Executes a SQL query that does not return a resultset
247 * @return int number of records affected
249 function Execute($sql) {
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 );
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 );
275 $this->RequireConnection ( true );
276 $this->Observe ( "DataAdapter ($this->_label) (DataAdapter.Execute) " . $sql, OBSERVE_QUERY
);
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 () );
301 * Return true if a transaction is in progress
305 function IsTransactionInProgress() {
306 return $this->_transactionInProgress
;
310 * Start a DB transaction, disabling auto-commit if necessar)
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
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
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
361 * variant string or Exception $msg
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
373 * bool true to ommit tables that are empty (default = false)
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' );
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 );
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
406 function GetLastInsertId() {
409 if ($this->ConnectionSetting
->IsReadOnlySlave
&& $this->_masterAdapter
) {
410 $id = $this->_masterAdapter
->GetLastInsertId ();
412 $this->RequireConnection ();
413 $this->Observe ( "DataAdapter ($this->_label) GetLastInsertId", OBSERVE_QUERY
);
414 $id = $this->_driver
->GetLastInsertId ( $this->_dbconn
);
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
426 * @param resultset $rs
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
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
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
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
499 * @param IObserver $observer
501 public function AttachObserver($listener) {
503 $this->_observers
[] = & $listener;
504 if ($this->_masterAdapter
)
505 $this->_masterAdapter
->AttachObserver ( $listener );
510 * Fires the Observe event on all registered observers
513 * @param variant $obj
514 * the $obj or message that you want to log/listen to, etc.
518 public function Observe($obj, $ltype = OBSERVE_INFO
) {
519 foreach ( $this->_observers
as $observer )
520 @$observer->Observe ( $obj, $ltype );