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 /**
0025  * @see Zend_Db_Adapter_Abstract
0026  */
0027 // require_once 'Zend/Db/Adapter/Abstract.php';
0028 
0029 /**
0030  * @see Zend_Db_Profiler
0031  */
0032 // require_once 'Zend/Db/Profiler.php';
0033 
0034 /**
0035  * @see Zend_Db_Select
0036  */
0037 // require_once 'Zend/Db/Select.php';
0038 
0039 /**
0040  * @see Zend_Db_Statement_Mysqli
0041  */
0042 // require_once 'Zend/Db/Statement/Mysqli.php';
0043 
0044 
0045 /**
0046  * @category   Zend
0047  * @package    Zend_Db
0048  * @subpackage Adapter
0049  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0050  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0051  */
0052 class Zend_Db_Adapter_Mysqli extends Zend_Db_Adapter_Abstract
0053 {
0054 
0055     /**
0056      * Keys are UPPERCASE SQL datatypes or the constants
0057      * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
0058      *
0059      * Values are:
0060      * 0 = 32-bit integer
0061      * 1 = 64-bit integer
0062      * 2 = float or decimal
0063      *
0064      * @var array Associative array of datatypes to values 0, 1, or 2.
0065      */
0066     protected $_numericDataTypes = array(
0067         Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
0068         Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
0069         Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
0070         'INT'                => Zend_Db::INT_TYPE,
0071         'INTEGER'            => Zend_Db::INT_TYPE,
0072         'MEDIUMINT'          => Zend_Db::INT_TYPE,
0073         'SMALLINT'           => Zend_Db::INT_TYPE,
0074         'TINYINT'            => Zend_Db::INT_TYPE,
0075         'BIGINT'             => Zend_Db::BIGINT_TYPE,
0076         'SERIAL'             => Zend_Db::BIGINT_TYPE,
0077         'DEC'                => Zend_Db::FLOAT_TYPE,
0078         'DECIMAL'            => Zend_Db::FLOAT_TYPE,
0079         'DOUBLE'             => Zend_Db::FLOAT_TYPE,
0080         'DOUBLE PRECISION'   => Zend_Db::FLOAT_TYPE,
0081         'FIXED'              => Zend_Db::FLOAT_TYPE,
0082         'FLOAT'              => Zend_Db::FLOAT_TYPE
0083     );
0084 
0085     /**
0086      * @var Zend_Db_Statement_Mysqli
0087      */
0088     protected $_stmt = null;
0089 
0090     /**
0091      * Default class name for a DB statement.
0092      *
0093      * @var string
0094      */
0095     protected $_defaultStmtClass = 'Zend_Db_Statement_Mysqli';
0096 
0097     /**
0098      * Quote a raw string.
0099      *
0100      * @param mixed $value Raw string
0101      *
0102      * @return string           Quoted string
0103      */
0104     protected function _quote($value)
0105     {
0106         if (is_int($value) || is_float($value)) {
0107             return $value;
0108         }
0109         $this->_connect();
0110         return "'" . $this->_connection->real_escape_string($value) . "'";
0111     }
0112 
0113     /**
0114      * Returns the symbol the adapter uses for delimiting identifiers.
0115      *
0116      * @return string
0117      */
0118     public function getQuoteIdentifierSymbol()
0119     {
0120         return "`";
0121     }
0122 
0123     /**
0124      * Returns a list of the tables in the database.
0125      *
0126      * @return array
0127      */
0128     public function listTables()
0129     {
0130         $result = array();
0131         // Use mysqli extension API, because SHOW doesn't work
0132         // well as a prepared statement on MySQL 4.1.
0133         $sql = 'SHOW TABLES';
0134         if ($queryResult = $this->getConnection()->query($sql)) {
0135             while ($row = $queryResult->fetch_row()) {
0136                 $result[] = $row[0];
0137             }
0138             $queryResult->close();
0139         } else {
0140             /**
0141              * @see Zend_Db_Adapter_Mysqli_Exception
0142              */
0143             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0144             throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
0145         }
0146         return $result;
0147     }
0148 
0149     /**
0150      * Returns the column descriptions for a table.
0151      *
0152      * The return value is an associative array keyed by the column name,
0153      * as returned by the RDBMS.
0154      *
0155      * The value of each array element is an associative array
0156      * with the following keys:
0157      *
0158      * SCHEMA_NAME      => string; name of database or schema
0159      * TABLE_NAME       => string;
0160      * COLUMN_NAME      => string; column name
0161      * COLUMN_POSITION  => number; ordinal position of column in table
0162      * DATA_TYPE        => string; SQL datatype name of column
0163      * DEFAULT          => string; default expression of column, null if none
0164      * NULLABLE         => boolean; true if column can have nulls
0165      * LENGTH           => number; length of CHAR/VARCHAR
0166      * SCALE            => number; scale of NUMERIC/DECIMAL
0167      * PRECISION        => number; precision of NUMERIC/DECIMAL
0168      * UNSIGNED         => boolean; unsigned property of an integer type
0169      * PRIMARY          => boolean; true if column is part of the primary key
0170      * PRIMARY_POSITION => integer; position of column in primary key
0171      * IDENTITY         => integer; true if column is auto-generated with unique values
0172      *
0173      * @param string $tableName
0174      * @param string $schemaName OPTIONAL
0175      * @return array
0176      */
0177     public function describeTable($tableName, $schemaName = null)
0178     {
0179         /**
0180          * @todo  use INFORMATION_SCHEMA someday when
0181          * MySQL's implementation isn't too slow.
0182          */
0183 
0184         if ($schemaName) {
0185             $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
0186         } else {
0187             $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
0188         }
0189 
0190         /**
0191          * Use mysqli extension API, because DESCRIBE doesn't work
0192          * well as a prepared statement on MySQL 4.1.
0193          */
0194         if ($queryResult = $this->getConnection()->query($sql)) {
0195             while ($row = $queryResult->fetch_assoc()) {
0196                 $result[] = $row;
0197             }
0198             $queryResult->close();
0199         } else {
0200             /**
0201              * @see Zend_Db_Adapter_Mysqli_Exception
0202              */
0203             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0204             throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
0205         }
0206 
0207         $desc = array();
0208 
0209         $row_defaults = array(
0210             'Length'          => null,
0211             'Scale'           => null,
0212             'Precision'       => null,
0213             'Unsigned'        => null,
0214             'Primary'         => false,
0215             'PrimaryPosition' => null,
0216             'Identity'        => false
0217         );
0218         $i = 1;
0219         $p = 1;
0220         foreach ($result as $key => $row) {
0221             $row = array_merge($row_defaults, $row);
0222             if (preg_match('/unsigned/', $row['Type'])) {
0223                 $row['Unsigned'] = true;
0224             }
0225             if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) {
0226                 $row['Type'] = $matches[1];
0227                 $row['Length'] = $matches[2];
0228             } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) {
0229                 $row['Type'] = 'decimal';
0230                 $row['Precision'] = $matches[1];
0231                 $row['Scale'] = $matches[2];
0232             } else if (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) {
0233                 $row['Type'] = 'float';
0234                 $row['Precision'] = $matches[1];
0235                 $row['Scale'] = $matches[2];
0236             } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) {
0237                 $row['Type'] = $matches[1];
0238                 /**
0239                  * The optional argument of a MySQL int type is not precision
0240                  * or length; it is only a hint for display width.
0241                  */
0242             }
0243             if (strtoupper($row['Key']) == 'PRI') {
0244                 $row['Primary'] = true;
0245                 $row['PrimaryPosition'] = $p;
0246                 if ($row['Extra'] == 'auto_increment') {
0247                     $row['Identity'] = true;
0248                 } else {
0249                     $row['Identity'] = false;
0250                 }
0251                 ++$p;
0252             }
0253             $desc[$this->foldCase($row['Field'])] = array(
0254                 'SCHEMA_NAME'      => null, // @todo
0255                 'TABLE_NAME'       => $this->foldCase($tableName),
0256                 'COLUMN_NAME'      => $this->foldCase($row['Field']),
0257                 'COLUMN_POSITION'  => $i,
0258                 'DATA_TYPE'        => $row['Type'],
0259                 'DEFAULT'          => $row['Default'],
0260                 'NULLABLE'         => (bool) ($row['Null'] == 'YES'),
0261                 'LENGTH'           => $row['Length'],
0262                 'SCALE'            => $row['Scale'],
0263                 'PRECISION'        => $row['Precision'],
0264                 'UNSIGNED'         => $row['Unsigned'],
0265                 'PRIMARY'          => $row['Primary'],
0266                 'PRIMARY_POSITION' => $row['PrimaryPosition'],
0267                 'IDENTITY'         => $row['Identity']
0268             );
0269             ++$i;
0270         }
0271         return $desc;
0272     }
0273 
0274     /**
0275      * Creates a connection to the database.
0276      *
0277      * @return void
0278      * @throws Zend_Db_Adapter_Mysqli_Exception
0279      */
0280     protected function _connect()
0281     {
0282         if ($this->_connection) {
0283             return;
0284         }
0285 
0286         if (!extension_loaded('mysqli')) {
0287             /**
0288              * @see Zend_Db_Adapter_Mysqli_Exception
0289              */
0290             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0291             throw new Zend_Db_Adapter_Mysqli_Exception('The Mysqli extension is required for this adapter but the extension is not loaded');
0292         }
0293 
0294         if (isset($this->_config['port'])) {
0295             $port = (integer) $this->_config['port'];
0296         } else {
0297             $port = null;
0298         }
0299 
0300         if (isset($this->_config['socket'])) {
0301             $socket = $this->_config['socket'];
0302         } else {
0303             $socket = null;
0304         }
0305 
0306         $this->_connection = mysqli_init();
0307 
0308         if(!empty($this->_config['driver_options'])) {
0309             foreach($this->_config['driver_options'] as $option=>$value) {
0310                 if(is_string($option)) {
0311                     // Suppress warnings here
0312                     // Ignore it if it's not a valid constant
0313                     $option = @constant(strtoupper($option));
0314                     if($option === null)
0315                         continue;
0316                 }
0317                 mysqli_options($this->_connection, $option, $value);
0318             }
0319         }
0320 
0321         // Suppress connection warnings here.
0322         // Throw an exception instead.
0323         $_isConnected = @mysqli_real_connect(
0324             $this->_connection,
0325             $this->_config['host'],
0326             $this->_config['username'],
0327             $this->_config['password'],
0328             $this->_config['dbname'],
0329             $port,
0330             $socket
0331         );
0332 
0333         if ($_isConnected === false || mysqli_connect_errno()) {
0334 
0335             $this->closeConnection();
0336             /**
0337              * @see Zend_Db_Adapter_Mysqli_Exception
0338              */
0339             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0340             throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error());
0341         }
0342 
0343         if (!empty($this->_config['charset'])) {
0344             mysqli_set_charset($this->_connection, $this->_config['charset']);
0345         }
0346     }
0347 
0348     /**
0349      * Test if a connection is active
0350      *
0351      * @return boolean
0352      */
0353     public function isConnected()
0354     {
0355         return ((bool) ($this->_connection instanceof mysqli));
0356     }
0357 
0358     /**
0359      * Force the connection to close.
0360      *
0361      * @return void
0362      */
0363     public function closeConnection()
0364     {
0365         if ($this->isConnected()) {
0366             $this->_connection->close();
0367         }
0368         $this->_connection = null;
0369     }
0370 
0371     /**
0372      * Prepare a statement and return a PDOStatement-like object.
0373      *
0374      * @param  string  $sql  SQL query
0375      * @return Zend_Db_Statement_Mysqli
0376      */
0377     public function prepare($sql)
0378     {
0379         $this->_connect();
0380         if ($this->_stmt) {
0381             $this->_stmt->close();
0382         }
0383         $stmtClass = $this->_defaultStmtClass;
0384         if (!class_exists($stmtClass)) {
0385             // require_once 'Zend/Loader.php';
0386             Zend_Loader::loadClass($stmtClass);
0387         }
0388         $stmt = new $stmtClass($this, $sql);
0389         if ($stmt === false) {
0390             return false;
0391         }
0392         $stmt->setFetchMode($this->_fetchMode);
0393         $this->_stmt = $stmt;
0394         return $stmt;
0395     }
0396 
0397     /**
0398      * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
0399      *
0400      * As a convention, on RDBMS brands that support sequences
0401      * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
0402      * from the arguments and returns the last id generated by that sequence.
0403      * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
0404      * returns the last value generated for such a column, and the table name
0405      * argument is disregarded.
0406      *
0407      * MySQL does not support sequences, so $tableName and $primaryKey are ignored.
0408      *
0409      * @param string $tableName   OPTIONAL Name of table.
0410      * @param string $primaryKey  OPTIONAL Name of primary key column.
0411      * @return string
0412      * @todo Return value should be int?
0413      */
0414     public function lastInsertId($tableName = null, $primaryKey = null)
0415     {
0416         $mysqli = $this->_connection;
0417         return (string) $mysqli->insert_id;
0418     }
0419 
0420     /**
0421      * Begin a transaction.
0422      *
0423      * @return void
0424      */
0425     protected function _beginTransaction()
0426     {
0427         $this->_connect();
0428         $this->_connection->autocommit(false);
0429     }
0430 
0431     /**
0432      * Commit a transaction.
0433      *
0434      * @return void
0435      */
0436     protected function _commit()
0437     {
0438         $this->_connect();
0439         $this->_connection->commit();
0440         $this->_connection->autocommit(true);
0441     }
0442 
0443     /**
0444      * Roll-back a transaction.
0445      *
0446      * @return void
0447      */
0448     protected function _rollBack()
0449     {
0450         $this->_connect();
0451         $this->_connection->rollback();
0452         $this->_connection->autocommit(true);
0453     }
0454 
0455     /**
0456      * Set the fetch mode.
0457      *
0458      * @param int $mode
0459      * @return void
0460      * @throws Zend_Db_Adapter_Mysqli_Exception
0461      */
0462     public function setFetchMode($mode)
0463     {
0464         switch ($mode) {
0465             case Zend_Db::FETCH_LAZY:
0466             case Zend_Db::FETCH_ASSOC:
0467             case Zend_Db::FETCH_NUM:
0468             case Zend_Db::FETCH_BOTH:
0469             case Zend_Db::FETCH_NAMED:
0470             case Zend_Db::FETCH_OBJ:
0471                 $this->_fetchMode = $mode;
0472                 break;
0473             case Zend_Db::FETCH_BOUND: // bound to PHP variable
0474                 /**
0475                  * @see Zend_Db_Adapter_Mysqli_Exception
0476                  */
0477                 // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0478                 throw new Zend_Db_Adapter_Mysqli_Exception('FETCH_BOUND is not supported yet');
0479                 break;
0480             default:
0481                 /**
0482                  * @see Zend_Db_Adapter_Mysqli_Exception
0483                  */
0484                 // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0485                 throw new Zend_Db_Adapter_Mysqli_Exception("Invalid fetch mode '$mode' specified");
0486         }
0487     }
0488 
0489     /**
0490      * Adds an adapter-specific LIMIT clause to the SELECT statement.
0491      *
0492      * @param string $sql
0493      * @param int $count
0494      * @param int $offset OPTIONAL
0495      * @return string
0496      */
0497     public function limit($sql, $count, $offset = 0)
0498     {
0499         $count = intval($count);
0500         if ($count <= 0) {
0501             /**
0502              * @see Zend_Db_Adapter_Mysqli_Exception
0503              */
0504             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0505             throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument count=$count is not valid");
0506         }
0507 
0508         $offset = intval($offset);
0509         if ($offset < 0) {
0510             /**
0511              * @see Zend_Db_Adapter_Mysqli_Exception
0512              */
0513             // require_once 'Zend/Db/Adapter/Mysqli/Exception.php';
0514             throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument offset=$offset is not valid");
0515         }
0516 
0517         $sql .= " LIMIT $count";
0518         if ($offset > 0) {
0519             $sql .= " OFFSET $offset";
0520         }
0521 
0522         return $sql;
0523     }
0524 
0525     /**
0526      * Check if the adapter supports real SQL parameters.
0527      *
0528      * @param string $type 'positional' or 'named'
0529      * @return bool
0530      */
0531     public function supportsParameters($type)
0532     {
0533         switch ($type) {
0534             case 'positional':
0535                 return true;
0536             case 'named':
0537             default:
0538                 return false;
0539         }
0540     }
0541 
0542     /**
0543      * Retrieve server version in PHP style
0544      *
0545      *@return string
0546      */
0547     public function getServerVersion()
0548     {
0549         $this->_connect();
0550         $version = $this->_connection->server_version;
0551         $major = (int) ($version / 10000);
0552         $minor = (int) ($version % 10000 / 100);
0553         $revision = (int) ($version % 100);
0554         return $major . '.' . $minor . '.' . $revision;
0555     }
0556 }