File indexing completed on 2025-01-26 05:24:56
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_Pdo_Abstract 0026 */ 0027 // require_once 'Zend/Db/Adapter/Pdo/Abstract.php'; 0028 0029 0030 /** 0031 * Class for connecting to PostgreSQL databases and performing common operations. 0032 * 0033 * @category Zend 0034 * @package Zend_Db 0035 * @subpackage Adapter 0036 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0037 * @license http://framework.zend.com/license/new-bsd New BSD License 0038 */ 0039 class Zend_Db_Adapter_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Abstract 0040 { 0041 0042 /** 0043 * PDO type. 0044 * 0045 * @var string 0046 */ 0047 protected $_pdoType = 'pgsql'; 0048 0049 /** 0050 * Keys are UPPERCASE SQL datatypes or the constants 0051 * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE. 0052 * 0053 * Values are: 0054 * 0 = 32-bit integer 0055 * 1 = 64-bit integer 0056 * 2 = float or decimal 0057 * 0058 * @var array Associative array of datatypes to values 0, 1, or 2. 0059 */ 0060 protected $_numericDataTypes = array( 0061 Zend_Db::INT_TYPE => Zend_Db::INT_TYPE, 0062 Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE, 0063 Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE, 0064 'INTEGER' => Zend_Db::INT_TYPE, 0065 'SERIAL' => Zend_Db::INT_TYPE, 0066 'SMALLINT' => Zend_Db::INT_TYPE, 0067 'BIGINT' => Zend_Db::BIGINT_TYPE, 0068 'BIGSERIAL' => Zend_Db::BIGINT_TYPE, 0069 'DECIMAL' => Zend_Db::FLOAT_TYPE, 0070 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE, 0071 'NUMERIC' => Zend_Db::FLOAT_TYPE, 0072 'REAL' => Zend_Db::FLOAT_TYPE 0073 ); 0074 0075 /** 0076 * Creates a PDO object and connects to the database. 0077 * 0078 * @return void 0079 * @throws Zend_Db_Adapter_Exception 0080 */ 0081 protected function _connect() 0082 { 0083 if ($this->_connection) { 0084 return; 0085 } 0086 0087 parent::_connect(); 0088 0089 if (!empty($this->_config['charset'])) { 0090 $sql = "SET NAMES '" . $this->_config['charset'] . "'"; 0091 $this->_connection->exec($sql); 0092 } 0093 } 0094 0095 /** 0096 * Returns a list of the tables in the database. 0097 * 0098 * @return array 0099 */ 0100 public function listTables() 0101 { 0102 // @todo use a better query with joins instead of subqueries 0103 $sql = "SELECT c.relname AS table_name " 0104 . "FROM pg_class c, pg_user u " 0105 . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' " 0106 . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) " 0107 . "AND c.relname !~ '^(pg_|sql_)' " 0108 . "UNION " 0109 . "SELECT c.relname AS table_name " 0110 . "FROM pg_class c " 0111 . "WHERE c.relkind = 'r' " 0112 . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) " 0113 . "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) " 0114 . "AND c.relname !~ '^pg_'"; 0115 0116 return $this->fetchCol($sql); 0117 } 0118 0119 /** 0120 * Returns the column descriptions for a table. 0121 * 0122 * The return value is an associative array keyed by the column name, 0123 * as returned by the RDBMS. 0124 * 0125 * The value of each array element is an associative array 0126 * with the following keys: 0127 * 0128 * SCHEMA_NAME => string; name of database or schema 0129 * TABLE_NAME => string; 0130 * COLUMN_NAME => string; column name 0131 * COLUMN_POSITION => number; ordinal position of column in table 0132 * DATA_TYPE => string; SQL datatype name of column 0133 * DEFAULT => string; default expression of column, null if none 0134 * NULLABLE => boolean; true if column can have nulls 0135 * LENGTH => number; length of CHAR/VARCHAR 0136 * SCALE => number; scale of NUMERIC/DECIMAL 0137 * PRECISION => number; precision of NUMERIC/DECIMAL 0138 * UNSIGNED => boolean; unsigned property of an integer type 0139 * PRIMARY => boolean; true if column is part of the primary key 0140 * PRIMARY_POSITION => integer; position of column in primary key 0141 * IDENTITY => integer; true if column is auto-generated with unique values 0142 * 0143 * @todo Discover integer unsigned property. 0144 * 0145 * @param string $tableName 0146 * @param string $schemaName OPTIONAL 0147 * @return array 0148 */ 0149 public function describeTable($tableName, $schemaName = null) 0150 { 0151 $sql = "SELECT 0152 a.attnum, 0153 n.nspname, 0154 c.relname, 0155 a.attname AS colname, 0156 t.typname AS type, 0157 a.atttypmod, 0158 FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type, 0159 d.adsrc AS default_value, 0160 a.attnotnull AS notnull, 0161 a.attlen AS length, 0162 co.contype, 0163 ARRAY_TO_STRING(co.conkey, ',') AS conkey 0164 FROM pg_attribute AS a 0165 JOIN pg_class AS c ON a.attrelid = c.oid 0166 JOIN pg_namespace AS n ON c.relnamespace = n.oid 0167 JOIN pg_type AS t ON a.atttypid = t.oid 0168 LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid 0169 AND a.attnum = ANY(co.conkey) AND co.contype = 'p') 0170 LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum 0171 WHERE a.attnum > 0 AND c.relname = ".$this->quote($tableName); 0172 if ($schemaName) { 0173 $sql .= " AND n.nspname = ".$this->quote($schemaName); 0174 } 0175 $sql .= ' ORDER BY a.attnum'; 0176 0177 $stmt = $this->query($sql); 0178 0179 // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection 0180 $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); 0181 0182 $attnum = 0; 0183 $nspname = 1; 0184 $relname = 2; 0185 $colname = 3; 0186 $type = 4; 0187 $atttypemod = 5; 0188 $complete_type = 6; 0189 $default_value = 7; 0190 $notnull = 8; 0191 $length = 9; 0192 $contype = 10; 0193 $conkey = 11; 0194 0195 $desc = array(); 0196 foreach ($result as $key => $row) { 0197 $defaultValue = $row[$default_value]; 0198 if ($row[$type] == 'varchar' || $row[$type] == 'bpchar' ) { 0199 if (preg_match('/character(?: varying)?(?:\((\d+)\))?/', $row[$complete_type], $matches)) { 0200 if (isset($matches[1])) { 0201 $row[$length] = $matches[1]; 0202 } else { 0203 $row[$length] = null; // unlimited 0204 } 0205 } 0206 if (preg_match("/^'(.*?)'::(?:character varying|bpchar)$/", $defaultValue, $matches)) { 0207 $defaultValue = $matches[1]; 0208 } 0209 } 0210 list($primary, $primaryPosition, $identity) = array(false, null, false); 0211 if ($row[$contype] == 'p') { 0212 $primary = true; 0213 $primaryPosition = array_search($row[$attnum], explode(',', $row[$conkey])) + 1; 0214 $identity = (bool) (preg_match('/^nextval/', $row[$default_value])); 0215 } 0216 $desc[$this->foldCase($row[$colname])] = array( 0217 'SCHEMA_NAME' => $this->foldCase($row[$nspname]), 0218 'TABLE_NAME' => $this->foldCase($row[$relname]), 0219 'COLUMN_NAME' => $this->foldCase($row[$colname]), 0220 'COLUMN_POSITION' => $row[$attnum], 0221 'DATA_TYPE' => $row[$type], 0222 'DEFAULT' => $defaultValue, 0223 'NULLABLE' => (bool) ($row[$notnull] != 't'), 0224 'LENGTH' => $row[$length], 0225 'SCALE' => null, // @todo 0226 'PRECISION' => null, // @todo 0227 'UNSIGNED' => null, // @todo 0228 'PRIMARY' => $primary, 0229 'PRIMARY_POSITION' => $primaryPosition, 0230 'IDENTITY' => $identity 0231 ); 0232 } 0233 return $desc; 0234 } 0235 0236 0237 /** 0238 * Adds an adapter-specific LIMIT clause to the SELECT statement. 0239 * 0240 * @param string $sql 0241 * @param integer $count 0242 * @param integer $offset OPTIONAL 0243 * @return string 0244 */ 0245 public function limit($sql, $count, $offset = 0) 0246 { 0247 $count = intval($count); 0248 if ($count <= 0) { 0249 /** 0250 * @see Zend_Db_Adapter_Exception 0251 */ 0252 // require_once 'Zend/Db/Adapter/Exception.php'; 0253 throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); 0254 } 0255 0256 $offset = intval($offset); 0257 if ($offset < 0) { 0258 /** 0259 * @see Zend_Db_Adapter_Exception 0260 */ 0261 // require_once 'Zend/Db/Adapter/Exception.php'; 0262 throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid"); 0263 } 0264 0265 $sql .= " LIMIT $count"; 0266 if ($offset > 0) { 0267 $sql .= " OFFSET $offset"; 0268 } 0269 0270 return $sql; 0271 } 0272 0273 /** 0274 * Return the most recent value from the specified sequence in the database. 0275 * This is supported only on RDBMS brands that support sequences 0276 * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. 0277 * 0278 * @param string $sequenceName 0279 * @return string 0280 */ 0281 public function lastSequenceId($sequenceName) 0282 { 0283 $this->_connect(); 0284 $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName); 0285 $value = $this->fetchOne("SELECT CURRVAL(" 0286 . $this->quote($this->quoteIdentifier($sequenceName, true)) 0287 . ")"); 0288 return $value; 0289 } 0290 0291 /** 0292 * Generate a new value from the specified sequence in the database, and return it. 0293 * This is supported only on RDBMS brands that support sequences 0294 * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null. 0295 * 0296 * @param string $sequenceName 0297 * @return string 0298 */ 0299 public function nextSequenceId($sequenceName) 0300 { 0301 $this->_connect(); 0302 $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName); 0303 $value = $this->fetchOne("SELECT NEXTVAL(" 0304 . $this->quote($this->quoteIdentifier($sequenceName, true)) 0305 . ")"); 0306 return $value; 0307 } 0308 0309 /** 0310 * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column. 0311 * 0312 * As a convention, on RDBMS brands that support sequences 0313 * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence 0314 * from the arguments and returns the last id generated by that sequence. 0315 * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method 0316 * returns the last value generated for such a column, and the table name 0317 * argument is disregarded. 0318 * 0319 * @param string $tableName OPTIONAL Name of table. 0320 * @param string $primaryKey OPTIONAL Name of primary key column. 0321 * @return string 0322 */ 0323 public function lastInsertId($tableName = null, $primaryKey = null) 0324 { 0325 if ($tableName !== null) { 0326 $sequenceName = $tableName; 0327 if ($primaryKey) { 0328 $sequenceName .= "_$primaryKey"; 0329 } 0330 $sequenceName .= '_seq'; 0331 return $this->lastSequenceId($sequenceName); 0332 } 0333 return $this->_connection->lastInsertId($tableName); 0334 } 0335 0336 }