2008-05-30 11:40:08 +00:00
< ? php
/**
* Oracle layer for DBO .
*
* PHP versions 4 and 5
*
2009-11-06 06:46:59 +00:00
* CakePHP ( tm ) : Rapid Development Framework ( http :// cakephp . org )
2010-01-26 19:18:20 +00:00
* Copyright 2005 - 2010 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2008-05-30 11:40:08 +00:00
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice .
*
2010-01-26 19:18:20 +00:00
* @ copyright Copyright 2005 - 2010 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2009-11-06 06:00:11 +00:00
* @ link http :// cakephp . org CakePHP ( tm ) Project
2008-10-30 17:30:26 +00:00
* @ package cake
* @ subpackage cake . cake . libs . model . datasources . dbo
* @ since CakePHP v 1.2 . 0.4041
2009-11-06 06:51:51 +00:00
* @ license MIT License ( http :// www . opensource . org / licenses / mit - license . php )
2008-05-30 11:40:08 +00:00
*/
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:20:28 +00:00
* Oracle layer for DBO .
2008-05-30 11:40:08 +00:00
*
* Long description for class
*
2008-10-30 17:30:26 +00:00
* @ package cake
* @ subpackage cake . cake . libs . model . datasources . dbo
2008-05-30 11:40:08 +00:00
*/
class DboOracle extends DboSource {
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Configuration options
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var array
2008-05-30 11:40:08 +00:00
* @ access public
*/
2010-04-04 07:14:00 +00:00
public $config = array ();
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Alias
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var string
2008-05-30 11:40:08 +00:00
*/
2010-04-04 07:14:00 +00:00
public $alias = '' ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Sequence names as introspected from the database
*/
2010-04-04 06:36:12 +00:00
protected $_sequences = array ();
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Transaction in progress flag
*
* @ var boolean
*/
2010-04-04 06:33:39 +00:00
private $__transactionStarted = false ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Column definitions
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var array
2008-05-30 11:40:08 +00:00
* @ access public
*/
2010-04-04 07:14:00 +00:00
public $columns = array (
2008-12-10 01:30:36 +00:00
'primary_key' => array ( 'name' => '' ),
2008-05-30 11:40:08 +00:00
'string' => array ( 'name' => 'varchar2' , 'limit' => '255' ),
'text' => array ( 'name' => 'varchar2' ),
2008-12-10 01:30:36 +00:00
'integer' => array ( 'name' => 'number' ),
2008-05-30 11:40:08 +00:00
'float' => array ( 'name' => 'float' ),
'datetime' => array ( 'name' => 'date' , 'format' => 'Y-m-d H:i:s' ),
'timestamp' => array ( 'name' => 'date' , 'format' => 'Y-m-d H:i:s' ),
'time' => array ( 'name' => 'date' , 'format' => 'Y-m-d H:i:s' ),
'date' => array ( 'name' => 'date' , 'format' => 'Y-m-d H:i:s' ),
'binary' => array ( 'name' => 'bytea' ),
'boolean' => array ( 'name' => 'boolean' ),
2008-12-10 01:30:36 +00:00
'number' => array ( 'name' => 'number' ),
2008-05-30 11:40:08 +00:00
'inet' => array ( 'name' => 'inet' ));
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Connection object
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var mixed
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 07:14:00 +00:00
public $connection ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Query limit
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var int
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_limit = - 1 ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Query offset
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var int
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_offset = 0 ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Enter description here ...
*
* @ var unknown_type
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_map ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Current Row
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var mixed
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_currentRow ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Number of rows
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var int
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_numRows ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Query results
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ var mixed
2008-05-30 11:40:08 +00:00
* @ access protected
*/
2010-04-04 06:36:12 +00:00
protected $_results ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Last error issued by oci extension
*
* @ var unknown_type
*/
2010-04-04 06:36:12 +00:00
protected $_error ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Base configuration settings for MySQL driver
*
* @ var array
*/
2010-04-04 06:36:12 +00:00
protected $_baseConfig = array (
2008-05-30 11:40:08 +00:00
'persistent' => true ,
'host' => 'localhost' ,
'login' => 'system' ,
'password' => '' ,
'database' => 'cake' ,
'nls_sort' => '' ,
'nls_sort' => ''
);
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Table - sequence map
*
* @ var unknown_type
*/
2010-04-04 06:36:12 +00:00
protected $_sequenceMap = array ();
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Connects to the database using options in the given configuration array .
*
* @ return boolean True if the database could be connected , else false
*/
2010-04-05 03:19:38 +00:00
public function connect () {
2008-05-30 11:40:08 +00:00
$config = $this -> config ;
$this -> connected = false ;
$config [ 'charset' ] = ! empty ( $config [ 'charset' ]) ? $config [ 'charset' ] : null ;
2009-12-24 10:03:23 +00:00
if ( ! $config [ 'persistent' ]) {
$this -> connection = @ ocilogon ( $config [ 'login' ], $config [ 'password' ], $config [ 'database' ], $config [ 'charset' ]);
2008-05-30 11:40:08 +00:00
} else {
2009-12-24 10:03:23 +00:00
$this -> connection = @ ociplogon ( $config [ 'login' ], $config [ 'password' ], $config [ 'database' ], $config [ 'charset' ]);
2008-05-30 11:40:08 +00:00
}
if ( $this -> connection ) {
$this -> connected = true ;
if ( ! empty ( $config [ 'nls_sort' ])) {
$this -> execute ( 'ALTER SESSION SET NLS_SORT=' . $config [ 'nls_sort' ]);
}
if ( ! empty ( $config [ 'nls_comp' ])) {
$this -> execute ( 'ALTER SESSION SET NLS_COMP=' . $config [ 'nls_comp' ]);
}
$this -> execute ( " ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS' " );
} else {
$this -> connected = false ;
$this -> _setError ();
return false ;
}
return $this -> connected ;
}
2009-07-26 10:46:07 +00:00
/**
* Keeps track of the most recent Oracle error
*
*/
2008-06-24 21:11:23 +00:00
function _setError ( $source = null , $clear = false ) {
2008-05-30 11:40:08 +00:00
if ( $source ) {
2008-06-24 20:30:51 +00:00
$e = ocierror ( $source );
2008-05-30 11:40:08 +00:00
} else {
$e = ocierror ();
}
$this -> _error = $e [ 'message' ];
2008-06-24 21:11:23 +00:00
if ( $clear ) {
$this -> _error = null ;
}
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Sets the encoding language of the session
*
* @ param string $lang language constant
* @ return bool
*/
function setEncoding ( $lang ) {
if ( ! $this -> execute ( 'ALTER SESSION SET NLS_LANGUAGE=' . $lang )) {
return false ;
}
return true ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Gets the current encoding language
*
* @ return string language constant
*/
function getEncoding () {
$sql = 'SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER=\'NLS_LANGUAGE\'' ;
if ( ! $this -> execute ( $sql )) {
return false ;
}
if ( ! $row = $this -> fetchRow ()) {
return false ;
}
return $row [ 0 ][ 'VALUE' ];
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Disconnects from database .
*
* @ return boolean True if the database could be disconnected , else false
*/
2010-04-05 03:19:38 +00:00
public function disconnect () {
2008-05-30 11:40:08 +00:00
if ( $this -> connection ) {
$this -> connected = ! ocilogoff ( $this -> connection );
return ! $this -> connected ;
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Scrape the incoming SQL to create the association map . This is an extremely
* experimental method that creates the association maps since Oracle will not tell us .
*
* @ param string $sql
* @ return false if sql is nor a SELECT
*/
2010-04-05 03:21:28 +00:00
protected function _scrapeSQL ( $sql ) {
2008-05-30 11:40:08 +00:00
$sql = str_replace ( " \" " , '' , $sql );
$preFrom = preg_split ( '/\bFROM\b/' , $sql );
$preFrom = $preFrom [ 0 ];
$find = array ( 'SELECT' );
$replace = array ( '' );
$fieldList = trim ( str_replace ( $find , $replace , $preFrom ));
$fields = preg_split ( '/,\s+/' , $fieldList ); //explode(', ', $fieldList);
$lastTableName = '' ;
foreach ( $fields as $key => $value ) {
if ( $value != 'COUNT(*) AS count' ) {
if ( preg_match ( '/\s+(\w+(\.\w+)*)$/' , $value , $matches )) {
$fields [ $key ] = $matches [ 1 ];
if ( preg_match ( '/^(\w+\.)/' , $value , $matches )) {
$fields [ $key ] = $matches [ 1 ] . $fields [ $key ];
$lastTableName = $matches [ 1 ];
}
}
/*
if ( preg_match ( '/(([[:alnum:]_]+)\.[[:alnum:]_]+)(\s+AS\s+(\w+))?$/i' , $value , $matches )) {
$fields [ $key ] = isset ( $matches [ 4 ]) ? $matches [ 2 ] . '.' . $matches [ 4 ] : $matches [ 1 ];
}
*/
}
}
$this -> _map = array ();
foreach ( $fields as $f ) {
$e = explode ( '.' , $f );
if ( count ( $e ) > 1 ) {
$table = $e [ 0 ];
$field = strtolower ( $e [ 1 ]);
} else {
$table = 0 ;
$field = $e [ 0 ];
}
$this -> _map [] = array ( $table , $field );
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Modify a SQL query to limit ( and offset ) the result set
*
* @ param integer $limit Maximum number of rows to return
* @ param integer $offset Row to begin returning
* @ return modified SQL Query
*/
2010-04-05 03:19:38 +00:00
public function limit ( $limit = - 1 , $offset = 0 ) {
2008-05-30 11:40:08 +00:00
$this -> _limit = ( int ) $limit ;
$this -> _offset = ( int ) $offset ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns number of rows in previous resultset . If no previous resultset exists ,
* this returns false .
*
* @ return integer Number of rows in resultset
*/
2010-04-05 03:19:38 +00:00
public function lastNumRows () {
2008-05-30 11:40:08 +00:00
return $this -> _numRows ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Executes given SQL statement . This is an overloaded method .
*
* @ param string $sql SQL statement
* @ return resource Result resource identifier or null
*/
2010-04-05 03:21:28 +00:00
protected function _execute ( $sql ) {
2008-05-30 11:40:08 +00:00
$this -> _statementId = @ ociparse ( $this -> connection , $sql );
if ( ! $this -> _statementId ) {
$this -> _setError ( $this -> connection );
return false ;
}
if ( $this -> __transactionStarted ) {
$mode = OCI_DEFAULT ;
} else {
$mode = OCI_COMMIT_ON_SUCCESS ;
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( !@ ociexecute ( $this -> _statementId , $mode )) {
$this -> _setError ( $this -> _statementId );
return false ;
}
2008-07-30 20:34:01 +00:00
2008-06-24 21:11:23 +00:00
$this -> _setError ( null , true );
2008-05-30 11:40:08 +00:00
switch ( ocistatementtype ( $this -> _statementId )) {
case 'DESCRIBE' :
case 'SELECT' :
$this -> _scrapeSQL ( $sql );
break ;
default :
return $this -> _statementId ;
break ;
}
if ( $this -> _limit >= 1 ) {
ocisetprefetch ( $this -> _statementId , $this -> _limit );
} else {
ocisetprefetch ( $this -> _statementId , 3000 );
}
$this -> _numRows = ocifetchstatement ( $this -> _statementId , $this -> _results , $this -> _offset , $this -> _limit , OCI_NUM | OCI_FETCHSTATEMENT_BY_ROW );
$this -> _currentRow = 0 ;
$this -> limit ();
return $this -> _statementId ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Fetch result row
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ return array
2008-05-30 11:40:08 +00:00
* @ access public
*/
2010-04-05 03:19:38 +00:00
public function fetchRow () {
2008-05-30 11:40:08 +00:00
if ( $this -> _currentRow >= $this -> _numRows ) {
ocifreestatement ( $this -> _statementId );
$this -> _map = null ;
$this -> _results = null ;
$this -> _currentRow = null ;
$this -> _numRows = null ;
return false ;
}
$resultRow = array ();
foreach ( $this -> _results [ $this -> _currentRow ] as $index => $field ) {
list ( $table , $column ) = $this -> _map [ $index ];
if ( strpos ( $column , ' count' )) {
$resultRow [ 0 ][ 'count' ] = $field ;
} else {
$resultRow [ $table ][ $column ] = $this -> _results [ $this -> _currentRow ][ $index ];
}
}
$this -> _currentRow ++ ;
return $resultRow ;
}
2009-07-24 19:18:37 +00:00
2008-11-08 02:54:07 +00:00
/**
* Fetches the next row from the current result set
*
* @ return unknown
*/
2008-07-22 12:20:18 +00:00
function fetchResult () {
return $this -> fetchRow ();
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Checks to see if a named sequence exists
*
* @ param string $sequence
* @ return bool
*/
2010-04-05 03:19:38 +00:00
public function sequenceExists ( $sequence ) {
2008-05-30 11:40:08 +00:00
$sql = " SELECT SEQUENCE_NAME FROM USER_SEQUENCES WHERE SEQUENCE_NAME = ' $sequence ' " ;
if ( ! $this -> execute ( $sql )) {
return false ;
}
return $this -> fetchRow ();
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Creates a database sequence
*
* @ param string $sequence
* @ return bool
*/
2010-04-05 03:19:38 +00:00
public function createSequence ( $sequence ) {
2008-05-30 11:40:08 +00:00
$sql = " CREATE SEQUENCE $sequence " ;
return $this -> execute ( $sql );
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2010-05-29 15:10:48 +00:00
* Create trigger
2008-05-30 11:40:08 +00:00
*
2010-05-29 15:10:48 +00:00
* @ param string $table
* @ return mixed
2008-05-30 11:40:08 +00:00
* @ access public
*/
2010-04-05 03:19:38 +00:00
public function createTrigger ( $table ) {
2008-05-30 11:40:08 +00:00
$sql = " CREATE OR REPLACE TRIGGER pk_ $table " . " _trigger BEFORE INSERT ON $table FOR EACH ROW BEGIN SELECT pk_ $table .NEXTVAL INTO :NEW.ID FROM DUAL; END; " ;
return $this -> execute ( $sql );
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of tables in the database . If there are no tables , an error is
* raised and the application exits .
*
* @ return array tablenames in the database
*/
2010-04-05 03:19:38 +00:00
public function listSources () {
2008-05-30 11:40:08 +00:00
$cache = parent :: listSources ();
if ( $cache != null ) {
return $cache ;
}
2008-06-24 20:30:51 +00:00
$sql = 'SELECT view_name AS name FROM all_views UNION SELECT table_name AS name FROM all_tables' ;
2008-05-30 11:40:08 +00:00
if ( ! $this -> execute ( $sql )) {
return false ;
}
$sources = array ();
while ( $r = $this -> fetchRow ()) {
$sources [] = strtolower ( $r [ 0 ][ 'name' ]);
}
2009-04-25 01:06:46 +00:00
parent :: listSources ( $sources );
2008-05-30 11:40:08 +00:00
return $sources ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of the fields in given table name .
*
* @ param object instance of a model to inspect
* @ return array Fields in table . Keys are name and type
*/
2010-04-05 03:19:38 +00:00
public function describe ( & $model ) {
2009-08-23 21:52:40 +00:00
$table = $this -> fullTableName ( $model , false );
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( ! empty ( $model -> sequence )) {
2009-08-02 06:47:28 +00:00
$this -> _sequenceMap [ $table ] = $model -> sequence ;
2008-05-30 11:40:08 +00:00
} elseif ( ! empty ( $model -> table )) {
2009-08-02 06:47:28 +00:00
$this -> _sequenceMap [ $table ] = $model -> table . '_seq' ;
2008-07-30 20:34:01 +00:00
}
2008-05-30 11:40:08 +00:00
$cache = parent :: describe ( $model );
if ( $cache != null ) {
return $cache ;
}
2009-08-02 06:47:28 +00:00
2008-06-24 20:30:51 +00:00
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM all_tab_columns WHERE table_name = \'' ;
2008-05-30 11:40:08 +00:00
$sql .= strtoupper ( $this -> fullTableName ( $model )) . '\'' ;
if ( ! $this -> execute ( $sql )) {
return false ;
}
2009-08-02 06:47:28 +00:00
2008-05-30 11:40:08 +00:00
$fields = array ();
2008-06-20 20:17:23 +00:00
for ( $i = 0 ; $row = $this -> fetchRow (); $i ++ ) {
$fields [ strtolower ( $row [ 0 ][ 'COLUMN_NAME' ])] = array (
'type' => $this -> column ( $row [ 0 ][ 'DATA_TYPE' ]),
'length' => $row [ 0 ][ 'DATA_LENGTH' ]
);
2008-05-30 11:40:08 +00:00
}
$this -> __cacheDescription ( $this -> fullTableName ( $model , false ), $fields );
2008-06-20 20:17:23 +00:00
2008-05-30 11:40:08 +00:00
return $fields ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Deletes all the records in a table and drops all associated auto - increment sequences .
* Using DELETE instead of TRUNCATE because it causes locking problems .
*
* @ param mixed $table A string or model class representing the table to be truncated
* @ param integer $reset If - 1 , sequences are dropped , if 0 ( default ), sequences are reset ,
* and if 1 , sequences are not modified
* @ return boolean SQL TRUNCATE TABLE statement , false if not applicable .
* @ access public
*
*/
function truncate ( $table , $reset = 0 ) {
2008-06-20 20:17:23 +00:00
2008-05-30 11:40:08 +00:00
if ( empty ( $this -> _sequences )) {
2008-06-24 20:30:51 +00:00
$sql = " SELECT sequence_name FROM all_sequences " ;
2008-05-30 11:40:08 +00:00
$this -> execute ( $sql );
while ( $row = $this -> fetchRow ()) {
$this -> _sequences [] = strtolower ( $row [ 0 ][ 'sequence_name' ]);
}
}
$this -> execute ( 'DELETE FROM ' . $this -> fullTableName ( $table ));
if ( ! isset ( $this -> _sequenceMap [ $table ]) || ! in_array ( $this -> _sequenceMap [ $table ], $this -> _sequences )) {
return true ;
}
if ( $reset === 0 ) {
$this -> execute ( " SELECT { $this -> _sequenceMap [ $table ] } .nextval FROM dual " );
$row = $this -> fetchRow ();
$currval = $row [ $this -> _sequenceMap [ $table ]][ 'nextval' ];
2008-06-24 20:30:51 +00:00
$this -> execute ( " SELECT min_value FROM all_sequences WHERE sequence_name = ' { $this -> _sequenceMap [ $table ] } ' " );
2008-05-30 11:40:08 +00:00
$row = $this -> fetchRow ();
$min_value = $row [ 0 ][ 'min_value' ];
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( $min_value == 1 ) $min_value = 0 ;
$offset = - ( $currval - $min_value );
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
$this -> execute ( " ALTER SEQUENCE { $this -> _sequenceMap [ $table ] } INCREMENT BY $offset MINVALUE $min_value " );
$this -> execute ( " SELECT { $this -> _sequenceMap [ $table ] } .nextval FROM dual " );
$this -> execute ( " ALTER SEQUENCE { $this -> _sequenceMap [ $table ] } INCREMENT BY 1 " );
} else {
2008-06-20 20:17:23 +00:00
//$this->execute("DROP SEQUENCE {$this->_sequenceMap[$table]}");
2008-05-30 11:40:08 +00:00
}
return true ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Enables , disables , and lists table constraints
2008-06-24 20:30:51 +00:00
*
2008-05-30 11:40:08 +00:00
* Note : This method could have been written using a subselect for each table ,
* however the effort Oracle expends to run the constraint introspection is very high .
* Therefore , this method caches the result once and loops through the arrays to find
* what it needs . It reduced my query time by 50 %. YMMV .
*
* @ param string $action
* @ param string $table
* @ return mixed boolean true or array of constraints
*/
function constraint ( $action , $table ) {
if ( empty ( $table )) {
2010-04-15 16:00:25 +00:00
trigger_error ( __ ( 'Must specify table to operate on constraints' ));
2008-05-30 11:40:08 +00:00
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
$table = strtoupper ( $table );
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( empty ( $this -> _keyConstraints )) {
2008-06-24 20:30:51 +00:00
$sql = " SELECT
table_name ,
2008-05-30 11:40:08 +00:00
c . constraint_name
2008-06-24 20:30:51 +00:00
FROM all_cons_columns cc
LEFT JOIN all_indexes i ON ( cc . constraint_name = i . index_name )
LEFT JOIN all_constraints c ON ( c . constraint_name = cc . constraint_name ) " ;
2008-05-30 11:40:08 +00:00
$this -> execute ( $sql );
while ( $row = $this -> fetchRow ()) {
$this -> _keyConstraints [] = array ( $row [ 0 ][ 'table_name' ], $row [ 'c' ][ 'constraint_name' ]);
}
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
$relatedKeys = array ();
foreach ( $this -> _keyConstraints as $c ) {
if ( $c [ 0 ] == $table ) {
$relatedKeys [] = $c [ 1 ];
}
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( empty ( $this -> _constraints )) {
2008-06-24 20:30:51 +00:00
$sql = " SELECT
2008-05-30 11:40:08 +00:00
table_name ,
constraint_name ,
r_constraint_name
2008-06-24 20:30:51 +00:00
FROM
all_constraints " ;
2008-05-30 11:40:08 +00:00
$this -> execute ( $sql );
while ( $row = $this -> fetchRow ()) {
$this -> _constraints [] = $row [ 0 ];
}
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
$constraints = array ();
foreach ( $this -> _constraints as $c ) {
if ( in_array ( $c [ 'r_constraint_name' ], $relatedKeys )) {
$constraints [] = array ( $c [ 'table_name' ], $c [ 'constraint_name' ]);
}
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
foreach ( $constraints as $c ) {
list ( $table , $constraint ) = $c ;
switch ( $action ) {
case 'enable' :
$this -> execute ( " ALTER TABLE $table ENABLE CONSTRAINT $constraint " );
break ;
case 'disable' :
$this -> execute ( " ALTER TABLE $table DISABLE CONSTRAINT $constraint " );
break ;
case 'list' :
return $constraints ;
break ;
default :
2010-04-15 16:00:25 +00:00
trigger_error ( __ ( 'DboOracle::constraint() accepts only enable, disable, or list' ));
2008-05-30 11:40:08 +00:00
}
}
return true ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns an array of the indexes in given table name .
*
* @ param string $model Name of model to inspect
* @ return array Fields in table . Keys are column and unique
*/
function index ( $model ) {
$index = array ();
$table = $this -> fullTableName ( $model , false );
2008-11-08 02:54:07 +00:00
if ( $table ) {
2008-05-30 11:40:08 +00:00
$indexes = $this -> query ( ' SELECT
cc . table_name ,
cc . column_name ,
cc . constraint_name ,
c . constraint_type ,
i . index_name ,
i . uniqueness
2008-06-24 20:30:51 +00:00
FROM all_cons_columns cc
LEFT JOIN all_indexes i ON ( cc . constraint_name = i . index_name )
LEFT JOIN all_constraints c ON ( c . constraint_name = cc . constraint_name )
2008-05-30 11:40:08 +00:00
WHERE cc . table_name = \ '' . strtoupper ( $table ) . '\'' );
foreach ( $indexes as $i => $idx ) {
if ( $idx [ 'c' ][ 'constraint_type' ] == 'P' ) {
$key = 'PRIMARY' ;
} else {
continue ;
}
2008-11-08 02:54:07 +00:00
if ( ! isset ( $index [ $key ])) {
2008-05-30 11:40:08 +00:00
$index [ $key ][ 'column' ] = strtolower ( $idx [ 'cc' ][ 'column_name' ]);
2008-07-30 20:34:01 +00:00
$index [ $key ][ 'unique' ] = intval ( $idx [ 'i' ][ 'uniqueness' ] == 'UNIQUE' );
2008-05-30 11:40:08 +00:00
} else {
2008-11-08 02:54:07 +00:00
if ( ! is_array ( $index [ $key ][ 'column' ])) {
2008-05-30 11:40:08 +00:00
$col [] = $index [ $key ][ 'column' ];
}
$col [] = strtolower ( $idx [ 'cc' ][ 'column_name' ]);
$index [ $key ][ 'column' ] = $col ;
}
}
}
return $index ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Generate a Oracle Alter Table syntax for the given Schema comparison
*
* @ param unknown_type $schema
* @ return unknown
*/
function alterSchema ( $compare , $table = null ) {
2008-11-08 02:54:07 +00:00
if ( ! is_array ( $compare )) {
2008-05-30 11:40:08 +00:00
return false ;
}
$out = '' ;
$colList = array ();
foreach ( $compare as $curTable => $types ) {
if ( ! $table || $table == $curTable ) {
$out .= 'ALTER TABLE ' . $this -> fullTableName ( $curTable ) . " \n " ;
foreach ( $types as $type => $column ) {
switch ( $type ) {
case 'add' :
foreach ( $column as $field => $col ) {
$col [ 'name' ] = $field ;
$alter = 'ADD ' . $this -> buildColumn ( $col );
2008-11-08 02:54:07 +00:00
if ( isset ( $col [ 'after' ])) {
2008-05-30 11:40:08 +00:00
$alter .= ' AFTER ' . $this -> name ( $col [ 'after' ]);
}
$colList [] = $alter ;
}
break ;
case 'drop' :
foreach ( $column as $field => $col ) {
$col [ 'name' ] = $field ;
$colList [] = 'DROP ' . $this -> name ( $field );
}
break ;
case 'change' :
foreach ( $column as $field => $col ) {
2008-11-08 02:54:07 +00:00
if ( ! isset ( $col [ 'name' ])) {
2008-05-30 11:40:08 +00:00
$col [ 'name' ] = $field ;
}
$colList [] = 'CHANGE ' . $this -> name ( $field ) . ' ' . $this -> buildColumn ( $col );
}
break ;
}
}
2009-11-19 22:13:35 +00:00
$out .= " \t " . implode ( " , \n \t " , $colList ) . " ; \n \n " ;
2008-05-30 11:40:08 +00:00
}
}
return $out ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* This method should quote Oracle identifiers . Well it doesn ' t .
* It would break all scaffolding and all of Cake ' s default assumptions .
*
* @ param unknown_type $var
* @ return unknown
*/
2010-04-05 03:19:38 +00:00
public function name ( $name ) {
2008-06-24 20:30:51 +00:00
if ( strpos ( $name , '.' ) !== false && strpos ( $name , '"' ) === false ) {
list ( $model , $field ) = explode ( '.' , $name );
if ( $field [ 0 ] == " _ " ) {
$name = " $model . \" $field\ " " ;
2008-06-24 21:11:23 +00:00
}
} else {
if ( $name [ 0 ] == " _ " ) {
2008-06-24 20:30:51 +00:00
$name = " \" $name\ " " ;
}
2008-05-30 11:40:08 +00:00
}
return $name ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Begin a transaction
*
* @ param unknown_type $model
* @ return boolean True on success , false on fail
* ( i . e . if the database / model does not support transactions ) .
*/
function begin () {
$this -> __transactionStarted = true ;
return true ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Rollback a transaction
*
* @ param unknown_type $model
* @ return boolean True on success , false on fail
* ( i . e . if the database / model does not support transactions ,
* or a transaction has not started ) .
*/
function rollback () {
return ocirollback ( $this -> connection );
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Commit a transaction
*
* @ param unknown_type $model
* @ return boolean True on success , false on fail
* ( i . e . if the database / model does not support transactions ,
* or a transaction has not started ) .
*/
function commit () {
$this -> __transactionStarted = false ;
return ocicommit ( $this -> connection );
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Converts database - layer column types to basic types
*
* @ param string $real Real database - layer column type ( i . e . " varchar(255) " )
* @ return string Abstract column type ( i . e . " string " )
*/
2010-04-05 03:19:38 +00:00
public function column ( $real ) {
2008-05-30 11:40:08 +00:00
if ( is_array ( $real )) {
$col = $real [ 'name' ];
if ( isset ( $real [ 'limit' ])) {
$col .= '(' . $real [ 'limit' ] . ')' ;
}
return $col ;
} else {
$real = strtolower ( $real );
}
$col = str_replace ( ')' , '' , $real );
$limit = null ;
if ( strpos ( $col , '(' ) !== false ) {
list ( $col , $limit ) = explode ( '(' , $col );
}
if ( in_array ( $col , array ( 'date' , 'timestamp' ))) {
return $col ;
}
if ( strpos ( $col , 'number' ) !== false ) {
return 'integer' ;
}
if ( strpos ( $col , 'integer' ) !== false ) {
return 'integer' ;
}
if ( strpos ( $col , 'char' ) !== false ) {
return 'string' ;
}
if ( strpos ( $col , 'text' ) !== false ) {
return 'text' ;
}
if ( strpos ( $col , 'blob' ) !== false ) {
return 'binary' ;
}
if ( in_array ( $col , array ( 'float' , 'double' , 'decimal' ))) {
return 'float' ;
}
if ( $col == 'boolean' ) {
return $col ;
}
return 'text' ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns a quoted and escaped string of $data for use in an SQL statement .
*
* @ param string $data String to be prepared for use in an SQL statement
* @ return string Quoted and escaped
*/
2010-04-05 03:19:38 +00:00
public function value ( $data , $column = null , $safe = false ) {
2008-06-20 20:17:23 +00:00
$parent = parent :: value ( $data , $column , $safe );
2008-05-30 11:40:08 +00:00
2008-06-20 20:17:23 +00:00
if ( $parent != null ) {
2008-05-30 11:40:08 +00:00
return $parent ;
}
if ( $data === null ) {
return 'NULL' ;
}
if ( $data === '' ) {
return " '' " ;
}
switch ( $column ) {
case 'date' :
2009-04-25 01:06:46 +00:00
$data = date ( 'Y-m-d H:i:s' , strtotime ( $data ));
2008-05-30 11:40:08 +00:00
$data = " TO_DATE(' $data ', 'YYYY-MM-DD HH24:MI:SS') " ;
2008-06-20 20:17:23 +00:00
break ;
2008-05-30 11:40:08 +00:00
case 'integer' :
case 'float' :
case null :
if ( is_numeric ( $data )) {
break ;
}
default :
2008-06-20 20:17:23 +00:00
$data = str_replace ( " ' " , " '' " , $data );
$data = " ' $data ' " ;
break ;
2008-05-30 11:40:08 +00:00
}
return $data ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns the ID generated from the previous INSERT operation .
*
* @ param string
* @ return integer
*/
2010-04-05 03:19:38 +00:00
public function lastInsertId ( $source ) {
2008-05-30 11:40:08 +00:00
$sequence = $this -> _sequenceMap [ $source ];
$sql = " SELECT $sequence .currval FROM dual " ;
if ( ! $this -> execute ( $sql )) {
return false ;
}
while ( $row = $this -> fetchRow ()) {
return $row [ $sequence ][ 'currval' ];
}
return false ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns a formatted error message from previous database operation .
*
* @ return string Error message with error number
*/
2010-04-05 03:19:38 +00:00
public function lastError () {
2008-05-30 11:40:08 +00:00
return $this -> _error ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Returns number of affected rows in previous database operation . If no previous operation exists , this returns false .
*
* @ return int Number of affected rows
*/
2010-04-05 03:19:38 +00:00
public function lastAffected () {
2008-05-30 11:40:08 +00:00
return $this -> _statementId ? ocirowcount ( $this -> _statementId ) : false ;
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Renders a final SQL statement by putting together the component parts in the correct order
*
* @ param string $type
* @ param array $data
* @ return string
*/
function renderStatement ( $type , $data ) {
extract ( $data );
$aliases = null ;
switch ( strtolower ( $type )) {
case 'select' :
2009-09-24 03:16:57 +00:00
return " SELECT { $fields } FROM { $table } { $alias } { $joins } { $conditions } { $group } { $order } { $limit } " ;
2008-05-30 11:40:08 +00:00
break ;
2008-12-10 01:30:36 +00:00
case 'create' :
return " INSERT INTO { $table } ( { $fields } ) VALUES ( { $values } ) " ;
break ;
2008-05-30 11:40:08 +00:00
case 'update' :
if ( ! empty ( $alias )) {
$aliases = " { $this -> alias } { $alias } " ;
}
return " UPDATE { $table } { $aliases } SET { $fields } { $conditions } " ;
break ;
case 'delete' :
if ( ! empty ( $alias )) {
$aliases = " { $this -> alias } { $alias } " ;
}
return " DELETE FROM { $table } { $aliases } { $conditions } " ;
break ;
2008-12-10 01:30:36 +00:00
case 'schema' :
foreach ( array ( 'columns' , 'indexes' ) as $var ) {
if ( is_array ( ${$var} )) {
2009-11-19 22:13:35 +00:00
${$var} = " \t " . implode ( " , \n \t " , array_filter ( ${$var} ));
2008-12-10 01:30:36 +00:00
}
}
if ( trim ( $indexes ) != '' ) {
$columns .= ',' ;
}
return " CREATE TABLE { $table } ( \n { $columns } { $indexes } ) " ;
break ;
case 'alter' :
break ;
2008-05-30 11:40:08 +00:00
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Enter description here ...
*
* @ param Model $model
* @ param unknown_type $linkModel
* @ param string $type Association type
* @ param unknown_type $association
* @ param unknown_type $assocData
* @ param unknown_type $queryData
* @ param unknown_type $external
* @ param unknown_type $resultSet
* @ param integer $recursive Number of levels of association
* @ param array $stack
*/
function queryAssociation ( & $model , & $linkModel , $type , $association , $assocData , & $queryData , $external = false , & $resultSet , $recursive , $stack ) {
if ( $query = $this -> generateAssociationQuery ( $model , $linkModel , $type , $association , $assocData , $queryData , $external , $resultSet )) {
if ( ! isset ( $resultSet ) || ! is_array ( $resultSet )) {
if ( Configure :: read () > 0 ) {
2010-04-15 16:00:25 +00:00
echo '<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf ( __ ( 'SQL Error in model %s:' ), $model -> alias ) . ' ' ;
2008-05-30 11:40:08 +00:00
if ( isset ( $this -> error ) && $this -> error != null ) {
2009-07-23 13:35:14 +00:00
echo $this -> error ;
2008-05-30 11:40:08 +00:00
}
2009-07-23 13:35:14 +00:00
echo '</div>' ;
2008-05-30 11:40:08 +00:00
}
return null ;
}
$count = count ( $resultSet );
if ( $type === 'hasMany' && ( ! isset ( $assocData [ 'limit' ]) || empty ( $assocData [ 'limit' ]))) {
$ins = $fetch = array ();
for ( $i = 0 ; $i < $count ; $i ++ ) {
if ( $in = $this -> insertQueryData ( '{$__cakeID__$}' , $resultSet [ $i ], $association , $assocData , $model , $linkModel , $stack )) {
$ins [] = $in ;
}
}
if ( ! empty ( $ins )) {
$fetch = array ();
$ins = array_chunk ( $ins , 1000 );
foreach ( $ins as $i ) {
2009-11-19 22:13:35 +00:00
$q = str_replace ( '{$__cakeID__$}' , implode ( ', ' , $i ), $query );
2008-12-29 21:37:01 +00:00
$q = str_replace ( '= (' , 'IN (' , $q );
2008-05-30 11:40:08 +00:00
$res = $this -> fetchAll ( $q , $model -> cacheQueries , $model -> alias );
$fetch = array_merge ( $fetch , $res );
}
}
if ( ! empty ( $fetch ) && is_array ( $fetch )) {
if ( $recursive > 0 ) {
2010-04-04 08:17:43 +00:00
foreach ( $linkModel -> associations () as $type1 ) {
2008-05-30 11:40:08 +00:00
foreach ( $linkModel -> { $type1 } as $assoc1 => $assocData1 ) {
$deepModel =& $linkModel -> { $assoc1 };
$tmpStack = $stack ;
$tmpStack [] = $assoc1 ;
if ( $linkModel -> useDbConfig === $deepModel -> useDbConfig ) {
$db =& $this ;
} else {
$db =& ConnectionManager :: getDataSource ( $deepModel -> useDbConfig );
}
$db -> queryAssociation ( $linkModel , $deepModel , $type1 , $assoc1 , $assocData1 , $queryData , true , $fetch , $recursive - 1 , $tmpStack );
}
}
}
}
return $this -> __mergeHasMany ( $resultSet , $fetch , $association , $model , $linkModel , $recursive );
} elseif ( $type === 'hasAndBelongsToMany' ) {
$ins = $fetch = array ();
for ( $i = 0 ; $i < $count ; $i ++ ) {
if ( $in = $this -> insertQueryData ( '{$__cakeID__$}' , $resultSet [ $i ], $association , $assocData , $model , $linkModel , $stack )) {
$ins [] = $in ;
}
}
$foreignKey = $model -> hasAndBelongsToMany [ $association ][ 'foreignKey' ];
$joinKeys = array ( $foreignKey , $model -> hasAndBelongsToMany [ $association ][ 'associationForeignKey' ]);
list ( $with , $habtmFields ) = $model -> joinModel ( $model -> hasAndBelongsToMany [ $association ][ 'with' ], $joinKeys );
$habtmFieldsCount = count ( $habtmFields );
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
if ( ! empty ( $ins )) {
$fetch = array ();
$ins = array_chunk ( $ins , 1000 );
foreach ( $ins as $i ) {
2009-11-19 22:13:35 +00:00
$q = str_replace ( '{$__cakeID__$}' , '(' . implode ( ', ' , $i ) . ')' , $query );
2008-12-29 21:37:01 +00:00
$q = str_replace ( '= (' , 'IN (' , $q );
2008-05-30 11:40:08 +00:00
$q = str_replace ( ' WHERE 1 = 1' , '' , $q );
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
$q = $this -> insertQueryData ( $q , null , $association , $assocData , $model , $linkModel , $stack );
if ( $q != false ) {
$res = $this -> fetchAll ( $q , $model -> cacheQueries , $model -> alias );
$fetch = array_merge ( $fetch , $res );
}
}
}
}
2008-06-24 20:30:51 +00:00
2008-05-30 11:40:08 +00:00
for ( $i = 0 ; $i < $count ; $i ++ ) {
$row =& $resultSet [ $i ];
if ( $type !== 'hasAndBelongsToMany' ) {
$q = $this -> insertQueryData ( $query , $resultSet [ $i ], $association , $assocData , $model , $linkModel , $stack );
if ( $q != false ) {
$fetch = $this -> fetchAll ( $q , $model -> cacheQueries , $model -> alias );
} else {
$fetch = null ;
}
}
if ( ! empty ( $fetch ) && is_array ( $fetch )) {
if ( $recursive > 0 ) {
2010-04-04 08:17:43 +00:00
foreach ( $linkModel -> associations () as $type1 ) {
2008-05-30 11:40:08 +00:00
foreach ( $linkModel -> { $type1 } as $assoc1 => $assocData1 ) {
$deepModel =& $linkModel -> { $assoc1 };
if (( $type1 === 'belongsTo' ) || ( $deepModel -> alias === $model -> alias && $type === 'belongsTo' ) || ( $deepModel -> alias != $model -> alias )) {
$tmpStack = $stack ;
$tmpStack [] = $assoc1 ;
if ( $linkModel -> useDbConfig == $deepModel -> useDbConfig ) {
$db =& $this ;
} else {
$db =& ConnectionManager :: getDataSource ( $deepModel -> useDbConfig );
}
$db -> queryAssociation ( $linkModel , $deepModel , $type1 , $assoc1 , $assocData1 , $queryData , true , $fetch , $recursive - 1 , $tmpStack );
}
}
}
}
if ( $type == 'hasAndBelongsToMany' ) {
$merge = array ();
foreach ( $fetch as $j => $data ) {
if ( isset ( $data [ $with ]) && $data [ $with ][ $foreignKey ] === $row [ $model -> alias ][ $model -> primaryKey ]) {
if ( $habtmFieldsCount > 2 ) {
$merge [] = $data ;
} else {
$merge [] = Set :: diff ( $data , array ( $with => $data [ $with ]));
}
}
}
if ( empty ( $merge ) && ! isset ( $row [ $association ])) {
$row [ $association ] = $merge ;
} else {
$this -> __mergeAssociation ( $resultSet [ $i ], $merge , $association , $type );
}
} else {
$this -> __mergeAssociation ( $resultSet [ $i ], $fetch , $association , $type );
}
$resultSet [ $i ][ $association ] = $linkModel -> afterfind ( $resultSet [ $i ][ $association ]);
} else {
$tempArray [ 0 ][ $association ] = false ;
$this -> __mergeAssociation ( $resultSet [ $i ], $tempArray , $association , $type );
}
}
}
}
2009-07-26 10:46:07 +00:00
/**
* Generate a " drop table " statement for the given Schema object
*
* @ param object $schema An instance of a subclass of CakeSchema
* @ param string $table Optional . If specified only the table name given will be generated .
* Otherwise , all tables defined in the schema are generated .
* @ return string
*/
2008-12-10 01:30:36 +00:00
function dropSchema ( $schema , $table = null ) {
if ( ! is_a ( $schema , 'CakeSchema' )) {
2010-04-15 16:00:25 +00:00
trigger_error ( __ ( 'Invalid schema object' ), E_USER_WARNING );
2008-12-10 01:30:36 +00:00
return null ;
}
$out = '' ;
foreach ( $schema -> tables as $curTable => $columns ) {
if ( ! $table || $table == $curTable ) {
$out .= 'DROP TABLE ' . $this -> fullTableName ( $curTable ) . " \n " ;
}
}
return $out ;
}
2008-05-30 11:40:08 +00:00
}