Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 /** 3 * ADOdb PostgreSQL 6.4 driver 4 * 5 * This file is part of ADOdb, a Database Abstraction Layer library for PHP. 6 * 7 * @package ADOdb 8 * @link https://adodb.org Project's web site and documentation 9 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker 10 * 11 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause 12 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option, 13 * any later version. This means you can use it in proprietary products. 14 * See the LICENSE.md file distributed with this source code for details. 15 * @license BSD-3-Clause 16 * @license LGPL-2.1-or-later 17 * 18 * @copyright 2000-2013 John Lim 19 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community 20 */ 21 22 // security - hide paths 23 if (!defined('ADODB_DIR')) die(); 24 25 class ADODB_postgres64 extends ADOConnection{ 26 var $databaseType = 'postgres64'; 27 var $dataProvider = 'postgres'; 28 var $hasInsertID = true; 29 var $_resultid = false; 30 var $concat_operator='||'; 31 var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1"; 32 var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%' 33 and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages', 34 'sql_packages', 'sql_sizing', 'sql_sizing_profiles') 35 union 36 select viewname,'V' from pg_views where viewname not like 'pg\_%'"; 37 //"select tablename from pg_tables where tablename not like 'pg_%' order by 1"; 38 var $isoDates = true; // accepts dates in ISO format 39 var $sysDate = "CURRENT_DATE"; 40 var $sysTimeStamp = "CURRENT_TIMESTAMP"; 41 var $blobEncodeType = 'C'; 42 var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum 43 FROM pg_class c, pg_attribute a,pg_type t 44 WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%' 45 AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; 46 47 // used when schema defined 48 var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum 49 FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n 50 WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) 51 and c.relnamespace=n.oid and n.nspname='%s' 52 and a.attname not like '....%%' AND a.attnum > 0 53 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; 54 55 // get primary key etc -- from Freek Dijkstra 56 var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key 57 FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a 58 WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid 59 AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) 60 AND a.attrelid = bc.oid AND bc.relname = '%s'"; 61 62 var $hasAffectedRows = true; 63 var $hasLimit = false; // set to true for pgsql 7 only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10 64 // below suggested by Freek Dijkstra 65 var $true = 'TRUE'; // string that represents TRUE for a database 66 var $false = 'FALSE'; // string that represents FALSE for a database 67 var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database 68 var $fmtTimeStamp = "'Y-m-d H:i:s'"; // used by DBTimeStamp as the default timestamp fmt. 69 var $hasMoveFirst = true; 70 var $hasGenID = true; 71 var $_genIDSQL = "SELECT NEXTVAL('%s')"; 72 var $_genSeqSQL = "CREATE SEQUENCE %s START %s"; 73 var $_dropSeqSQL = "DROP SEQUENCE %s"; 74 var $metaDefaultsSQL = "SELECT d.adnum as num, d.adsrc as def from pg_attrdef d, pg_class c where d.adrelid=c.oid and c.relname='%s' order by d.adnum"; 75 var $random = 'random()'; /// random function 76 var $autoRollback = true; // apparently pgsql does not autorollback properly before php 4.3.4 77 // http://bugs.php.net/bug.php?id=25404 78 79 var $uniqueIisR = true; 80 var $_bindInputArray = false; // requires postgresql 7.3+ and ability to modify database 81 var $disableBlobs = false; // set to true to disable blob checking, resulting in 2-5% improvement in performance. 82 83 /** @var int $_pnum Number of the last assigned query parameter {@see param()} */ 84 var $_pnum = 0; 85 86 // The last (fmtTimeStamp is not entirely correct: 87 // PostgreSQL also has support for time zones, 88 // and writes these time in this format: "2001-03-01 18:59:26+02". 89 // There is no code for the "+02" time zone information, so I just left that out. 90 // I'm not familiar enough with both ADODB as well as Postgres 91 // to know what the concequences are. The other values are correct (wheren't in 0.94) 92 // -- Freek Dijkstra 93 94 /** 95 * Retrieve Server information. 96 * In addition to server version and description, the function also returns 97 * the client version. 98 * @param bool $detailed If true, retrieve detailed version string (executes 99 * a SQL query) in addition to the version number 100 * @return array|bool Server info or false if version could not be retrieved 101 * e.g. if there is no active connection 102 */ 103 function ServerInfo($detailed = true) 104 { 105 if (empty($this->version['version'])) { 106 // We don't have a connection, so we can't retrieve server info 107 if (!$this->_connectionID) { 108 return false; 109 } 110 111 $version = pg_version($this->_connectionID); 112 $this->version = array( 113 // If PHP has been compiled with PostgreSQL 7.3 or lower, then 114 // server version is not set so we use pg_parameter_status() 115 // which includes logic to obtain values server_version 116 'version' => isset($version['server']) 117 ? $version['server'] 118 : pg_parameter_status($this->_connectionID, 'server_version'), 119 'client' => $version['client'], 120 'description' => null, 121 ); 122 } 123 if ($detailed && $this->version['description'] === null) { 124 $this->version['description'] = $this->GetOne('select version()'); 125 } 126 127 return $this->version; 128 } 129 130 function IfNull( $field, $ifNull ) 131 { 132 return " coalesce($field, $ifNull) "; 133 } 134 135 // get the last id - never tested 136 function pg_insert_id($tablename,$fieldname) 137 { 138 $result=pg_query($this->_connectionID, 'SELECT last_value FROM '. $tablename .'_'. $fieldname .'_seq'); 139 if ($result) { 140 $arr = @pg_fetch_row($result,0); 141 pg_free_result($result); 142 if (isset($arr[0])) return $arr[0]; 143 } 144 return false; 145 } 146 147 /** 148 * Warning from http://www.php.net/manual/function.pg-getlastoid.php: 149 * Using a OID as a unique identifier is not generally wise. 150 * Unless you are very careful, you might end up with a tuple having 151 * a different OID if a database must be reloaded. 152 * 153 * @inheritDoc 154 */ 155 protected function _insertID($table = '', $column = '') 156 { 157 if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false; 158 $oid = pg_last_oid($this->_resultid); 159 // to really return the id, we need the table and column-name, else we can only return the oid != id 160 return empty($table) || empty($column) ? $oid : $this->GetOne("SELECT $column FROM $table WHERE oid=".(int)$oid); 161 } 162 163 function _affectedrows() 164 { 165 if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false; 166 return pg_affected_rows($this->_resultid); 167 } 168 169 170 /** 171 * @return true/false 172 */ 173 function BeginTrans() 174 { 175 if ($this->transOff) return true; 176 $this->transCnt += 1; 177 return pg_query($this->_connectionID, 'begin '.$this->_transmode); 178 } 179 180 function RowLock($tables,$where,$col='1 as adodbignore') 181 { 182 if (!$this->transCnt) $this->BeginTrans(); 183 return $this->GetOne("select $col from $tables where $where for update"); 184 } 185 186 // returns true/false. 187 function CommitTrans($ok=true) 188 { 189 if ($this->transOff) return true; 190 if (!$ok) return $this->RollbackTrans(); 191 192 $this->transCnt -= 1; 193 return pg_query($this->_connectionID, 'commit'); 194 } 195 196 // returns true/false 197 function RollbackTrans() 198 { 199 if ($this->transOff) return true; 200 $this->transCnt -= 1; 201 return pg_query($this->_connectionID, 'rollback'); 202 } 203 204 function MetaTables($ttype=false,$showSchema=false,$mask=false) 205 { 206 $info = $this->ServerInfo(); 207 if ($info['version'] >= 7.3) { 208 $this->metaTablesSQL = " 209 select table_name,'T' from information_schema.tables where table_schema not in ( 'pg_catalog','information_schema') 210 union 211 select table_name,'V' from information_schema.views where table_schema not in ( 'pg_catalog','information_schema') "; 212 } 213 if ($mask) { 214 $save = $this->metaTablesSQL; 215 $mask = $this->qstr(strtolower($mask)); 216 if ($info['version']>=7.3) 217 $this->metaTablesSQL = " 218 select table_name,'T' from information_schema.tables where table_name like $mask and table_schema not in ( 'pg_catalog','information_schema') 219 union 220 select table_name,'V' from information_schema.views where table_name like $mask and table_schema not in ( 'pg_catalog','information_schema') "; 221 else 222 $this->metaTablesSQL = " 223 select tablename,'T' from pg_tables where tablename like $mask 224 union 225 select viewname,'V' from pg_views where viewname like $mask"; 226 } 227 $ret = ADOConnection::MetaTables($ttype,$showSchema); 228 229 if ($mask) { 230 $this->metaTablesSQL = $save; 231 } 232 return $ret; 233 } 234 235 236 /** 237 * Quotes a string to be sent to the database. 238 * 239 * Relies on pg_escape_string() 240 * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:qstr 241 * 242 * @param string $s The string to quote 243 * @param bool $magic_quotes This param is not used since 5.21.0. 244 * It remains for backwards compatibility. 245 * 246 * @return string Quoted string 247 */ 248 function qStr($s, $magic_quotes=false) 249 { 250 if (is_bool($s)) { 251 return $s ? 'true' : 'false'; 252 } 253 254 if ($this->_connectionID) { 255 return "'" . pg_escape_string($this->_connectionID, $s) . "'"; 256 } else { 257 return "'" . pg_escape_string($s) . "'"; 258 } 259 } 260 261 262 // Format date column in sql string given an input format that understands Y M D 263 function SQLDate($fmt, $col=false) 264 { 265 if (!$col) $col = $this->sysTimeStamp; 266 $s = 'TO_CHAR('.$col.",'"; 267 268 $len = strlen($fmt); 269 for ($i=0; $i < $len; $i++) { 270 $ch = $fmt[$i]; 271 switch($ch) { 272 case 'Y': 273 case 'y': 274 $s .= 'YYYY'; 275 break; 276 case 'Q': 277 case 'q': 278 $s .= 'Q'; 279 break; 280 281 case 'M': 282 $s .= 'Mon'; 283 break; 284 285 case 'm': 286 $s .= 'MM'; 287 break; 288 case 'D': 289 case 'd': 290 $s .= 'DD'; 291 break; 292 293 case 'H': 294 $s.= 'HH24'; 295 break; 296 297 case 'h': 298 $s .= 'HH'; 299 break; 300 301 case 'i': 302 $s .= 'MI'; 303 break; 304 305 case 's': 306 $s .= 'SS'; 307 break; 308 309 case 'a': 310 case 'A': 311 $s .= 'AM'; 312 break; 313 314 case 'w': 315 $s .= 'D'; 316 break; 317 318 case 'l': 319 $s .= 'DAY'; 320 break; 321 322 case 'W': 323 $s .= 'WW'; 324 break; 325 326 default: 327 // handle escape characters... 328 if ($ch == '\\') { 329 $i++; 330 $ch = substr($fmt,$i,1); 331 } 332 if (strpos('-/.:;, ',$ch) !== false) $s .= $ch; 333 else $s .= '"'.$ch.'"'; 334 335 } 336 } 337 return $s. "')"; 338 } 339 340 341 342 /* 343 * Load a Large Object from a file 344 * - the procedure stores the object id in the table and imports the object using 345 * postgres proprietary blob handling routines 346 * 347 * contributed by Mattia Rossi mattia@technologist.com 348 * modified for safe mode by juraj chlebec 349 */ 350 function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') 351 { 352 pg_query($this->_connectionID, 'begin'); 353 354 $fd = fopen($path,'r'); 355 $contents = fread($fd,filesize($path)); 356 fclose($fd); 357 358 $oid = pg_lo_create($this->_connectionID); 359 $handle = pg_lo_open($this->_connectionID, $oid, 'w'); 360 pg_lo_write($handle, $contents); 361 pg_lo_close($handle); 362 363 // $oid = pg_lo_import ($path); 364 pg_query($this->_connectionID, 'commit'); 365 $rs = ADOConnection::UpdateBlob($table,$column,$oid,$where,$blobtype); 366 $rez = !empty($rs); 367 return $rez; 368 } 369 370 /* 371 * Deletes/Unlinks a Blob from the database, otherwise it 372 * will be left behind 373 * 374 * Returns TRUE on success or FALSE on failure. 375 * 376 * contributed by Todd Rogers todd#windfox.net 377 */ 378 function BlobDelete( $blob ) 379 { 380 pg_query($this->_connectionID, 'begin'); 381 $result = @pg_lo_unlink($blob); 382 pg_query($this->_connectionID, 'commit'); 383 return( $result ); 384 } 385 386 /* 387 Heuristic - not guaranteed to work. 388 */ 389 function GuessOID($oid) 390 { 391 if (strlen($oid)>16) return false; 392 return is_numeric($oid); 393 } 394 395 /* 396 * If an OID is detected, then we use pg_lo_* to open the oid file and read the 397 * real blob from the db using the oid supplied as a parameter. If you are storing 398 * blobs using bytea, we autodetect and process it so this function is not needed. 399 * 400 * contributed by Mattia Rossi mattia@technologist.com 401 * 402 * see http://www.postgresql.org/idocs/index.php?largeobjects.html 403 * 404 * Since adodb 4.54, this returns the blob, instead of sending it to stdout. Also 405 * added maxsize parameter, which defaults to $db->maxblobsize if not defined. 406 */ 407 function BlobDecode($blob,$maxsize=false,$hastrans=true) 408 { 409 if (!$this->GuessOID($blob)) return $blob; 410 411 if ($hastrans) pg_query($this->_connectionID,'begin'); 412 $fd = @pg_lo_open($this->_connectionID,$blob,'r'); 413 if ($fd === false) { 414 if ($hastrans) pg_query($this->_connectionID,'commit'); 415 return $blob; 416 } 417 if (!$maxsize) $maxsize = $this->maxblobsize; 418 $realblob = @pg_lo_read($fd,$maxsize); 419 @pg_lo_close($fd); 420 if ($hastrans) pg_query($this->_connectionID,'commit'); 421 return $realblob; 422 } 423 424 /** 425 * Encode binary value prior to DB storage. 426 * 427 * See https://www.postgresql.org/docs/current/static/datatype-binary.html 428 * 429 * NOTE: SQL string literals (input strings) must be preceded with two 430 * backslashes due to the fact that they must pass through two parsers in 431 * the PostgreSQL backend. 432 * 433 * @param string $blob 434 */ 435 function BlobEncode($blob) 436 { 437 return pg_escape_bytea($this->_connectionID, $blob); 438 } 439 440 // assumes bytea for blob, and varchar for clob 441 function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') 442 { 443 if ($blobtype == 'CLOB') { 444 return $this->Execute("UPDATE $table SET $column=" . $this->qstr($val) . " WHERE $where"); 445 } 446 // do not use bind params which uses qstr(), as blobencode() already quotes data 447 return $this->Execute("UPDATE $table SET $column='".$this->BlobEncode($val)."'::bytea WHERE $where"); 448 } 449 450 function OffsetDate($dayFraction,$date=false) 451 { 452 if (!$date) $date = $this->sysDate; 453 else if (strncmp($date,"'",1) == 0) { 454 $len = strlen($date); 455 if (10 <= $len && $len <= 12) $date = 'date '.$date; 456 else $date = 'timestamp '.$date; 457 } 458 459 460 return "($date+interval'".($dayFraction * 1440)." minutes')"; 461 #return "($date+interval'$dayFraction days')"; 462 } 463 464 /** 465 * Generate the SQL to retrieve MetaColumns data 466 * @param string $table Table name 467 * @param string $schema Schema name (can be blank) 468 * @return string SQL statement to execute 469 */ 470 protected function _generateMetaColumnsSQL($table, $schema) 471 { 472 if ($schema) { 473 return sprintf($this->metaColumnsSQL1, $table, $table, $schema); 474 } 475 else { 476 return sprintf($this->metaColumnsSQL, $table, $table, $schema); 477 } 478 } 479 480 // for schema support, pass in the $table param "$schema.$tabname". 481 // converts field names to lowercase, $upper is ignored 482 // see PHPLens Issue No: 14018 for more info 483 function MetaColumns($table,$normalize=true) 484 { 485 global $ADODB_FETCH_MODE; 486 487 $schema = false; 488 $false = false; 489 $this->_findschema($table,$schema); 490 491 if ($normalize) $table = strtolower($table); 492 493 $save = $ADODB_FETCH_MODE; 494 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 495 if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); 496 497 $rs = $this->Execute($this->_generateMetaColumnsSQL($table, $schema)); 498 if (isset($savem)) $this->SetFetchMode($savem); 499 $ADODB_FETCH_MODE = $save; 500 501 if ($rs === false) { 502 return $false; 503 } 504 if (!empty($this->metaKeySQL)) { 505 // If we want the primary keys, we have to issue a separate query 506 // Of course, a modified version of the metaColumnsSQL query using a 507 // LEFT JOIN would have been much more elegant, but postgres does 508 // not support OUTER JOINS. So here is the clumsy way. 509 510 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; 511 512 $rskey = $this->Execute(sprintf($this->metaKeySQL,($table))); 513 // fetch all result in once for performance. 514 $keys = $rskey->GetArray(); 515 if (isset($savem)) $this->SetFetchMode($savem); 516 $ADODB_FETCH_MODE = $save; 517 518 $rskey->Close(); 519 unset($rskey); 520 } 521 522 $rsdefa = array(); 523 if (!empty($this->metaDefaultsSQL)) { 524 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; 525 $sql = sprintf($this->metaDefaultsSQL, ($table)); 526 $rsdef = $this->Execute($sql); 527 if (isset($savem)) $this->SetFetchMode($savem); 528 $ADODB_FETCH_MODE = $save; 529 530 if ($rsdef) { 531 while (!$rsdef->EOF) { 532 $num = $rsdef->fields['num']; 533 $s = $rsdef->fields['def']; 534 if (strpos($s,'::')===false && substr($s, 0, 1) == "'") { /* quoted strings hack... for now... fixme */ 535 $s = substr($s, 1); 536 $s = substr($s, 0, strlen($s) - 1); 537 } 538 539 $rsdefa[$num] = $s; 540 $rsdef->MoveNext(); 541 } 542 } else { 543 ADOConnection::outp( "==> SQL => " . $sql); 544 } 545 unset($rsdef); 546 } 547 548 $retarr = array(); 549 while (!$rs->EOF) { 550 $fld = new ADOFieldObject(); 551 $fld->name = $rs->fields[0]; 552 $fld->type = $rs->fields[1]; 553 $fld->max_length = $rs->fields[2]; 554 $fld->attnum = $rs->fields[6]; 555 556 if ($fld->max_length <= 0) $fld->max_length = $rs->fields[3]-4; 557 if ($fld->max_length <= 0) $fld->max_length = -1; 558 if ($fld->type == 'numeric') { 559 $fld->scale = $fld->max_length & 0xFFFF; 560 $fld->max_length >>= 16; 561 } 562 // dannym 563 // 5 hasdefault; 6 num-of-column 564 $fld->has_default = ($rs->fields[5] == 't'); 565 if ($fld->has_default) { 566 $fld->default_value = $rsdefa[$rs->fields[6]]; 567 } 568 569 //Freek 570 $fld->not_null = $rs->fields[4] == 't'; 571 572 573 // Freek 574 if (is_array($keys)) { 575 foreach($keys as $key) { 576 if ($fld->name == $key['column_name'] AND $key['primary_key'] == 't') 577 $fld->primary_key = true; 578 if ($fld->name == $key['column_name'] AND $key['unique_key'] == 't') 579 $fld->unique = true; // What name is more compatible? 580 } 581 } 582 583 if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; 584 else $retarr[($normalize) ? strtoupper($fld->name) : $fld->name] = $fld; 585 586 $rs->MoveNext(); 587 } 588 $rs->Close(); 589 if (empty($retarr)) 590 return $false; 591 else 592 return $retarr; 593 594 } 595 596 function param($name, $type='C') 597 { 598 if (!$name) { 599 // Reset parameter number if $name is falsy 600 $this->_pnum = 0; 601 if ($name === false) { 602 // and don't return placeholder if false (see #380) 603 return ''; 604 } 605 } 606 607 return '$' . ++$this->_pnum; 608 } 609 610 function MetaIndexes ($table, $primary = FALSE, $owner = false) 611 { 612 global $ADODB_FETCH_MODE; 613 614 $schema = false; 615 $this->_findschema($table,$schema); 616 617 if ($schema) { // requires pgsql 7.3+ - pg_namespace used. 618 $sql = ' 619 SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns" 620 FROM pg_catalog.pg_class c 621 JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid 622 JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid 623 ,pg_namespace n 624 WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\')) 625 and c.relnamespace=c2.relnamespace 626 and c.relnamespace=n.oid 627 and n.nspname=\'%s\''; 628 } else { 629 $sql = ' 630 SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns" 631 FROM pg_catalog.pg_class c 632 JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid 633 JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid 634 WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\'))'; 635 } 636 637 if ($primary == FALSE) { 638 $sql .= ' AND i.indisprimary=false;'; 639 } 640 641 $save = $ADODB_FETCH_MODE; 642 $ADODB_FETCH_MODE = ADODB_FETCH_NUM; 643 if ($this->fetchMode !== FALSE) { 644 $savem = $this->SetFetchMode(FALSE); 645 } 646 647 $rs = $this->Execute(sprintf($sql,$table,$table,$schema)); 648 if (isset($savem)) { 649 $this->SetFetchMode($savem); 650 } 651 $ADODB_FETCH_MODE = $save; 652 653 if (!is_object($rs)) { 654 $false = false; 655 return $false; 656 } 657 658 $col_names = $this->MetaColumnNames($table,true,true); 659 // 3rd param is use attnum, 660 // see https://sourceforge.net/p/adodb/bugs/45/ 661 $indexes = array(); 662 while ($row = $rs->FetchRow()) { 663 $columns = array(); 664 foreach (explode(' ', $row[2]) as $col) { 665 $columns[] = $col_names[$col]; 666 } 667 668 $indexes[$row[0]] = array( 669 'unique' => ($row[1] == 't'), 670 'columns' => $columns 671 ); 672 } 673 return $indexes; 674 } 675 676 /** 677 * Connect to a database. 678 * 679 * Examples: 680 * $db->Connect("host=host1 user=user1 password=secret port=4341"); 681 * $db->Connect('host1:4341', 'user1', 'secret'); 682 * 683 * @param string $str pg_connect() Connection string or Hostname[:port] 684 * @param string $user (Optional) The username to connect as. 685 * @param string $pwd (Optional) The password to connect with. 686 * @param string $db (Optional) The name of the database to start in when connected. 687 * @param int $ctype Connection type 688 * @return bool|null True if connected successfully, false if connection failed, or 689 * null if the PostgreSQL extension is not loaded. 690 */ 691 function _connect($str, $user='', $pwd='', $db='', $ctype=0) 692 { 693 if (!function_exists('pg_connect')) { 694 return null; 695 } 696 697 $this->_errorMsg = false; 698 699 // If $user, $pwd and $db are all null, then $str is a pg_connect() 700 // connection string. Otherwise we expect it to be a hostname, 701 // with optional port separated by ':' 702 if ($user || $pwd || $db) { 703 // Hostname & port 704 if ($str) { 705 $host = explode(':', $str); 706 if ($host[0]) { 707 $conn['host'] = $host[0]; 708 } 709 if (isset($host[1])) { 710 $conn['port'] = (int)$host[1]; 711 } elseif (!empty($this->port)) { 712 $conn['port'] = $this->port; 713 } 714 } 715 $conn['user'] = $user; 716 $conn['password'] = $pwd; 717 // @TODO not sure why we default to 'template1', pg_connect() uses the username when dbname is empty 718 $conn['dbname'] = $db ?: 'template1'; 719 720 // Generate connection string 721 $str = ''; 722 foreach ($conn as $param => $value) { 723 // Escaping single quotes and backslashes per pg_connect() documentation 724 $str .= $param . "='" . addcslashes($value, "'\\") . "' "; 725 } 726 } 727 728 if ($ctype === 1) { // persistent 729 $this->_connectionID = pg_pconnect($str); 730 } else { 731 if ($ctype === -1) { // nconnect, we trick pgsql ext by changing the connection str 732 static $ncnt; 733 734 if (empty($ncnt)) $ncnt = 1; 735 else $ncnt += 1; 736 737 $str .= str_repeat(' ',$ncnt); 738 } 739 $this->_connectionID = pg_connect($str); 740 } 741 if ($this->_connectionID === false) return false; 742 $this->Execute("set datestyle='ISO'"); 743 744 $info = $this->ServerInfo(false); 745 746 if (version_compare($info['version'], '7.1', '>=')) { 747 $this->_nestedSQL = true; 748 } 749 750 # PostgreSQL 9.0 changed the default output for bytea from 'escape' to 'hex' 751 # PHP does not handle 'hex' properly ('x74657374' is returned as 't657374') 752 # https://bugs.php.net/bug.php?id=59831 states this is in fact not a bug, 753 # so we manually set bytea_output 754 if (!empty($this->connection->noBlobs) 755 && version_compare($info['version'], '9.0', '>=') 756 && version_compare($info['client'], '9.2', '<') 757 ) { 758 $this->Execute('set bytea_output=escape'); 759 } 760 761 return true; 762 } 763 764 function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) 765 { 766 return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName,-1); 767 } 768 769 // returns true or false 770 // 771 // examples: 772 // $db->PConnect("host=host1 user=user1 password=secret port=4341"); 773 // $db->PConnect('host1','user1','secret'); 774 function _pconnect($str,$user='',$pwd='',$db='') 775 { 776 return $this->_connect($str,$user,$pwd,$db,1); 777 } 778 779 780 // returns queryID or false 781 function _query($sql,$inputarr=false) 782 { 783 $this->_pnum = 0; 784 $this->_errorMsg = false; 785 if ($inputarr) { 786 /* 787 It appears that PREPARE/EXECUTE is slower for many queries. 788 789 For query executed 1000 times: 790 "select id,firstname,lastname from adoxyz 791 where firstname not like ? and lastname not like ? and id = ?" 792 793 with plan = 1.51861286163 secs 794 no plan = 1.26903700829 secs 795 */ 796 $plan = 'P'.md5($sql); 797 798 $execp = ''; 799 foreach($inputarr as $v) { 800 if ($execp) $execp .= ','; 801 if (is_string($v)) { 802 if (strncmp($v,"'",1) !== 0) $execp .= $this->qstr($v); 803 } else { 804 $execp .= $v; 805 } 806 } 807 808 if ($execp) $exsql = "EXECUTE $plan ($execp)"; 809 else $exsql = "EXECUTE $plan"; 810 811 812 $rez = @pg_execute($this->_connectionID,$exsql); 813 if (!$rez) { 814 # Perhaps plan does not exist? Prepare/compile plan. 815 $params = ''; 816 foreach($inputarr as $v) { 817 if ($params) $params .= ','; 818 if (is_string($v)) { 819 $params .= 'VARCHAR'; 820 } else if (is_integer($v)) { 821 $params .= 'INTEGER'; 822 } else { 823 $params .= "REAL"; 824 } 825 } 826 $sqlarr = explode('?',$sql); 827 //print_r($sqlarr); 828 $sql = ''; 829 $i = 1; 830 foreach($sqlarr as $v) { 831 $sql .= $v.' $'.$i; 832 $i++; 833 } 834 $s = "PREPARE $plan ($params) AS ".substr($sql,0,strlen($sql)-2); 835 //adodb_pr($s); 836 $rez = pg_execute($this->_connectionID,$s); 837 //echo $this->ErrorMsg(); 838 } 839 if ($rez) 840 $rez = pg_execute($this->_connectionID,$exsql); 841 } else { 842 //adodb_backtrace(); 843 $rez = pg_query($this->_connectionID,$sql); 844 } 845 // check if no data returned, then no need to create real recordset 846 if ($rez && pg_num_fields($rez) <= 0) { 847 if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') { 848 pg_free_result($this->_resultid); 849 } 850 $this->_resultid = $rez; 851 return true; 852 } 853 854 return $rez; 855 } 856 857 function _errconnect() 858 { 859 if (defined('DB_ERROR_CONNECT_FAILED')) return DB_ERROR_CONNECT_FAILED; 860 else return 'Database connection failed'; 861 } 862 863 /* Returns: the last error message from previous database operation */ 864 function ErrorMsg() 865 { 866 if ($this->_errorMsg !== false) { 867 return $this->_errorMsg; 868 } 869 870 if (!empty($this->_resultid)) { 871 $this->_errorMsg = @pg_result_error($this->_resultid); 872 if ($this->_errorMsg) { 873 return $this->_errorMsg; 874 } 875 } 876 877 if (!empty($this->_connectionID)) { 878 $this->_errorMsg = @pg_last_error($this->_connectionID); 879 } else { 880 $this->_errorMsg = $this->_errconnect(); 881 } 882 883 return $this->_errorMsg; 884 } 885 886 function ErrorNo() 887 { 888 $e = $this->ErrorMsg(); 889 if (strlen($e)) { 890 return ADOConnection::MetaError($e); 891 } 892 return 0; 893 } 894 895 // returns true or false 896 function _close() 897 { 898 if ($this->transCnt) $this->RollbackTrans(); 899 if ($this->_resultid) { 900 @pg_free_result($this->_resultid); 901 $this->_resultid = false; 902 } 903 @pg_close($this->_connectionID); 904 $this->_connectionID = false; 905 return true; 906 } 907 908 909 /* 910 * Maximum size of C field 911 */ 912 function CharMax() 913 { 914 return 1000000000; // should be 1 Gb? 915 } 916 917 /* 918 * Maximum size of X field 919 */ 920 function TextMax() 921 { 922 return 1000000000; // should be 1 Gb? 923 } 924 925 926 } 927 928 /*-------------------------------------------------------------------------------------- 929 Class Name: Recordset 930 --------------------------------------------------------------------------------------*/ 931 932 class ADORecordSet_postgres64 extends ADORecordSet{ 933 var $_blobArr; 934 var $databaseType = "postgres64"; 935 var $canSeek = true; 936 937 function __construct($queryID, $mode=false) 938 { 939 if ($mode === false) { 940 global $ADODB_FETCH_MODE; 941 $mode = $ADODB_FETCH_MODE; 942 } 943 switch ($mode) 944 { 945 case ADODB_FETCH_NUM: $this->fetchMode = PGSQL_NUM; break; 946 case ADODB_FETCH_ASSOC:$this->fetchMode = PGSQL_ASSOC; break; 947 948 case ADODB_FETCH_DEFAULT: 949 case ADODB_FETCH_BOTH: 950 default: $this->fetchMode = PGSQL_BOTH; break; 951 } 952 $this->adodbFetchMode = $mode; 953 954 // Parent's constructor 955 parent::__construct($queryID); 956 } 957 958 function GetRowAssoc($upper = ADODB_ASSOC_CASE) 959 { 960 if ($this->fetchMode == PGSQL_ASSOC && $upper == ADODB_ASSOC_CASE_LOWER) { 961 return $this->fields; 962 } 963 $row = ADORecordSet::GetRowAssoc($upper); 964 return $row; 965 } 966 967 function _initRS() 968 { 969 global $ADODB_COUNTRECS; 970 $qid = $this->_queryID; 971 $this->_numOfRows = ($ADODB_COUNTRECS)? @pg_num_rows($qid):-1; 972 $this->_numOfFields = @pg_num_fields($qid); 973 974 // cache types for blob decode check 975 // apparently pg_field_type actually performs an sql query on the database to get the type. 976 if (empty($this->connection->noBlobs)) 977 for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { 978 if (pg_field_type($qid,$i) == 'bytea') { 979 $this->_blobArr[$i] = pg_field_name($qid,$i); 980 } 981 } 982 } 983 984 function fields($colname) 985 { 986 if ($this->fetchMode != PGSQL_NUM) { 987 return @$this->fields[$colname]; 988 } 989 990 if (!$this->bind) { 991 $this->bind = array(); 992 for ($i=0; $i < $this->_numOfFields; $i++) { 993 $o = $this->FetchField($i); 994 $this->bind[strtoupper($o->name)] = $i; 995 } 996 } 997 return $this->fields[$this->bind[strtoupper($colname)]]; 998 } 999 1000 function fetchField($fieldOffset = 0) 1001 { 1002 // offsets begin at 0 1003 1004 $o = new ADOFieldObject(); 1005 $o->name = @pg_field_name($this->_queryID, $fieldOffset); 1006 $o->type = @pg_field_type($this->_queryID, $fieldOffset); 1007 $o->max_length = @pg_field_size($this->_queryID, $fieldOffset); 1008 return $o; 1009 } 1010 1011 function _seek($row) 1012 { 1013 return @pg_fetch_row($this->_queryID,$row); 1014 } 1015 1016 function _decode($blob) 1017 { 1018 if ($blob === NULL) return NULL; 1019 // eval('$realblob="'.str_replace(array('"','$'),array('\"','\$'),$blob).'";'); 1020 return pg_unescape_bytea($blob); 1021 } 1022 1023 function _fixblobs() 1024 { 1025 if ($this->fetchMode == PGSQL_NUM || $this->fetchMode == PGSQL_BOTH) { 1026 foreach($this->_blobArr as $k => $v) { 1027 $this->fields[$k] = ADORecordSet_postgres64::_decode($this->fields[$k]); 1028 } 1029 } 1030 if ($this->fetchMode == PGSQL_ASSOC || $this->fetchMode == PGSQL_BOTH) { 1031 foreach($this->_blobArr as $k => $v) { 1032 $this->fields[$v] = ADORecordSet_postgres64::_decode($this->fields[$v]); 1033 } 1034 } 1035 } 1036 1037 // 10% speedup to move MoveNext to child class 1038 function MoveNext() 1039 { 1040 if (!$this->EOF) { 1041 $this->_currentRow++; 1042 if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) { 1043 $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); 1044 if (is_array($this->fields) && $this->fields) { 1045 if (isset($this->_blobArr)) $this->_fixblobs(); 1046 return true; 1047 } 1048 } 1049 $this->fields = false; 1050 $this->EOF = true; 1051 } 1052 return false; 1053 } 1054 1055 function _fetch() 1056 { 1057 1058 if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0) 1059 return false; 1060 1061 $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); 1062 1063 if ($this->fields && isset($this->_blobArr)) $this->_fixblobs(); 1064 1065 return (is_array($this->fields)); 1066 } 1067 1068 function _close() 1069 { 1070 if (!is_resource($this->_queryID) 1071 || get_resource_type($this->_queryID) != 'pgsql result' 1072 ) { 1073 return true; 1074 } 1075 return pg_free_result($this->_queryID); 1076 } 1077 1078 function MetaType($t,$len=-1,$fieldobj=false) 1079 { 1080 if (is_object($t)) { 1081 $fieldobj = $t; 1082 $t = $fieldobj->type; 1083 $len = $fieldobj->max_length; 1084 } 1085 switch (strtoupper($t)) { 1086 case 'MONEY': // stupid, postgres expects money to be a string 1087 case 'INTERVAL': 1088 case 'CHAR': 1089 case 'CHARACTER': 1090 case 'VARCHAR': 1091 case 'NAME': 1092 case 'BPCHAR': 1093 case '_VARCHAR': 1094 case 'CIDR': 1095 case 'INET': 1096 case 'MACADDR': 1097 if ($len <= $this->blobSize) return 'C'; 1098 1099 case 'TEXT': 1100 return 'X'; 1101 1102 case 'IMAGE': // user defined type 1103 case 'BLOB': // user defined type 1104 case 'BIT': // This is a bit string, not a single bit, so don't return 'L' 1105 case 'VARBIT': 1106 case 'BYTEA': 1107 return 'B'; 1108 1109 case 'BOOL': 1110 case 'BOOLEAN': 1111 return 'L'; 1112 1113 case 'DATE': 1114 return 'D'; 1115 1116 1117 case 'TIMESTAMP WITHOUT TIME ZONE': 1118 case 'TIME': 1119 case 'DATETIME': 1120 case 'TIMESTAMP': 1121 case 'TIMESTAMPTZ': 1122 return 'T'; 1123 1124 case 'SMALLINT': 1125 case 'BIGINT': 1126 case 'INTEGER': 1127 case 'INT8': 1128 case 'INT4': 1129 case 'INT2': 1130 if (isset($fieldobj) && 1131 empty($fieldobj->primary_key) && (!$this->connection->uniqueIisR || empty($fieldobj->unique))) return 'I'; 1132 1133 case 'OID': 1134 case 'SERIAL': 1135 return 'R'; 1136 1137 default: 1138 return ADODB_DEFAULT_METATYPE; 1139 } 1140 } 1141 1142 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body