1<?php 2/** 3 * @package FrameworkOnFramework 4 * @subpackage database 5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. 8 * 9 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects 10 * instead of plain stdClass objects 11 */ 12 13// Protect from unauthorized access 14defined('FOF_INCLUDED') or die; 15 16/** 17 * Oracle database driver 18 * 19 * @see http://php.net/pdo 20 * @since 12.1 21 */ 22class FOFDatabaseDriverOracle extends FOFDatabaseDriverPdo 23{ 24 /** 25 * The name of the database driver. 26 * 27 * @var string 28 * @since 12.1 29 */ 30 public $name = 'oracle'; 31 32 /** 33 * The type of the database server family supported by this driver. 34 * 35 * @var string 36 * @since CMS 3.5.0 37 */ 38 public $serverType = 'oracle'; 39 40 /** 41 * The character(s) used to quote SQL statement names such as table names or field names, 42 * etc. The child classes should define this as necessary. If a single character string the 43 * same character is used for both sides of the quoted name, else the first character will be 44 * used for the opening quote and the second for the closing quote. 45 * 46 * @var string 47 * @since 12.1 48 */ 49 protected $nameQuote = '"'; 50 51 /** 52 * Returns the current dateformat 53 * 54 * @var string 55 * @since 12.1 56 */ 57 protected $dateformat; 58 59 /** 60 * Returns the current character set 61 * 62 * @var string 63 * @since 12.1 64 */ 65 protected $charset; 66 67 /** 68 * Constructor. 69 * 70 * @param array $options List of options used to configure the connection 71 * 72 * @since 12.1 73 */ 74 public function __construct($options) 75 { 76 $options['driver'] = 'oci'; 77 $options['charset'] = (isset($options['charset'])) ? $options['charset'] : 'AL32UTF8'; 78 $options['dateformat'] = (isset($options['dateformat'])) ? $options['dateformat'] : 'RRRR-MM-DD HH24:MI:SS'; 79 80 $this->charset = $options['charset']; 81 $this->dateformat = $options['dateformat']; 82 83 // Finalize initialisation 84 parent::__construct($options); 85 } 86 87 /** 88 * Destructor. 89 * 90 * @since 12.1 91 */ 92 public function __destruct() 93 { 94 $this->freeResult(); 95 unset($this->connection); 96 } 97 98 /** 99 * Connects to the database if needed. 100 * 101 * @return void Returns void if the database connected successfully. 102 * 103 * @since 12.1 104 * @throws RuntimeException 105 */ 106 public function connect() 107 { 108 if ($this->connection) 109 { 110 return; 111 } 112 113 parent::connect(); 114 115 if (isset($this->options['schema'])) 116 { 117 $this->setQuery('ALTER SESSION SET CURRENT_SCHEMA = ' . $this->quoteName($this->options['schema']))->execute(); 118 } 119 120 $this->setDateFormat($this->dateformat); 121 } 122 123 /** 124 * Disconnects the database. 125 * 126 * @return void 127 * 128 * @since 12.1 129 */ 130 public function disconnect() 131 { 132 // Close the connection. 133 $this->freeResult(); 134 unset($this->connection); 135 } 136 137 /** 138 * Drops a table from the database. 139 * 140 * Note: The IF EXISTS flag is unused in the Oracle driver. 141 * 142 * @param string $tableName The name of the database table to drop. 143 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. 144 * 145 * @return FOFDatabaseDriverOracle Returns this object to support chaining. 146 * 147 * @since 12.1 148 */ 149 public function dropTable($tableName, $ifExists = true) 150 { 151 $this->connect(); 152 153 $query = $this->getQuery(true) 154 ->setQuery('DROP TABLE :tableName'); 155 $query->bind(':tableName', $tableName); 156 157 $this->setQuery($query); 158 159 $this->execute(); 160 161 return $this; 162 } 163 164 /** 165 * Method to get the database collation in use by sampling a text field of a table in the database. 166 * 167 * @return mixed The collation in use by the database or boolean false if not supported. 168 * 169 * @since 12.1 170 */ 171 public function getCollation() 172 { 173 return $this->charset; 174 } 175 176 /** 177 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support 178 * reporting this value please return an empty string. 179 * 180 * @return string 181 */ 182 public function getConnectionCollation() 183 { 184 return $this->charset; 185 } 186 187 /** 188 * Get a query to run and verify the database is operational. 189 * 190 * @return string The query to check the health of the DB. 191 * 192 * @since 12.2 193 */ 194 public function getConnectedQuery() 195 { 196 return 'SELECT 1 FROM dual'; 197 } 198 199 /** 200 * Returns the current date format 201 * This method should be useful in the case that 202 * somebody actually wants to use a different 203 * date format and needs to check what the current 204 * one is to see if it needs to be changed. 205 * 206 * @return string The current date format 207 * 208 * @since 12.1 209 */ 210 public function getDateFormat() 211 { 212 return $this->dateformat; 213 } 214 215 /** 216 * Shows the table CREATE statement that creates the given tables. 217 * 218 * Note: You must have the correct privileges before this method 219 * will return usable results! 220 * 221 * @param mixed $tables A table name or a list of table names. 222 * 223 * @return array A list of the create SQL for the tables. 224 * 225 * @since 12.1 226 * @throws RuntimeException 227 */ 228 public function getTableCreate($tables) 229 { 230 $this->connect(); 231 232 $result = array(); 233 $query = $this->getQuery(true) 234 ->select('dbms_metadata.get_ddl(:type, :tableName)') 235 ->from('dual') 236 ->bind(':type', 'TABLE'); 237 238 // Sanitize input to an array and iterate over the list. 239 settype($tables, 'array'); 240 241 foreach ($tables as $table) 242 { 243 $query->bind(':tableName', $table); 244 $this->setQuery($query); 245 $statement = (string) $this->loadResult(); 246 $result[$table] = $statement; 247 } 248 249 return $result; 250 } 251 252 /** 253 * Retrieves field information about a given table. 254 * 255 * @param string $table The name of the database table. 256 * @param boolean $typeOnly True to only return field types. 257 * 258 * @return array An array of fields for the database table. 259 * 260 * @since 12.1 261 * @throws RuntimeException 262 */ 263 public function getTableColumns($table, $typeOnly = true) 264 { 265 $this->connect(); 266 267 $columns = array(); 268 $query = $this->getQuery(true); 269 270 $fieldCasing = $this->getOption(PDO::ATTR_CASE); 271 272 $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); 273 274 $table = strtoupper($table); 275 276 $query->select('*'); 277 $query->from('ALL_TAB_COLUMNS'); 278 $query->where('table_name = :tableName'); 279 280 $prefixedTable = str_replace('#__', strtoupper($this->tablePrefix), $table); 281 $query->bind(':tableName', $prefixedTable); 282 $this->setQuery($query); 283 $fields = $this->loadObjectList(); 284 285 if ($typeOnly) 286 { 287 foreach ($fields as $field) 288 { 289 $columns[$field->COLUMN_NAME] = $field->DATA_TYPE; 290 } 291 } 292 else 293 { 294 foreach ($fields as $field) 295 { 296 $columns[$field->COLUMN_NAME] = $field; 297 $columns[$field->COLUMN_NAME]->Default = null; 298 } 299 } 300 301 $this->setOption(PDO::ATTR_CASE, $fieldCasing); 302 303 return $columns; 304 } 305 306 /** 307 * Get the details list of keys for a table. 308 * 309 * @param string $table The name of the table. 310 * 311 * @return array An array of the column specification for the table. 312 * 313 * @since 12.1 314 * @throws RuntimeException 315 */ 316 public function getTableKeys($table) 317 { 318 $this->connect(); 319 320 $query = $this->getQuery(true); 321 322 $fieldCasing = $this->getOption(PDO::ATTR_CASE); 323 324 $this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); 325 326 $table = strtoupper($table); 327 $query->select('*') 328 ->from('ALL_CONSTRAINTS') 329 ->where('table_name = :tableName') 330 ->bind(':tableName', $table); 331 332 $this->setQuery($query); 333 $keys = $this->loadObjectList(); 334 335 $this->setOption(PDO::ATTR_CASE, $fieldCasing); 336 337 return $keys; 338 } 339 340 /** 341 * Method to get an array of all tables in the database (schema). 342 * 343 * @param string $databaseName The database (schema) name 344 * @param boolean $includeDatabaseName Whether to include the schema name in the results 345 * 346 * @return array An array of all the tables in the database. 347 * 348 * @since 12.1 349 * @throws RuntimeException 350 */ 351 public function getTableList($databaseName = null, $includeDatabaseName = false) 352 { 353 $this->connect(); 354 355 $query = $this->getQuery(true); 356 357 if ($includeDatabaseName) 358 { 359 $query->select('owner, table_name'); 360 } 361 else 362 { 363 $query->select('table_name'); 364 } 365 366 $query->from('all_tables'); 367 368 if ($databaseName) 369 { 370 $query->where('owner = :database') 371 ->bind(':database', $databaseName); 372 } 373 374 $query->order('table_name'); 375 376 $this->setQuery($query); 377 378 if ($includeDatabaseName) 379 { 380 $tables = $this->loadAssocList(); 381 } 382 else 383 { 384 $tables = $this->loadColumn(); 385 } 386 387 return $tables; 388 } 389 390 /** 391 * Get the version of the database connector. 392 * 393 * @return string The database connector version. 394 * 395 * @since 12.1 396 */ 397 public function getVersion() 398 { 399 $this->connect(); 400 401 $this->setQuery("select value from nls_database_parameters where parameter = 'NLS_RDBMS_VERSION'"); 402 403 return $this->loadResult(); 404 } 405 406 /** 407 * Select a database for use. 408 * 409 * @param string $database The name of the database to select for use. 410 * 411 * @return boolean True if the database was successfully selected. 412 * 413 * @since 12.1 414 * @throws RuntimeException 415 */ 416 public function select($database) 417 { 418 $this->connect(); 419 420 return true; 421 } 422 423 /** 424 * Sets the Oracle Date Format for the session 425 * Default date format for Oracle is = DD-MON-RR 426 * The default date format for this driver is: 427 * 'RRRR-MM-DD HH24:MI:SS' since it is the format 428 * that matches the MySQL one used within most Joomla 429 * tables. 430 * 431 * @param string $dateFormat Oracle Date Format String 432 * 433 * @return boolean 434 * 435 * @since 12.1 436 */ 437 public function setDateFormat($dateFormat = 'DD-MON-RR') 438 { 439 $this->connect(); 440 441 $this->setQuery("ALTER SESSION SET NLS_DATE_FORMAT = '$dateFormat'"); 442 443 if (!$this->execute()) 444 { 445 return false; 446 } 447 448 $this->setQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = '$dateFormat'"); 449 450 if (!$this->execute()) 451 { 452 return false; 453 } 454 455 $this->dateformat = $dateFormat; 456 457 return true; 458 } 459 460 /** 461 * Set the connection to use UTF-8 character encoding. 462 * 463 * Returns false automatically for the Oracle driver since 464 * you can only set the character set when the connection 465 * is created. 466 * 467 * @return boolean True on success. 468 * 469 * @since 12.1 470 */ 471 public function setUtf() 472 { 473 return false; 474 } 475 476 /** 477 * Locks a table in the database. 478 * 479 * @param string $table The name of the table to unlock. 480 * 481 * @return FOFDatabaseDriverOracle Returns this object to support chaining. 482 * 483 * @since 12.1 484 * @throws RuntimeException 485 */ 486 public function lockTable($table) 487 { 488 $this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute(); 489 490 return $this; 491 } 492 493 /** 494 * Renames a table in the database. 495 * 496 * @param string $oldTable The name of the table to be renamed 497 * @param string $newTable The new name for the table. 498 * @param string $backup Not used by Oracle. 499 * @param string $prefix Not used by Oracle. 500 * 501 * @return FOFDatabaseDriverOracle Returns this object to support chaining. 502 * 503 * @since 12.1 504 * @throws RuntimeException 505 */ 506 public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) 507 { 508 $this->setQuery('RENAME ' . $oldTable . ' TO ' . $newTable)->execute(); 509 510 return $this; 511 } 512 513 /** 514 * Unlocks tables in the database. 515 * 516 * @return FOFDatabaseDriverOracle Returns this object to support chaining. 517 * 518 * @since 12.1 519 * @throws RuntimeException 520 */ 521 public function unlockTables() 522 { 523 $this->setQuery('COMMIT')->execute(); 524 525 return $this; 526 } 527 528 /** 529 * Test to see if the PDO ODBC connector is available. 530 * 531 * @return boolean True on success, false otherwise. 532 * 533 * @since 12.1 534 */ 535 public static function isSupported() 536 { 537 return class_exists('PDO') && in_array('oci', PDO::getAvailableDrivers()); 538 } 539 540 /** 541 * This function replaces a string identifier <var>$prefix</var> with the string held is the 542 * <var>tablePrefix</var> class variable. 543 * 544 * @param string $query The SQL statement to prepare. 545 * @param string $prefix The common table prefix. 546 * 547 * @return string The processed SQL statement. 548 * 549 * @since 11.1 550 */ 551 public function replacePrefix($query, $prefix = '#__') 552 { 553 $startPos = 0; 554 $quoteChar = "'"; 555 $literal = ''; 556 557 $query = trim($query); 558 $n = strlen($query); 559 560 while ($startPos < $n) 561 { 562 $ip = strpos($query, $prefix, $startPos); 563 564 if ($ip === false) 565 { 566 break; 567 } 568 569 $j = strpos($query, "'", $startPos); 570 571 if ($j === false) 572 { 573 $j = $n; 574 } 575 576 $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos)); 577 $startPos = $j; 578 579 $j = $startPos + 1; 580 581 if ($j >= $n) 582 { 583 break; 584 } 585 586 // Quote comes first, find end of quote 587 while (true) 588 { 589 $k = strpos($query, $quoteChar, $j); 590 $escaped = false; 591 592 if ($k === false) 593 { 594 break; 595 } 596 597 $l = $k - 1; 598 599 while ($l >= 0 && $query[$l] == '\\') 600 { 601 $l--; 602 $escaped = !$escaped; 603 } 604 605 if ($escaped) 606 { 607 $j = $k + 1; 608 continue; 609 } 610 611 break; 612 } 613 614 if ($k === false) 615 { 616 // Error in the query - no end quote; ignore it 617 break; 618 } 619 620 $literal .= substr($query, $startPos, $k - $startPos + 1); 621 $startPos = $k + 1; 622 } 623 624 if ($startPos < $n) 625 { 626 $literal .= substr($query, $startPos, $n - $startPos); 627 } 628 629 return $literal; 630 } 631 632 /** 633 * Method to commit a transaction. 634 * 635 * @param boolean $toSavepoint If true, commit to the last savepoint. 636 * 637 * @return void 638 * 639 * @since 12.3 640 * @throws RuntimeException 641 */ 642 public function transactionCommit($toSavepoint = false) 643 { 644 $this->connect(); 645 646 if (!$toSavepoint || $this->transactionDepth <= 1) 647 { 648 parent::transactionCommit($toSavepoint); 649 } 650 else 651 { 652 $this->transactionDepth--; 653 } 654 } 655 656 /** 657 * Method to roll back a transaction. 658 * 659 * @param boolean $toSavepoint If true, rollback to the last savepoint. 660 * 661 * @return void 662 * 663 * @since 12.3 664 * @throws RuntimeException 665 */ 666 public function transactionRollback($toSavepoint = false) 667 { 668 $this->connect(); 669 670 if (!$toSavepoint || $this->transactionDepth <= 1) 671 { 672 parent::transactionRollback($toSavepoint); 673 } 674 else 675 { 676 $savepoint = 'SP_' . ($this->transactionDepth - 1); 677 $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); 678 679 if ($this->execute()) 680 { 681 $this->transactionDepth--; 682 } 683 } 684 } 685 686 /** 687 * Method to initialize a transaction. 688 * 689 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 690 * 691 * @return void 692 * 693 * @since 12.3 694 * @throws RuntimeException 695 */ 696 public function transactionStart($asSavepoint = false) 697 { 698 $this->connect(); 699 700 if (!$asSavepoint || !$this->transactionDepth) 701 { 702 return parent::transactionStart($asSavepoint); 703 } 704 705 $savepoint = 'SP_' . $this->transactionDepth; 706 $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); 707 708 if ($this->execute()) 709 { 710 $this->transactionDepth++; 711 } 712 } 713} 714