PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/libs/db/adapters/oracle.php

http://github.com/KumbiaPHP/KumbiaPHP
PHP | 596 lines | 310 code | 57 blank | 229 comment | 58 complexity | ad6609ef8590051e18d73069ba782e72 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * KumbiaPHP web & app Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.
  9. *
  10. * @category Kumbia
  11. * @package Db
  12. * @subpackage Adapters
  13. *
  14. * @copyright Copyright (c) 2005 - 2020 KumbiaPHP Team (http://www.kumbiaphp.com)
  15. * @license https://github.com/KumbiaPHP/KumbiaPHP/blob/master/LICENSE New BSD License
  16. */
  17. /**
  18. * Oracle Database Support.
  19. *
  20. * @category Kumbia
  21. * @package Db
  22. * @subpackage Adapters
  23. */
  24. class DbOracle extends DbBase implements DbBaseInterface
  25. {
  26. /**
  27. * Resource de la Conexlón a Oracle.
  28. *
  29. * @var resource
  30. */
  31. public $id_connection;
  32. /**
  33. * Último Resultado de una Query.
  34. *
  35. * @var resource
  36. */
  37. public $last_result_query;
  38. /**
  39. * Última sentencia SQL enviada a Oracle.
  40. *
  41. * @var string
  42. */
  43. protected $last_query;
  44. /**
  45. * Último error generado por Oracle.
  46. *
  47. * @var string
  48. */
  49. public $last_error;
  50. /**
  51. * Indica si los modelos usan autocommit.
  52. *
  53. * @var bool
  54. */
  55. private $autocommit = true;
  56. /**
  57. * Número de filas devueltas.
  58. *
  59. * @var bool
  60. */
  61. private $num_rows = false;
  62. /**
  63. * Resultado de Array Asociativo.
  64. */
  65. const DB_ASSOC = OCI_ASSOC;
  66. /**
  67. * Resultado de Array Asociativo y Numérico.
  68. */
  69. const DB_BOTH = OCI_BOTH;
  70. /**
  71. * Resultado de Array Numérico.
  72. */
  73. const DB_NUM = OCI_NUM;
  74. /**
  75. * Tipo de Dato Integer.
  76. */
  77. const TYPE_INTEGER = 'INTEGER';
  78. /**
  79. * Tipo de Dato Date.
  80. */
  81. const TYPE_DATE = 'DATE';
  82. /**
  83. * Tipo de Dato Varchar.
  84. */
  85. const TYPE_VARCHAR = 'VARCHAR2';
  86. /**
  87. * Tipo de Dato Decimal.
  88. */
  89. const TYPE_DECIMAL = 'DECIMAL';
  90. /**
  91. * Tipo de Dato Datetime.
  92. */
  93. const TYPE_DATETIME = 'DATETIME';
  94. /**
  95. * Tipo de Dato Char.
  96. */
  97. const TYPE_CHAR = 'CHAR';
  98. /**
  99. * Constructor de la Clase.
  100. *
  101. * @param array $config
  102. */
  103. public function __construct($config)
  104. {
  105. $this->connect($config);
  106. }
  107. /**
  108. * Hace una conexión a la base de datos de Oracle.
  109. *
  110. * @param array $config
  111. *
  112. * @return bool
  113. */
  114. public function connect(array $config)
  115. {
  116. if (!extension_loaded('oci8')) {
  117. throw new KumbiaException('Debe cargar la extensión de PHP llamada php_oci8');
  118. }
  119. if ($this->id_connection = oci_pconnect($config['username'], $config['password'], "{$config['host']}/{$config['name']}", $config['charset'])) {
  120. /*
  121. * Cambio el formato de fecha al estandar YYYY-MM-DD
  122. */
  123. $this->query("alter session set nls_date_format = 'YYYY-MM-DD'");
  124. return true;
  125. }
  126. throw new KumbiaException($this->error('Error al conectar a Oracle'));
  127. }
  128. /**
  129. * Efectúa operaciones SQL sobre la base de datos.
  130. *
  131. * @param string $sqlQuery
  132. *
  133. * @return resource|false
  134. */
  135. public function query($sqlQuery)
  136. {
  137. $this->debug($sqlQuery);
  138. if ($this->logger) {
  139. Logger::debug($sqlQuery);
  140. }
  141. $this->num_rows = false;
  142. $this->last_query = $sqlQuery;
  143. $resultQuery = oci_parse($this->id_connection, $sqlQuery);
  144. if (!$resultQuery) {
  145. throw new KumbiaException($this->error("Error al ejecutar <em>'$sqlQuery'</em>"));
  146. }
  147. $this->last_result_query = $resultQuery;
  148. $commit = $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT;
  149. if (!oci_execute($resultQuery, $commit)) {
  150. throw new KumbiaException($this->error("Error al ejecutar <em>'$sqlQuery'</em>"));
  151. }
  152. return $resultQuery;
  153. }
  154. /**
  155. * Cierra la Conexión al Motor de Base de datos.
  156. */
  157. public function close()
  158. {
  159. if ($this->id_connection) {
  160. return oci_close($this->id_connection);
  161. }
  162. }
  163. /**
  164. * Devuelve fila por fila el contenido de un select.
  165. *
  166. * @param resource $resultQuery
  167. * @param int $opt
  168. *
  169. * @return array
  170. */
  171. public function fetch_array($resultQuery = null, $opt = OCI_BOTH)
  172. {
  173. if (!$resultQuery) {
  174. $resultQuery = $this->last_result_query;
  175. if (!$resultQuery) {
  176. return false;
  177. }
  178. }
  179. $result = oci_fetch_array($resultQuery, $opt);
  180. if (is_array($result)) {
  181. $result_to_lower = array();
  182. foreach ($result as $key => $value) {
  183. $result_to_lower[strtolower($key)] = $value;
  184. }
  185. return $result_to_lower;
  186. }
  187. return false;
  188. }
  189. /**
  190. * Devuelve el número de filas de un select.
  191. */
  192. public function num_rows($resultQuery = null)
  193. {
  194. if (!$resultQuery) {
  195. $resultQuery = $this->last_result_query;
  196. if (!$resultQuery) {
  197. throw new KumbiaException($this->error('Resource invalido para db::num_rows'));
  198. }
  199. }
  200. // El Adaptador cachea la ultima llamada a num_rows por razones de performance
  201. /* if($resultQuery==$this->last_result_query){
  202. if($this->num_rows!==false){
  203. return $this->num_rows;
  204. }
  205. } */
  206. $commit = $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT;
  207. if (!oci_execute($resultQuery, $commit)) {
  208. throw new KumbiaException($this->error("Error al ejecutar <em>'{$this->lastQuery}'</em>"));
  209. }
  210. $tmp = array();
  211. $this->num_rows = oci_fetch_all($resultQuery, $tmp);
  212. //unset($tmp);
  213. oci_execute($resultQuery, $commit);
  214. return $this->num_rows;
  215. }
  216. /**
  217. * Devuelve el nombre de un campo en el resultado de un select.
  218. *
  219. * @param int $number
  220. * @param resource $resultQuery
  221. *
  222. * @return string
  223. */
  224. public function field_name($number, $resultQuery = null)
  225. {
  226. if (!$resultQuery) {
  227. $resultQuery = $this->last_result_query;
  228. if (!$resultQuery) {
  229. throw new KumbiaException($this->error('Resource invalido para db::field_name'));
  230. }
  231. }
  232. if (($fieldName = oci_field_name($resultQuery, $number + 1)) !== false) {
  233. return strtolower($fieldName);
  234. }
  235. throw new KumbiaException($this->error('No se pudo conseguir el nombre de campo'));
  236. }
  237. /**
  238. * Se mueve al resultado indicado por $number en un select.
  239. *
  240. * @param int $number
  241. * @param resource $resultQuery
  242. *
  243. * @return bool
  244. */
  245. public function data_seek($number, $resultQuery = null)
  246. {
  247. if (!$resultQuery) {
  248. $resultQuery = $this->last_result_query;
  249. if (!$resultQuery) {
  250. throw new KumbiaException($this->error('Resource invalido para db::data_seek'));
  251. }
  252. }
  253. $commit = $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT;
  254. if (!oci_execute($resultQuery, $commit)) {
  255. throw new KumbiaException($this->error("Error al ejecutar <em>'{$this->lastQuery}'</em>"));
  256. }
  257. if ($number) {
  258. for ($i = 0; $i <= $number - 1; ++$i) {
  259. if (!oci_fetch_row($resultQuery)) {
  260. return false;
  261. }
  262. }
  263. }
  264. return true;
  265. }
  266. /**
  267. * Número de Filas afectadas en un insert, update o delete.
  268. *
  269. * @param resource $resultQuery
  270. *
  271. * @return int
  272. */
  273. public function affected_rows($resultQuery = null)
  274. {
  275. if (!$resultQuery) {
  276. $resultQuery = $this->last_result_query;
  277. if (!$resultQuery) {
  278. return false;
  279. }
  280. }
  281. if (($numberRows = oci_num_rows($resultQuery)) !== false) {
  282. return $numberRows;
  283. }
  284. throw new KumbiaException($this->error('Resource invalido para db::affected_rows'));
  285. }
  286. /**
  287. * Devuelve el error de Oracle.
  288. *
  289. * @return string
  290. */
  291. public function error($err = '')
  292. {
  293. if (!$this->id_connection) {
  294. $error = oci_error() ?: '[Error desconocido en Oracle (sin conexión)]';
  295. if (is_array($error)) {
  296. return $error['message']." > $err ";
  297. }
  298. return $error." > $err ";
  299. }
  300. $error = oci_error($this->id_connection);
  301. if ($error) {
  302. return $error['message']." > $err ";
  303. }
  304. return $err;
  305. }
  306. /**
  307. * Devuelve el no error de Oracle.
  308. *
  309. * @return int
  310. */
  311. public function no_error()
  312. {
  313. if (!$this->id_connection) {
  314. $error = oci_error() ?: 0;
  315. if (is_array($error)) {
  316. return $error['code'];
  317. }
  318. return $error;
  319. }
  320. $error = oci_error($this->id_connection);
  321. return $error['code'];
  322. }
  323. /**
  324. * Devuelve un LIMIT válido para un SELECT del RBDM.
  325. *
  326. * @param string $sql
  327. *
  328. * @return string
  329. */
  330. public function limit($sql)
  331. {
  332. $number = 0;
  333. $params = Util::getParams(func_get_args());
  334. if (isset($params['limit'])) {
  335. $number = $params['limit'];
  336. }
  337. if (!is_numeric($number) || $number < 0) {
  338. return $sql;
  339. }
  340. if (preg_match("/ORDER[\t\n\r ]+BY/i", $sql)) {
  341. if (stripos($sql, 'WHERE')) {
  342. return preg_replace("/ORDER[\t\n\r ]+BY/i", "AND ROWNUM <= $number ORDER BY", $sql);
  343. }
  344. return preg_replace("/ORDER[\t\n\r ]+BY/i", "WHERE ROWNUM <= $number ORDER BY", $sql);
  345. }
  346. if (stripos($sql, 'WHERE')) {
  347. return "$sql AND ROWNUM <= $number";
  348. }
  349. return "$sql WHERE ROWNUM <= $number";
  350. }
  351. /**
  352. * Borra una tabla de la base de datos.
  353. *
  354. * @param string $table
  355. *
  356. * @return bool
  357. */
  358. public function drop_table($table, $if_exists = true)
  359. {
  360. if ($if_exists) {
  361. if ($this->table_exists($table)) {
  362. return $this->query("DROP TABLE $table");
  363. }
  364. return true;
  365. }
  366. return $this->query("DROP TABLE $table");
  367. }
  368. /**
  369. * Crea una tabla utilizando SQL nativo del RDBM.
  370. *
  371. * TODO:
  372. * - Falta que el parámetro index funcione. Este debe listar indices compuestos múltipes y únicos
  373. * - Agregar el tipo de tabla que debe usarse (Oracle)
  374. * - Soporte para campos autonumericos
  375. * - Soporte para llaves foraneas
  376. *
  377. * @param string $table
  378. * @param array $definition
  379. *
  380. * @return resource
  381. */
  382. public function create_table($table, $definition, $index = array())
  383. {
  384. $create_sql = "CREATE TABLE $table (";
  385. if (!is_array($definition)) {
  386. throw new KumbiaException("Definición inválida para crear la tabla '$table'");
  387. }
  388. $create_lines = array();
  389. $index = array();
  390. $unique_index = array();
  391. $primary = array();
  392. //$not_null = "";
  393. //$size = "";
  394. foreach ($definition as $field => $field_def) {
  395. if (isset($field_def['not_null'])) {
  396. $not_null = $field_def['not_null'] ? 'NOT NULL' : '';
  397. } else {
  398. $not_null = '';
  399. }
  400. if (isset($field_def['size'])) {
  401. $size = $field_def['size'] ? '('.$field_def['size'].')' : '';
  402. } else {
  403. $size = '';
  404. }
  405. if (isset($field_def['index']) && $field_def['index']) {
  406. $index[] = "INDEX($field)";
  407. }
  408. if (isset($field_def['unique_index']) && $field_def['unique_index']) {
  409. $index[] = "UNIQUE($field)";
  410. }
  411. if (isset($field_def['primary']) && $field_def['primary']) {
  412. $primary[] = "$field";
  413. }
  414. if (isset($field_def['auto']) && $field_def['auto']) {
  415. $this->query("CREATE SEQUENCE {$table}_{$field}_seq START WITH 1");
  416. }
  417. $extra = isset($field_def['extra']) ? $field_def['extra'] : '';
  418. $create_lines[] = "$field ".$field_def['type'].$size.' '.$not_null.' '.$extra;
  419. }
  420. $create_sql .= join(',', $create_lines);
  421. $last_lines = array();
  422. if (count($primary)) {
  423. $last_lines[] = 'PRIMARY KEY('.join(',', $primary).')';
  424. }
  425. if (count($index)) {
  426. $last_lines[] = join(',', $index);
  427. }
  428. if (count($unique_index)) {
  429. $last_lines[] = join(',', $unique_index);
  430. }
  431. if (count($last_lines)) {
  432. $create_sql .= ','.join(',', $last_lines).')';
  433. }
  434. return $this->query($create_sql);
  435. }
  436. /**
  437. * Listado de Tablas.
  438. *
  439. * @return bool
  440. */
  441. public function list_tables()
  442. {
  443. return $this->fetch_all('SELECT table_name FROM all_tables');
  444. }
  445. /**
  446. * Devuelve el último id autonumérico generado en la BD.
  447. *
  448. * @return int
  449. */
  450. public function last_insert_id($table = '', $primary_key = '')
  451. {
  452. if (!$this->id_connection) {
  453. return false;
  454. }
  455. /*
  456. * Oracle No soporta columnas autonuméricas
  457. */
  458. if ($table && $primary_key) {
  459. $sequence = $table.'_'.$primary_key.'_seq';
  460. $value = $this->fetch_one("SELECT $sequence.CURRVAL FROM dual");
  461. return $value[0];
  462. }
  463. return false;
  464. }
  465. /**
  466. * Verifica si una tabla existe o no.
  467. *
  468. * @param string $table
  469. *
  470. * @return bool
  471. */
  472. public function table_exists($table, $schema = '')
  473. {
  474. $num = $this->fetch_one("SELECT COUNT(*) FROM ALL_TABLES WHERE TABLE_NAME = '".strtoupper($table)."'");
  475. return $num[0];
  476. }
  477. /**
  478. * Listar los campos de una tabla.
  479. *
  480. * @param string $table
  481. *
  482. * @return array
  483. */
  484. public function describe_table($table, $schema = '')
  485. {
  486. /**
  487. * Soporta schemas?
  488. */
  489. $describe = $this->fetch_all("SELECT LOWER(ALL_TAB_COLUMNS.COLUMN_NAME) AS FIELD,
  490. LOWER(ALL_TAB_COLUMNS.DATA_TYPE) AS TYPE,
  491. ALL_TAB_COLUMNS.DATA_LENGTH AS LENGTH, (
  492. SELECT COUNT(*)
  493. FROM ALL_CONS_COLUMNS
  494. WHERE TABLE_NAME = '".strtoupper($table)."' AND ALL_CONS_COLUMNS.COLUMN_NAME = ALL_TAB_COLUMNS.COLUMN_NAME AND ALL_CONS_COLUMNS.POSITION IS NOT NULL) AS KEY, ALL_TAB_COLUMNS.NULLABLE AS ISNULL FROM ALL_TAB_COLUMNS
  495. WHERE ALL_TAB_COLUMNS.TABLE_NAME = '".strtoupper($table)."'");
  496. $final_describe = [];
  497. foreach ($describe as $field) {
  498. $final_describe[] = array(
  499. 'Field' => $field['field'],
  500. 'Type' => $field['type'],
  501. 'Length' => $field['length'],
  502. 'Null' => $field['isnull'] === 'Y' ? 'YES' : 'NO',
  503. 'Key' => $field['key'] == 1 ? 'PRI' : '',
  504. );
  505. }
  506. return $final_describe;
  507. }
  508. /**
  509. * Inicia una transacción si es posible.
  510. */
  511. public function begin()
  512. {
  513. $this->autocommit = false;
  514. }
  515. /**
  516. * Inicia una transacción si es posible.
  517. */
  518. public function commit()
  519. {
  520. $this->autocommit = true;
  521. return oci_commit($this->id_connection);
  522. }
  523. /**
  524. * Revierte una transacción.
  525. */
  526. public function rollback()
  527. {
  528. return oci_rollback($this->id_connection);
  529. }
  530. /**
  531. * Devuelve la última sentencia sql ejecutada por el Adaptador.
  532. *
  533. * @return string
  534. */
  535. public function last_sql_query()
  536. {
  537. return $this->last_query;
  538. }
  539. }