File indexing completed on 2025-01-19 05:21:02

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Db
0017  * @subpackage Adapter
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Db_Adapter_Abstract
0025  */
0026 // require_once 'Zend/Db/Adapter/Abstract.php';
0027 
0028 /**
0029  * @see Zend_Db_Statement_Oracle
0030  */
0031 // require_once 'Zend/Db/Statement/Oracle.php';
0032 
0033 /**
0034  * @category   Zend
0035  * @package    Zend_Db
0036  * @subpackage Adapter
0037  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0038  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0039  */
0040 class Zend_Db_Adapter_Oracle extends Zend_Db_Adapter_Abstract
0041 {
0042     /**
0043      * User-provided configuration.
0044      *
0045      * Basic keys are:
0046      *
0047      * username => (string) Connect to the database as this username.
0048      * password => (string) Password associated with the username.
0049      * dbname   => Either the name of the local Oracle instance, or the
0050      *             name of the entry in tnsnames.ora to which you want to connect.
0051      * persistent => (boolean) Set TRUE to use a persistent connection
0052      * @var array
0053      */
0054     protected $_config = array(
0055         'dbname'       => null,
0056         'username'     => null,
0057         'password'     => null,
0058         'persistent'   => false
0059     );
0060 
0061     /**
0062      * Keys are UPPERCASE SQL datatypes or the constants
0063      * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
0064      *
0065      * Values are:
0066      * 0 = 32-bit integer
0067      * 1 = 64-bit integer
0068      * 2 = float or decimal
0069      *
0070      * @var array Associative array of datatypes to values 0, 1, or 2.
0071      */
0072     protected $_numericDataTypes = array(
0073         Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
0074         Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
0075         Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
0076         'BINARY_DOUBLE'      => Zend_Db::FLOAT_TYPE,
0077         'BINARY_FLOAT'       => Zend_Db::FLOAT_TYPE,
0078         'NUMBER'             => Zend_Db::FLOAT_TYPE,
0079     );
0080 
0081     /**
0082      * @var integer
0083      */
0084     protected $_execute_mode = null;
0085 
0086     /**
0087      * Default class name for a DB statement.
0088      *
0089      * @var string
0090      */
0091     protected $_defaultStmtClass = 'Zend_Db_Statement_Oracle';
0092 
0093     /**
0094      * Check if LOB field are returned as string
0095      * instead of OCI-Lob object
0096      *
0097      * @var boolean
0098      */
0099     protected $_lobAsString = null;
0100 
0101     /**
0102      * Creates a connection resource.
0103      *
0104      * @return void
0105      * @throws Zend_Db_Adapter_Oracle_Exception
0106      */
0107     protected function _connect()
0108     {
0109         if (is_resource($this->_connection)) {
0110             // connection already exists
0111             return;
0112         }
0113 
0114         if (!extension_loaded('oci8')) {
0115             /**
0116              * @see Zend_Db_Adapter_Oracle_Exception
0117              */
0118             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0119             throw new Zend_Db_Adapter_Oracle_Exception('The OCI8 extension is required for this adapter but the extension is not loaded');
0120         }
0121 
0122         $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
0123 
0124         $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect';
0125 
0126         $this->_connection = @$connectionFuncName(
0127                 $this->_config['username'],
0128                 $this->_config['password'],
0129                 $this->_config['dbname'],
0130                 $this->_config['charset']);
0131 
0132         // check the connection
0133         if (!$this->_connection) {
0134             /**
0135              * @see Zend_Db_Adapter_Oracle_Exception
0136              */
0137             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0138             throw new Zend_Db_Adapter_Oracle_Exception(oci_error());
0139         }
0140     }
0141 
0142     /**
0143      * Test if a connection is active
0144      *
0145      * @return boolean
0146      */
0147     public function isConnected()
0148     {
0149         return ((bool) (is_resource($this->_connection)
0150                     && (get_resource_type($this->_connection) == 'oci8 connection'
0151                      || get_resource_type($this->_connection) == 'oci8 persistent connection')));
0152         }
0153 
0154     /**
0155      * Force the connection to close.
0156      *
0157      * @return void
0158      */
0159     public function closeConnection()
0160     {
0161         if ($this->isConnected()) {
0162             oci_close($this->_connection);
0163         }
0164         $this->_connection = null;
0165     }
0166 
0167     /**
0168      * Activate/deactivate return of LOB as string
0169      *
0170      * @param string $lob_as_string
0171      * @return Zend_Db_Adapter_Oracle
0172      */
0173     public function setLobAsString($lobAsString)
0174     {
0175         $this->_lobAsString = (bool) $lobAsString;
0176         return $this;
0177     }
0178 
0179     /**
0180      * Return whether or not LOB are returned as string
0181      *
0182      * @return boolean
0183      */
0184     public function getLobAsString()
0185     {
0186         if ($this->_lobAsString === null) {
0187             // if never set by user, we use driver option if it exists otherwise false
0188             if (isset($this->_config['driver_options']) &&
0189                 isset($this->_config['driver_options']['lob_as_string'])) {
0190                 $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string'];
0191             } else {
0192                 $this->_lobAsString = false;
0193             }
0194         }
0195         return $this->_lobAsString;
0196     }
0197 
0198     /**
0199      * Returns an SQL statement for preparation.
0200      *
0201      * @param string $sql The SQL statement with placeholders.
0202      * @return Zend_Db_Statement_Oracle
0203      */
0204     public function prepare($sql)
0205     {
0206         $this->_connect();
0207         $stmtClass = $this->_defaultStmtClass;
0208         if (!class_exists($stmtClass)) {
0209             // require_once 'Zend/Loader.php';
0210             Zend_Loader::loadClass($stmtClass);
0211         }
0212         $stmt = new $stmtClass($this, $sql);
0213         if ($stmt instanceof Zend_Db_Statement_Oracle) {
0214             $stmt->setLobAsString($this->getLobAsString());
0215         }
0216         $stmt->setFetchMode($this->_fetchMode);
0217         return $stmt;
0218     }
0219 
0220     /**
0221      * Quote a raw string.
0222      *
0223      * @param string $value     Raw string
0224      * @return string           Quoted string
0225      */
0226     protected function _quote($value)
0227     {
0228         if (is_int($value) || is_float($value)) {
0229             return $value;
0230         }
0231         $value = str_replace("'", "''", $value);
0232         return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
0233     }
0234 
0235     /**
0236      * Quote a table identifier and alias.
0237      *
0238      * @param string|array|Zend_Db_Expr $ident The identifier or expression.
0239      * @param string $alias An alias for the table.
0240      * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
0241      * @return string The quoted identifier and alias.
0242      */
0243     public function quoteTableAs($ident, $alias = null, $auto = false)
0244     {
0245         // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
0246         return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
0247     }
0248 
0249     /**
0250      * Return the most recent value from the specified sequence in the database.
0251      * This is supported only on RDBMS brands that support sequences
0252      * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
0253      *
0254      * @param string $sequenceName
0255      * @return string
0256      */
0257     public function lastSequenceId($sequenceName)
0258     {
0259         $this->_connect();
0260         $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual';
0261         $value = $this->fetchOne($sql);
0262         return $value;
0263     }
0264 
0265     /**
0266      * Generate a new value from the specified sequence in the database, and return it.
0267      * This is supported only on RDBMS brands that support sequences
0268      * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
0269      *
0270      * @param string $sequenceName
0271      * @return string
0272      */
0273     public function nextSequenceId($sequenceName)
0274     {
0275         $this->_connect();
0276         $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual';
0277         $value = $this->fetchOne($sql);
0278         return $value;
0279     }
0280 
0281     /**
0282      * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
0283      *
0284      * As a convention, on RDBMS brands that support sequences
0285      * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
0286      * from the arguments and returns the last id generated by that sequence.
0287      * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
0288      * returns the last value generated for such a column, and the table name
0289      * argument is disregarded.
0290      *
0291      * Oracle does not support IDENTITY columns, so if the sequence is not
0292      * specified, this method returns null.
0293      *
0294      * @param string $tableName   OPTIONAL Name of table.
0295      * @param string $primaryKey  OPTIONAL Name of primary key column.
0296      * @return string
0297      */
0298     public function lastInsertId($tableName = null, $primaryKey = null)
0299     {
0300         if ($tableName !== null) {
0301             $sequenceName = $tableName;
0302             if ($primaryKey) {
0303                 $sequenceName .= "_$primaryKey";
0304             }
0305             $sequenceName .= '_seq';
0306             return $this->lastSequenceId($sequenceName);
0307         }
0308 
0309         // No support for IDENTITY columns; return null
0310         return null;
0311     }
0312 
0313     /**
0314      * Returns a list of the tables in the database.
0315      *
0316      * @return array
0317      */
0318     public function listTables()
0319     {
0320         $this->_connect();
0321         $data = $this->fetchCol('SELECT table_name FROM all_tables');
0322         return $data;
0323     }
0324 
0325     /**
0326      * Returns the column descriptions for a table.
0327      *
0328      * The return value is an associative array keyed by the column name,
0329      * as returned by the RDBMS.
0330      *
0331      * The value of each array element is an associative array
0332      * with the following keys:
0333      *
0334      * SCHEMA_NAME      => string; name of schema
0335      * TABLE_NAME       => string;
0336      * COLUMN_NAME      => string; column name
0337      * COLUMN_POSITION  => number; ordinal position of column in table
0338      * DATA_TYPE        => string; SQL datatype name of column
0339      * DEFAULT          => string; default expression of column, null if none
0340      * NULLABLE         => boolean; true if column can have nulls
0341      * LENGTH           => number; length of CHAR/VARCHAR
0342      * SCALE            => number; scale of NUMERIC/DECIMAL
0343      * PRECISION        => number; precision of NUMERIC/DECIMAL
0344      * UNSIGNED         => boolean; unsigned property of an integer type
0345      * PRIMARY          => boolean; true if column is part of the primary key
0346      * PRIMARY_POSITION => integer; position of column in primary key
0347      * IDENTITY         => integer; true if column is auto-generated with unique values
0348      *
0349      * @todo Discover integer unsigned property.
0350      *
0351      * @param string $tableName
0352      * @param string $schemaName OPTIONAL
0353      * @return array
0354      */
0355     public function describeTable($tableName, $schemaName = null)
0356     {
0357         $version = $this->getServerVersion();
0358         if (($version === null) || version_compare($version, '9.0.0', '>=')) {
0359             $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
0360                     TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
0361                     TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
0362                 FROM ALL_TAB_COLUMNS TC
0363                 LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
0364                     ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
0365                   ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
0366                 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
0367             $bind[':TBNAME'] = $tableName;
0368             if ($schemaName) {
0369                 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
0370                 $bind[':SCNAME'] = $schemaName;
0371             }
0372             $sql .= ' ORDER BY TC.COLUMN_ID';
0373         } else {
0374             $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
0375                 from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
0376                   WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
0377                     AND ACC.TABLE_NAME = AC.TABLE_NAME
0378                     AND ACC.OWNER = AC.OWNER
0379                     AND AC.CONSTRAINT_TYPE = 'P'
0380                     AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
0381             $bind[':TBNAME'] = $tableName;
0382             if ($schemaName) {
0383                 $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
0384                 $bind[':SCNAME'] = $schemaName;
0385             }
0386             $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
0387                     TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
0388                     TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
0389                 FROM ALL_TAB_COLUMNS TC, ($subSql) CC
0390                 WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
0391                   AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
0392             if ($schemaName) {
0393                 $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
0394             }
0395             $sql .= ' ORDER BY TC.COLUMN_ID';
0396         }
0397 
0398         $stmt = $this->query($sql, $bind);
0399 
0400         /**
0401          * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
0402          */
0403         $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
0404 
0405         $table_name      = 0;
0406         $owner           = 1;
0407         $column_name     = 2;
0408         $data_type       = 3;
0409         $data_default    = 4;
0410         $nullable        = 5;
0411         $column_id       = 6;
0412         $data_length     = 7;
0413         $data_scale      = 8;
0414         $data_precision  = 9;
0415         $constraint_type = 10;
0416         $position        = 11;
0417 
0418         $desc = array();
0419         foreach ($result as $key => $row) {
0420             list ($primary, $primaryPosition, $identity) = array(false, null, false);
0421             if ($row[$constraint_type] == 'P') {
0422                 $primary = true;
0423                 $primaryPosition = $row[$position];
0424                 /**
0425                  * Oracle does not support auto-increment keys.
0426                  */
0427                 $identity = false;
0428             }
0429             $desc[$this->foldCase($row[$column_name])] = array(
0430                 'SCHEMA_NAME'      => $this->foldCase($row[$owner]),
0431                 'TABLE_NAME'       => $this->foldCase($row[$table_name]),
0432                 'COLUMN_NAME'      => $this->foldCase($row[$column_name]),
0433                 'COLUMN_POSITION'  => $row[$column_id],
0434                 'DATA_TYPE'        => $row[$data_type],
0435                 'DEFAULT'          => $row[$data_default],
0436                 'NULLABLE'         => (bool) ($row[$nullable] == 'Y'),
0437                 'LENGTH'           => $row[$data_length],
0438                 'SCALE'            => $row[$data_scale],
0439                 'PRECISION'        => $row[$data_precision],
0440                 'UNSIGNED'         => null, // @todo
0441                 'PRIMARY'          => $primary,
0442                 'PRIMARY_POSITION' => $primaryPosition,
0443                 'IDENTITY'         => $identity
0444             );
0445         }
0446         return $desc;
0447     }
0448 
0449     /**
0450      * Leave autocommit mode and begin a transaction.
0451      *
0452      * @return void
0453      */
0454     protected function _beginTransaction()
0455     {
0456         $this->_setExecuteMode(OCI_DEFAULT);
0457     }
0458 
0459     /**
0460      * Commit a transaction and return to autocommit mode.
0461      *
0462      * @return void
0463      * @throws Zend_Db_Adapter_Oracle_Exception
0464      */
0465     protected function _commit()
0466     {
0467         if (!oci_commit($this->_connection)) {
0468             /**
0469              * @see Zend_Db_Adapter_Oracle_Exception
0470              */
0471             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0472             throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
0473         }
0474         $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
0475     }
0476 
0477     /**
0478      * Roll back a transaction and return to autocommit mode.
0479      *
0480      * @return void
0481      * @throws Zend_Db_Adapter_Oracle_Exception
0482      */
0483     protected function _rollBack()
0484     {
0485         if (!oci_rollback($this->_connection)) {
0486             /**
0487              * @see Zend_Db_Adapter_Oracle_Exception
0488              */
0489             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0490             throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
0491         }
0492         $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
0493     }
0494 
0495     /**
0496      * Set the fetch mode.
0497      *
0498      * @todo Support FETCH_CLASS and FETCH_INTO.
0499      *
0500      * @param integer $mode A fetch mode.
0501      * @return void
0502      * @throws Zend_Db_Adapter_Oracle_Exception
0503      */
0504     public function setFetchMode($mode)
0505     {
0506         switch ($mode) {
0507             case Zend_Db::FETCH_NUM:   // seq array
0508             case Zend_Db::FETCH_ASSOC: // assoc array
0509             case Zend_Db::FETCH_BOTH:  // seq+assoc array
0510             case Zend_Db::FETCH_OBJ:   // object
0511                 $this->_fetchMode = $mode;
0512                 break;
0513             case Zend_Db::FETCH_BOUND: // bound to PHP variable
0514                 /**
0515                  * @see Zend_Db_Adapter_Oracle_Exception
0516                  */
0517                 // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0518                 throw new Zend_Db_Adapter_Oracle_Exception('FETCH_BOUND is not supported yet');
0519                 break;
0520             default:
0521                 /**
0522                  * @see Zend_Db_Adapter_Oracle_Exception
0523                  */
0524                 // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0525                 throw new Zend_Db_Adapter_Oracle_Exception("Invalid fetch mode '$mode' specified");
0526                 break;
0527         }
0528     }
0529 
0530     /**
0531      * Adds an adapter-specific LIMIT clause to the SELECT statement.
0532      *
0533      * @param string $sql
0534      * @param integer $count
0535      * @param integer $offset OPTIONAL
0536      * @return string
0537      * @throws Zend_Db_Adapter_Oracle_Exception
0538      */
0539     public function limit($sql, $count, $offset = 0)
0540     {
0541         $count = intval($count);
0542         if ($count <= 0) {
0543             /**
0544              * @see Zend_Db_Adapter_Oracle_Exception
0545              */
0546             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0547             throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument count=$count is not valid");
0548         }
0549 
0550         $offset = intval($offset);
0551         if ($offset < 0) {
0552             /**
0553              * @see Zend_Db_Adapter_Oracle_Exception
0554              */
0555             // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0556             throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument offset=$offset is not valid");
0557         }
0558 
0559         /**
0560          * Oracle does not implement the LIMIT clause as some RDBMS do.
0561          * We have to simulate it with subqueries and ROWNUM.
0562          * Unfortunately because we use the column wildcard "*",
0563          * this puts an extra column into the query result set.
0564          */
0565         $limit_sql = "SELECT z2.*
0566             FROM (
0567                 SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
0568                 FROM (
0569                     " . $sql . "
0570                 ) z1
0571             ) z2
0572             WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
0573         return $limit_sql;
0574     }
0575 
0576     /**
0577      * @param integer $mode
0578      * @throws Zend_Db_Adapter_Oracle_Exception
0579      */
0580     private function _setExecuteMode($mode)
0581     {
0582         switch($mode) {
0583             case OCI_COMMIT_ON_SUCCESS:
0584             case OCI_DEFAULT:
0585             case OCI_DESCRIBE_ONLY:
0586                 $this->_execute_mode = $mode;
0587                 break;
0588             default:
0589                 /**
0590                  * @see Zend_Db_Adapter_Oracle_Exception
0591                  */
0592                 // require_once 'Zend/Db/Adapter/Oracle/Exception.php';
0593                 throw new Zend_Db_Adapter_Oracle_Exception("Invalid execution mode '$mode' specified");
0594                 break;
0595         }
0596     }
0597 
0598     /**
0599      * @return int
0600      */
0601     public function _getExecuteMode()
0602     {
0603         return $this->_execute_mode;
0604     }
0605 
0606     /**
0607      * Check if the adapter supports real SQL parameters.
0608      *
0609      * @param string $type 'positional' or 'named'
0610      * @return bool
0611      */
0612     public function supportsParameters($type)
0613     {
0614         switch ($type) {
0615             case 'named':
0616                 return true;
0617             case 'positional':
0618             default:
0619                 return false;
0620         }
0621     }
0622 
0623     /**
0624      * Retrieve server version in PHP style
0625      *
0626      * @return string
0627      */
0628     public function getServerVersion()
0629     {
0630         $this->_connect();
0631         $version = oci_server_version($this->_connection);
0632         if ($version !== false) {
0633             $matches = null;
0634             if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
0635                 return $matches[1];
0636             } else {
0637                 return null;
0638             }
0639         } else {
0640             return null;
0641         }
0642     }
0643 }