DatabaseOracle.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  1. <?php
  2. /**
  3. * This is the Oracle database abstraction layer.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Database
  22. */
  23. use Wikimedia\Rdbms\Database;
  24. use Wikimedia\Rdbms\Blob;
  25. use Wikimedia\Rdbms\ResultWrapper;
  26. use Wikimedia\Rdbms\DBConnectionError;
  27. use Wikimedia\Rdbms\DBUnexpectedError;
  28. /**
  29. * @ingroup Database
  30. */
  31. class DatabaseOracle extends Database {
  32. /** @var resource */
  33. protected $mLastResult = null;
  34. /** @var int The number of rows affected as an integer */
  35. protected $mAffectedRows;
  36. /** @var bool */
  37. private $ignoreDupValOnIndex = false;
  38. /** @var bool|array */
  39. private $sequenceData = null;
  40. /** @var string Character set for Oracle database */
  41. private $defaultCharset = 'AL32UTF8';
  42. /** @var array */
  43. private $mFieldInfoCache = [];
  44. function __construct( array $p ) {
  45. global $wgDBprefix;
  46. if ( $p['tablePrefix'] == 'get from global' ) {
  47. $p['tablePrefix'] = $wgDBprefix;
  48. }
  49. $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
  50. parent::__construct( $p );
  51. Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
  52. }
  53. function __destruct() {
  54. if ( $this->opened ) {
  55. Wikimedia\suppressWarnings();
  56. $this->close();
  57. Wikimedia\restoreWarnings();
  58. }
  59. }
  60. function getType() {
  61. return 'oracle';
  62. }
  63. function implicitGroupby() {
  64. return false;
  65. }
  66. function implicitOrderby() {
  67. return false;
  68. }
  69. /**
  70. * Usually aborts on failure
  71. * @param string $server
  72. * @param string $user
  73. * @param string $password
  74. * @param string $dbName
  75. * @throws DBConnectionError
  76. * @return resource|null
  77. */
  78. function open( $server, $user, $password, $dbName ) {
  79. global $wgDBOracleDRCP;
  80. if ( !function_exists( 'oci_connect' ) ) {
  81. throw new DBConnectionError(
  82. $this,
  83. "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n " .
  84. "(Note: if you recently installed PHP, you may need to restart your webserver\n " .
  85. "and database)\n" );
  86. }
  87. $this->close();
  88. $this->user = $user;
  89. $this->password = $password;
  90. // changed internal variables functions
  91. // mServer now holds the TNS endpoint
  92. // mDBname is schema name if different from username
  93. if ( !$server ) {
  94. // backward compatibillity (server used to be null and TNS was supplied in dbname)
  95. $this->server = $dbName;
  96. $this->dbName = $user;
  97. } else {
  98. $this->server = $server;
  99. if ( !$dbName ) {
  100. $this->dbName = $user;
  101. } else {
  102. $this->dbName = $dbName;
  103. }
  104. }
  105. if ( !strlen( $user ) ) { # e.g. the class is being loaded
  106. return null;
  107. }
  108. if ( $wgDBOracleDRCP ) {
  109. $this->setFlag( DBO_PERSISTENT );
  110. }
  111. $session_mode = $this->flags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
  112. Wikimedia\suppressWarnings();
  113. if ( $this->flags & DBO_PERSISTENT ) {
  114. $this->conn = oci_pconnect(
  115. $this->user,
  116. $this->password,
  117. $this->server,
  118. $this->defaultCharset,
  119. $session_mode
  120. );
  121. } elseif ( $this->flags & DBO_DEFAULT ) {
  122. $this->conn = oci_new_connect(
  123. $this->user,
  124. $this->password,
  125. $this->server,
  126. $this->defaultCharset,
  127. $session_mode
  128. );
  129. } else {
  130. $this->conn = oci_connect(
  131. $this->user,
  132. $this->password,
  133. $this->server,
  134. $this->defaultCharset,
  135. $session_mode
  136. );
  137. }
  138. Wikimedia\restoreWarnings();
  139. if ( $this->user != $this->dbName ) {
  140. // change current schema in session
  141. $this->selectDB( $this->dbName );
  142. }
  143. if ( !$this->conn ) {
  144. throw new DBConnectionError( $this, $this->lastError() );
  145. }
  146. $this->opened = true;
  147. # removed putenv calls because they interfere with the system globaly
  148. $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
  149. $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
  150. $this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
  151. return $this->conn;
  152. }
  153. /**
  154. * Closes a database connection, if it is open
  155. * Returns success, true if already closed
  156. * @return bool
  157. */
  158. protected function closeConnection() {
  159. return oci_close( $this->conn );
  160. }
  161. function execFlags() {
  162. return $this->trxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
  163. }
  164. protected function doQuery( $sql ) {
  165. wfDebug( "SQL: [$sql]\n" );
  166. if ( !StringUtils::isUtf8( $sql ) ) {
  167. throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
  168. }
  169. // handle some oracle specifics
  170. // remove AS column/table/subquery namings
  171. if ( !$this->getFlag( DBO_DDLMODE ) ) {
  172. $sql = preg_replace( '/ as /i', ' ', $sql );
  173. }
  174. // Oracle has issues with UNION clause if the statement includes LOB fields
  175. // So we do a UNION ALL and then filter the results array with array_unique
  176. $union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
  177. // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
  178. // you have to select data from plan table after explain
  179. $explain_id = MWTimestamp::getLocalInstance()->format( 'dmYHis' );
  180. $sql = preg_replace(
  181. '/^EXPLAIN /',
  182. 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR',
  183. $sql,
  184. 1,
  185. $explain_count
  186. );
  187. Wikimedia\suppressWarnings();
  188. $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
  189. if ( $stmt === false ) {
  190. $e = oci_error( $this->conn );
  191. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  192. return false;
  193. }
  194. if ( !oci_execute( $stmt, $this->execFlags() ) ) {
  195. $e = oci_error( $stmt );
  196. if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
  197. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  198. return false;
  199. }
  200. }
  201. Wikimedia\restoreWarnings();
  202. if ( $explain_count > 0 ) {
  203. return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table ' .
  204. 'WHERE statement_id = \'' . $explain_id . '\'' );
  205. } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
  206. return new ORAResult( $this, $stmt, $union_unique );
  207. } else {
  208. $this->mAffectedRows = oci_num_rows( $stmt );
  209. return true;
  210. }
  211. }
  212. function queryIgnore( $sql, $fname = '' ) {
  213. return $this->query( $sql, $fname, true );
  214. }
  215. /**
  216. * Frees resources associated with the LOB descriptor
  217. * @param ResultWrapper|ORAResult $res
  218. */
  219. function freeResult( $res ) {
  220. if ( $res instanceof ResultWrapper ) {
  221. $res = $res->result;
  222. }
  223. $res->free();
  224. }
  225. /**
  226. * @param ResultWrapper|ORAResult $res
  227. * @return mixed
  228. */
  229. function fetchObject( $res ) {
  230. if ( $res instanceof ResultWrapper ) {
  231. $res = $res->result;
  232. }
  233. return $res->fetchObject();
  234. }
  235. /**
  236. * @param ResultWrapper|ORAResult $res
  237. * @return mixed
  238. */
  239. function fetchRow( $res ) {
  240. if ( $res instanceof ResultWrapper ) {
  241. $res = $res->result;
  242. }
  243. return $res->fetchRow();
  244. }
  245. /**
  246. * @param ResultWrapper|ORAResult $res
  247. * @return int
  248. */
  249. function numRows( $res ) {
  250. if ( $res instanceof ResultWrapper ) {
  251. $res = $res->result;
  252. }
  253. return $res->numRows();
  254. }
  255. /**
  256. * @param ResultWrapper|ORAResult $res
  257. * @return int
  258. */
  259. function numFields( $res ) {
  260. if ( $res instanceof ResultWrapper ) {
  261. $res = $res->result;
  262. }
  263. return $res->numFields();
  264. }
  265. function fieldName( $stmt, $n ) {
  266. return oci_field_name( $stmt, $n );
  267. }
  268. function insertId() {
  269. $res = $this->query( "SELECT lastval_pkg.getLastval FROM dual" );
  270. $row = $this->fetchRow( $res );
  271. return is_null( $row[0] ) ? null : (int)$row[0];
  272. }
  273. /**
  274. * @param mixed $res
  275. * @param int $row
  276. */
  277. function dataSeek( $res, $row ) {
  278. if ( $res instanceof ORAResult ) {
  279. $res->seek( $row );
  280. } else {
  281. $res->result->seek( $row );
  282. }
  283. }
  284. function lastError() {
  285. if ( $this->conn === false ) {
  286. $e = oci_error();
  287. } else {
  288. $e = oci_error( $this->conn );
  289. }
  290. return $e['message'];
  291. }
  292. function lastErrno() {
  293. if ( $this->conn === false ) {
  294. $e = oci_error();
  295. } else {
  296. $e = oci_error( $this->conn );
  297. }
  298. return $e['code'];
  299. }
  300. protected function fetchAffectedRowCount() {
  301. return $this->mAffectedRows;
  302. }
  303. /**
  304. * Returns information about an index
  305. * If errors are explicitly ignored, returns NULL on failure
  306. * @param string $table
  307. * @param string $index
  308. * @param string $fname
  309. * @return bool
  310. */
  311. function indexInfo( $table, $index, $fname = __METHOD__ ) {
  312. return false;
  313. }
  314. function indexUnique( $table, $index, $fname = __METHOD__ ) {
  315. return false;
  316. }
  317. function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
  318. if ( !count( $a ) ) {
  319. return true;
  320. }
  321. if ( !is_array( $options ) ) {
  322. $options = [ $options ];
  323. }
  324. if ( in_array( 'IGNORE', $options ) ) {
  325. $this->ignoreDupValOnIndex = true;
  326. }
  327. if ( !is_array( reset( $a ) ) ) {
  328. $a = [ $a ];
  329. }
  330. foreach ( $a as &$row ) {
  331. $this->insertOneRow( $table, $row, $fname );
  332. }
  333. $retVal = true;
  334. if ( in_array( 'IGNORE', $options ) ) {
  335. $this->ignoreDupValOnIndex = false;
  336. }
  337. return $retVal;
  338. }
  339. private function fieldBindStatement( $table, $col, &$val, $includeCol = false ) {
  340. $col_info = $this->fieldInfoMulti( $table, $col );
  341. $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
  342. $bind = '';
  343. if ( is_numeric( $col ) ) {
  344. $bind = $val;
  345. $val = null;
  346. return $bind;
  347. } elseif ( $includeCol ) {
  348. $bind = "$col = ";
  349. }
  350. if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
  351. $val = null;
  352. }
  353. if ( $val === 'NULL' ) {
  354. $val = null;
  355. }
  356. if ( $val === null ) {
  357. if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
  358. $bind .= 'DEFAULT';
  359. } else {
  360. $bind .= 'NULL';
  361. }
  362. } else {
  363. $bind .= ':' . $col;
  364. }
  365. return $bind;
  366. }
  367. /**
  368. * @param string $table
  369. * @param array $row
  370. * @param string $fname
  371. * @return bool
  372. * @throws DBUnexpectedError
  373. */
  374. private function insertOneRow( $table, $row, $fname ) {
  375. global $wgContLang;
  376. $table = $this->tableName( $table );
  377. // "INSERT INTO tables (a, b, c)"
  378. $sql = "INSERT INTO " . $table . " (" . implode( ',', array_keys( $row ) ) . ')';
  379. $sql .= " VALUES (";
  380. // for each value, append ":key"
  381. $first = true;
  382. foreach ( $row as $col => &$val ) {
  383. if ( !$first ) {
  384. $sql .= ', ';
  385. } else {
  386. $first = false;
  387. }
  388. if ( $this->isQuotedIdentifier( $val ) ) {
  389. $sql .= $this->removeIdentifierQuotes( $val );
  390. unset( $row[$col] );
  391. } else {
  392. $sql .= $this->fieldBindStatement( $table, $col, $val );
  393. }
  394. }
  395. $sql .= ')';
  396. $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
  397. if ( $stmt === false ) {
  398. $e = oci_error( $this->conn );
  399. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  400. return false;
  401. }
  402. foreach ( $row as $col => &$val ) {
  403. $col_info = $this->fieldInfoMulti( $table, $col );
  404. $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
  405. if ( $val === null ) {
  406. // do nothing ... null was inserted in statement creation
  407. } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
  408. if ( is_object( $val ) ) {
  409. $val = $val->fetch();
  410. }
  411. // backward compatibility
  412. if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
  413. $val = $this->getInfinity();
  414. }
  415. $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
  416. if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
  417. $e = oci_error( $stmt );
  418. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  419. return false;
  420. }
  421. } else {
  422. /** @var OCI_Lob[] $lob */
  423. $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
  424. if ( $lob[$col] === false ) {
  425. $e = oci_error( $stmt );
  426. throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
  427. }
  428. if ( is_object( $val ) ) {
  429. $val = $val->fetch();
  430. }
  431. if ( $col_type == 'BLOB' ) {
  432. $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
  433. oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_BLOB );
  434. } else {
  435. $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
  436. oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
  437. }
  438. }
  439. }
  440. Wikimedia\suppressWarnings();
  441. if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
  442. $e = oci_error( $stmt );
  443. if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
  444. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  445. return false;
  446. } else {
  447. $this->mAffectedRows = oci_num_rows( $stmt );
  448. }
  449. } else {
  450. $this->mAffectedRows = oci_num_rows( $stmt );
  451. }
  452. Wikimedia\restoreWarnings();
  453. if ( isset( $lob ) ) {
  454. foreach ( $lob as $lob_v ) {
  455. $lob_v->free();
  456. }
  457. }
  458. if ( !$this->trxLevel ) {
  459. oci_commit( $this->conn );
  460. }
  461. return oci_free_statement( $stmt );
  462. }
  463. function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
  464. $insertOptions = [], $selectOptions = [], $selectJoinConds = []
  465. ) {
  466. $destTable = $this->tableName( $destTable );
  467. $sequenceData = $this->getSequenceData( $destTable );
  468. if ( $sequenceData !== false &&
  469. !isset( $varMap[$sequenceData['column']] )
  470. ) {
  471. $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
  472. }
  473. // count-alias subselect fields to avoid abigious definition errors
  474. $i = 0;
  475. foreach ( $varMap as &$val ) {
  476. $val = $val . ' field' . ( $i++ );
  477. }
  478. $selectSql = $this->selectSQLText(
  479. $srcTable,
  480. array_values( $varMap ),
  481. $conds,
  482. $fname,
  483. $selectOptions,
  484. $selectJoinConds
  485. );
  486. $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
  487. if ( in_array( 'IGNORE', $insertOptions ) ) {
  488. $this->ignoreDupValOnIndex = true;
  489. }
  490. $retval = $this->query( $sql, $fname );
  491. if ( in_array( 'IGNORE', $insertOptions ) ) {
  492. $this->ignoreDupValOnIndex = false;
  493. }
  494. return $retval;
  495. }
  496. public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
  497. $fname = __METHOD__
  498. ) {
  499. if ( !count( $rows ) ) {
  500. return true; // nothing to do
  501. }
  502. if ( !is_array( reset( $rows ) ) ) {
  503. $rows = [ $rows ];
  504. }
  505. $sequenceData = $this->getSequenceData( $table );
  506. if ( $sequenceData !== false ) {
  507. // add sequence column to each list of columns, when not set
  508. foreach ( $rows as &$row ) {
  509. if ( !isset( $row[$sequenceData['column']] ) ) {
  510. $row[$sequenceData['column']] =
  511. $this->addIdentifierQuotes( 'GET_SEQUENCE_VALUE(\'' .
  512. $sequenceData['sequence'] . '\')' );
  513. }
  514. }
  515. }
  516. return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
  517. }
  518. function tableName( $name, $format = 'quoted' ) {
  519. /*
  520. Replace reserved words with better ones
  521. Using uppercase because that's the only way Oracle can handle
  522. quoted tablenames
  523. */
  524. switch ( $name ) {
  525. case 'user':
  526. $name = 'MWUSER';
  527. break;
  528. case 'text':
  529. $name = 'PAGECONTENT';
  530. break;
  531. }
  532. return strtoupper( parent::tableName( $name, $format ) );
  533. }
  534. function tableNameInternal( $name ) {
  535. $name = $this->tableName( $name );
  536. return preg_replace( '/.*\.(.*)/', '$1', $name );
  537. }
  538. /**
  539. * Return sequence_name if table has a sequence
  540. *
  541. * @param string $table
  542. * @return bool
  543. */
  544. private function getSequenceData( $table ) {
  545. if ( $this->sequenceData == null ) {
  546. $result = $this->doQuery( "SELECT lower(asq.sequence_name),
  547. lower(atc.table_name),
  548. lower(atc.column_name)
  549. FROM all_sequences asq, all_tab_columns atc
  550. WHERE decode(
  551. atc.table_name,
  552. '{$this->tablePrefix}MWUSER',
  553. '{$this->tablePrefix}USER',
  554. atc.table_name
  555. ) || '_' ||
  556. atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
  557. AND asq.sequence_owner = upper('{$this->dbName}')
  558. AND atc.owner = upper('{$this->dbName}')" );
  559. while ( ( $row = $result->fetchRow() ) !== false ) {
  560. $this->sequenceData[$row[1]] = [
  561. 'sequence' => $row[0],
  562. 'column' => $row[2]
  563. ];
  564. }
  565. }
  566. $table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
  567. return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
  568. }
  569. /**
  570. * Returns the size of a text field, or -1 for "unlimited"
  571. *
  572. * @param string $table
  573. * @param string $field
  574. * @return mixed
  575. */
  576. function textFieldSize( $table, $field ) {
  577. $fieldInfoData = $this->fieldInfo( $table, $field );
  578. return $fieldInfoData->maxLength();
  579. }
  580. function limitResult( $sql, $limit, $offset = false ) {
  581. if ( $offset === false ) {
  582. $offset = 0;
  583. }
  584. return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
  585. }
  586. function encodeBlob( $b ) {
  587. return new Blob( $b );
  588. }
  589. function decodeBlob( $b ) {
  590. if ( $b instanceof Blob ) {
  591. $b = $b->fetch();
  592. }
  593. return $b;
  594. }
  595. function unionQueries( $sqls, $all ) {
  596. $glue = ' UNION ALL ';
  597. return 'SELECT * ' . ( $all ? '' : '/* UNION_UNIQUE */ ' ) .
  598. 'FROM (' . implode( $glue, $sqls ) . ')';
  599. }
  600. function wasDeadlock() {
  601. return $this->lastErrno() == 'OCI-00060';
  602. }
  603. function duplicateTableStructure( $oldName, $newName, $temporary = false,
  604. $fname = __METHOD__
  605. ) {
  606. $temporary = $temporary ? 'TRUE' : 'FALSE';
  607. $newName = strtoupper( $newName );
  608. $oldName = strtoupper( $oldName );
  609. $tabName = substr( $newName, strlen( $this->tablePrefix ) );
  610. $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
  611. $newPrefix = strtoupper( $this->tablePrefix );
  612. return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
  613. "'$oldPrefix', '$newPrefix', $temporary ); END;" );
  614. }
  615. function listTables( $prefix = null, $fname = __METHOD__ ) {
  616. $listWhere = '';
  617. if ( !empty( $prefix ) ) {
  618. $listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
  619. }
  620. $owner = strtoupper( $this->dbName );
  621. $result = $this->doQuery( "SELECT table_name FROM all_tables " .
  622. "WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
  623. // dirty code ... i know
  624. $endArray = [];
  625. $endArray[] = strtoupper( $prefix . 'MWUSER' );
  626. $endArray[] = strtoupper( $prefix . 'PAGE' );
  627. $endArray[] = strtoupper( $prefix . 'IMAGE' );
  628. $fixedOrderTabs = $endArray;
  629. while ( ( $row = $result->fetchRow() ) !== false ) {
  630. if ( !in_array( $row['table_name'], $fixedOrderTabs ) ) {
  631. $endArray[] = $row['table_name'];
  632. }
  633. }
  634. return $endArray;
  635. }
  636. public function dropTable( $tableName, $fName = __METHOD__ ) {
  637. $tableName = $this->tableName( $tableName );
  638. if ( !$this->tableExists( $tableName ) ) {
  639. return false;
  640. }
  641. return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
  642. }
  643. function timestamp( $ts = 0 ) {
  644. return wfTimestamp( TS_ORACLE, $ts );
  645. }
  646. /**
  647. * Return aggregated value function call
  648. *
  649. * @param array $valuedata
  650. * @param string $valuename
  651. * @return mixed
  652. */
  653. public function aggregateValue( $valuedata, $valuename = 'value' ) {
  654. return $valuedata;
  655. }
  656. /**
  657. * @return string Wikitext of a link to the server software's web site
  658. */
  659. public function getSoftwareLink() {
  660. return '[{{int:version-db-oracle-url}} Oracle]';
  661. }
  662. /**
  663. * @return string Version information from the database
  664. */
  665. function getServerVersion() {
  666. // better version number, fallback on driver
  667. $rset = $this->doQuery(
  668. 'SELECT version FROM product_component_version ' .
  669. 'WHERE UPPER(product) LIKE \'ORACLE DATABASE%\''
  670. );
  671. $row = $rset->fetchRow();
  672. if ( !$row ) {
  673. return oci_server_version( $this->conn );
  674. }
  675. return $row['version'];
  676. }
  677. /**
  678. * Query whether a given index exists
  679. * @param string $table
  680. * @param string $index
  681. * @param string $fname
  682. * @return bool
  683. */
  684. function indexExists( $table, $index, $fname = __METHOD__ ) {
  685. $table = $this->tableName( $table );
  686. $table = strtoupper( $this->removeIdentifierQuotes( $table ) );
  687. $index = strtoupper( $index );
  688. $owner = strtoupper( $this->dbName );
  689. $sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
  690. $res = $this->doQuery( $sql );
  691. if ( $res ) {
  692. $count = $res->numRows();
  693. $res->free();
  694. } else {
  695. $count = 0;
  696. }
  697. return $count != 0;
  698. }
  699. /**
  700. * Query whether a given table exists (in the given schema, or the default mw one if not given)
  701. * @param string $table
  702. * @param string $fname
  703. * @return bool
  704. */
  705. function tableExists( $table, $fname = __METHOD__ ) {
  706. $table = $this->tableName( $table );
  707. $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
  708. $owner = $this->addQuotes( strtoupper( $this->dbName ) );
  709. $sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
  710. $res = $this->doQuery( $sql );
  711. if ( $res && $res->numRows() > 0 ) {
  712. $exists = true;
  713. } else {
  714. $exists = false;
  715. }
  716. $res->free();
  717. return $exists;
  718. }
  719. /**
  720. * Function translates mysql_fetch_field() functionality on ORACLE.
  721. * Caching is present for reducing query time.
  722. * For internal calls. Use fieldInfo for normal usage.
  723. * Returns false if the field doesn't exist
  724. *
  725. * @param array|string $table
  726. * @param string $field
  727. * @return ORAField|ORAResult|false
  728. */
  729. private function fieldInfoMulti( $table, $field ) {
  730. $field = strtoupper( $field );
  731. if ( is_array( $table ) ) {
  732. $table = array_map( [ $this, 'tableNameInternal' ], $table );
  733. $tableWhere = 'IN (';
  734. foreach ( $table as &$singleTable ) {
  735. $singleTable = $this->removeIdentifierQuotes( $singleTable );
  736. if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
  737. return $this->mFieldInfoCache["$singleTable.$field"];
  738. }
  739. $tableWhere .= '\'' . $singleTable . '\',';
  740. }
  741. $tableWhere = rtrim( $tableWhere, ',' ) . ')';
  742. } else {
  743. $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
  744. if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
  745. return $this->mFieldInfoCache["$table.$field"];
  746. }
  747. $tableWhere = '= \'' . $table . '\'';
  748. }
  749. $fieldInfoStmt = oci_parse(
  750. $this->conn,
  751. 'SELECT * FROM wiki_field_info_full WHERE table_name ' .
  752. $tableWhere . ' and column_name = \'' . $field . '\''
  753. );
  754. if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
  755. $e = oci_error( $fieldInfoStmt );
  756. $this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
  757. return false;
  758. }
  759. $res = new ORAResult( $this, $fieldInfoStmt );
  760. if ( $res->numRows() == 0 ) {
  761. if ( is_array( $table ) ) {
  762. foreach ( $table as &$singleTable ) {
  763. $this->mFieldInfoCache["$singleTable.$field"] = false;
  764. }
  765. } else {
  766. $this->mFieldInfoCache["$table.$field"] = false;
  767. }
  768. $fieldInfoTemp = null;
  769. } else {
  770. $fieldInfoTemp = new ORAField( $res->fetchRow() );
  771. $table = $fieldInfoTemp->tableName();
  772. $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
  773. }
  774. $res->free();
  775. return $fieldInfoTemp;
  776. }
  777. /**
  778. * @throws DBUnexpectedError
  779. * @param string $table
  780. * @param string $field
  781. * @return ORAField
  782. */
  783. function fieldInfo( $table, $field ) {
  784. if ( is_array( $table ) ) {
  785. throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
  786. }
  787. return $this->fieldInfoMulti( $table, $field );
  788. }
  789. protected function doBegin( $fname = __METHOD__ ) {
  790. $this->trxLevel = 1;
  791. $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
  792. }
  793. protected function doCommit( $fname = __METHOD__ ) {
  794. if ( $this->trxLevel ) {
  795. $ret = oci_commit( $this->conn );
  796. if ( !$ret ) {
  797. throw new DBUnexpectedError( $this, $this->lastError() );
  798. }
  799. $this->trxLevel = 0;
  800. $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
  801. }
  802. }
  803. protected function doRollback( $fname = __METHOD__ ) {
  804. if ( $this->trxLevel ) {
  805. oci_rollback( $this->conn );
  806. $this->trxLevel = 0;
  807. $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
  808. }
  809. }
  810. function sourceStream(
  811. $fp,
  812. callable $lineCallback = null,
  813. callable $resultCallback = null,
  814. $fname = __METHOD__, callable $inputCallback = null
  815. ) {
  816. $cmd = '';
  817. $done = false;
  818. $dollarquote = false;
  819. $replacements = [];
  820. // Defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}';
  821. while ( !feof( $fp ) ) {
  822. if ( $lineCallback ) {
  823. call_user_func( $lineCallback );
  824. }
  825. $line = trim( fgets( $fp, 1024 ) );
  826. $sl = strlen( $line ) - 1;
  827. if ( $sl < 0 ) {
  828. continue;
  829. }
  830. if ( '-' == $line[0] && '-' == $line[1] ) {
  831. continue;
  832. }
  833. // Allow dollar quoting for function declarations
  834. if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
  835. if ( $dollarquote ) {
  836. $dollarquote = false;
  837. $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
  838. $done = true;
  839. } else {
  840. $dollarquote = true;
  841. }
  842. } elseif ( !$dollarquote ) {
  843. if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
  844. $done = true;
  845. $line = substr( $line, 0, $sl );
  846. }
  847. }
  848. if ( $cmd != '' ) {
  849. $cmd .= ' ';
  850. }
  851. $cmd .= "$line\n";
  852. if ( $done ) {
  853. $cmd = str_replace( ';;', ";", $cmd );
  854. if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
  855. if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
  856. $replacements[$defines[2]] = $defines[1];
  857. }
  858. } else {
  859. foreach ( $replacements as $mwVar => $scVar ) {
  860. $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
  861. }
  862. $cmd = $this->replaceVars( $cmd );
  863. if ( $inputCallback ) {
  864. call_user_func( $inputCallback, $cmd );
  865. }
  866. $res = $this->doQuery( $cmd );
  867. if ( $resultCallback ) {
  868. call_user_func( $resultCallback, $res, $this );
  869. }
  870. if ( false === $res ) {
  871. $err = $this->lastError();
  872. return "Query \"{$cmd}\" failed with error code \"$err\".\n";
  873. }
  874. }
  875. $cmd = '';
  876. $done = false;
  877. }
  878. }
  879. return true;
  880. }
  881. function selectDB( $db ) {
  882. $this->dbName = $db;
  883. if ( $db == null || $db == $this->user ) {
  884. return true;
  885. }
  886. $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
  887. $stmt = oci_parse( $this->conn, $sql );
  888. Wikimedia\suppressWarnings();
  889. $success = oci_execute( $stmt );
  890. Wikimedia\restoreWarnings();
  891. if ( !$success ) {
  892. $e = oci_error( $stmt );
  893. if ( $e['code'] != '1435' ) {
  894. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  895. }
  896. return false;
  897. }
  898. return true;
  899. }
  900. function strencode( $s ) {
  901. return str_replace( "'", "''", $s );
  902. }
  903. function addQuotes( $s ) {
  904. global $wgContLang;
  905. if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
  906. $s = $wgContLang->checkTitleEncoding( $s );
  907. }
  908. return "'" . $this->strencode( $s ) . "'";
  909. }
  910. public function addIdentifierQuotes( $s ) {
  911. if ( !$this->getFlag( DBO_DDLMODE ) ) {
  912. $s = '/*Q*/' . $s;
  913. }
  914. return $s;
  915. }
  916. public function removeIdentifierQuotes( $s ) {
  917. return strpos( $s, '/*Q*/' ) === false ? $s : substr( $s, 5 );
  918. }
  919. public function isQuotedIdentifier( $s ) {
  920. return strpos( $s, '/*Q*/' ) !== false;
  921. }
  922. private function wrapFieldForWhere( $table, &$col, &$val ) {
  923. global $wgContLang;
  924. $col_info = $this->fieldInfoMulti( $table, $col );
  925. $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
  926. if ( $col_type == 'CLOB' ) {
  927. $col = 'TO_CHAR(' . $col . ')';
  928. $val = $wgContLang->checkTitleEncoding( $val );
  929. } elseif ( $col_type == 'VARCHAR2' ) {
  930. $val = $wgContLang->checkTitleEncoding( $val );
  931. }
  932. }
  933. private function wrapConditionsForWhere( $table, $conds, $parentCol = null ) {
  934. $conds2 = [];
  935. foreach ( $conds as $col => $val ) {
  936. if ( is_array( $val ) ) {
  937. $conds2[$col] = $this->wrapConditionsForWhere( $table, $val, $col );
  938. } else {
  939. if ( is_numeric( $col ) && $parentCol != null ) {
  940. $this->wrapFieldForWhere( $table, $parentCol, $val );
  941. } else {
  942. $this->wrapFieldForWhere( $table, $col, $val );
  943. }
  944. $conds2[$col] = $val;
  945. }
  946. }
  947. return $conds2;
  948. }
  949. function selectRow( $table, $vars, $conds, $fname = __METHOD__,
  950. $options = [], $join_conds = []
  951. ) {
  952. if ( is_array( $conds ) ) {
  953. $conds = $this->wrapConditionsForWhere( $table, $conds );
  954. }
  955. return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
  956. }
  957. /**
  958. * Returns an optional USE INDEX clause to go after the table, and a
  959. * string to go at the end of the query
  960. *
  961. * @param array $options An associative array of options to be turned into
  962. * an SQL query, valid keys are listed in the function.
  963. * @return array
  964. */
  965. function makeSelectOptions( $options ) {
  966. $preLimitTail = $postLimitTail = '';
  967. $startOpts = '';
  968. $noKeyOptions = [];
  969. foreach ( $options as $key => $option ) {
  970. if ( is_numeric( $key ) ) {
  971. $noKeyOptions[$option] = true;
  972. }
  973. }
  974. $preLimitTail .= $this->makeGroupByWithHaving( $options );
  975. $preLimitTail .= $this->makeOrderBy( $options );
  976. if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
  977. $postLimitTail .= ' FOR UPDATE';
  978. }
  979. if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
  980. $startOpts .= 'DISTINCT';
  981. }
  982. if ( isset( $options['USE INDEX'] ) && !is_array( $options['USE INDEX'] ) ) {
  983. $useIndex = $this->useIndexClause( $options['USE INDEX'] );
  984. } else {
  985. $useIndex = '';
  986. }
  987. if ( isset( $options['IGNORE INDEX'] ) && !is_array( $options['IGNORE INDEX'] ) ) {
  988. $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
  989. } else {
  990. $ignoreIndex = '';
  991. }
  992. return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
  993. }
  994. public function delete( $table, $conds, $fname = __METHOD__ ) {
  995. global $wgActorTableSchemaMigrationStage;
  996. if ( is_array( $conds ) ) {
  997. $conds = $this->wrapConditionsForWhere( $table, $conds );
  998. }
  999. // a hack for deleting pages, users and images (which have non-nullable FKs)
  1000. // all deletions on these tables have transactions so final failure rollbacks these updates
  1001. // @todo: Normalize the schema to match MySQL, no special FKs and such
  1002. $table = $this->tableName( $table );
  1003. if ( $table == $this->tableName( 'user' ) && $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
  1004. $this->update( 'archive', [ 'ar_user' => 0 ],
  1005. [ 'ar_user' => $conds['user_id'] ], $fname );
  1006. $this->update( 'ipblocks', [ 'ipb_user' => 0 ],
  1007. [ 'ipb_user' => $conds['user_id'] ], $fname );
  1008. $this->update( 'image', [ 'img_user' => 0 ],
  1009. [ 'img_user' => $conds['user_id'] ], $fname );
  1010. $this->update( 'oldimage', [ 'oi_user' => 0 ],
  1011. [ 'oi_user' => $conds['user_id'] ], $fname );
  1012. $this->update( 'filearchive', [ 'fa_deleted_user' => 0 ],
  1013. [ 'fa_deleted_user' => $conds['user_id'] ], $fname );
  1014. $this->update( 'filearchive', [ 'fa_user' => 0 ],
  1015. [ 'fa_user' => $conds['user_id'] ], $fname );
  1016. $this->update( 'uploadstash', [ 'us_user' => 0 ],
  1017. [ 'us_user' => $conds['user_id'] ], $fname );
  1018. $this->update( 'recentchanges', [ 'rc_user' => 0 ],
  1019. [ 'rc_user' => $conds['user_id'] ], $fname );
  1020. $this->update( 'logging', [ 'log_user' => 0 ],
  1021. [ 'log_user' => $conds['user_id'] ], $fname );
  1022. } elseif ( $table == $this->tableName( 'image' ) ) {
  1023. $this->update( 'oldimage', [ 'oi_name' => 0 ],
  1024. [ 'oi_name' => $conds['img_name'] ], $fname );
  1025. }
  1026. return parent::delete( $table, $conds, $fname );
  1027. }
  1028. /**
  1029. * @param string $table
  1030. * @param array $values
  1031. * @param array $conds
  1032. * @param string $fname
  1033. * @param array $options
  1034. * @return bool
  1035. * @throws DBUnexpectedError
  1036. */
  1037. function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
  1038. global $wgContLang;
  1039. $table = $this->tableName( $table );
  1040. $opts = $this->makeUpdateOptions( $options );
  1041. $sql = "UPDATE $opts $table SET ";
  1042. $first = true;
  1043. foreach ( $values as $col => &$val ) {
  1044. $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
  1045. if ( !$first ) {
  1046. $sqlSet = ', ' . $sqlSet;
  1047. } else {
  1048. $first = false;
  1049. }
  1050. $sql .= $sqlSet;
  1051. }
  1052. if ( $conds !== [] && $conds !== '*' ) {
  1053. $conds = $this->wrapConditionsForWhere( $table, $conds );
  1054. $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
  1055. }
  1056. $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
  1057. if ( $stmt === false ) {
  1058. $e = oci_error( $this->conn );
  1059. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  1060. return false;
  1061. }
  1062. foreach ( $values as $col => &$val ) {
  1063. $col_info = $this->fieldInfoMulti( $table, $col );
  1064. $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
  1065. if ( $val === null ) {
  1066. // do nothing ... null was inserted in statement creation
  1067. } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
  1068. if ( is_object( $val ) ) {
  1069. $val = $val->getData();
  1070. }
  1071. if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
  1072. $val = '31-12-2030 12:00:00.000000';
  1073. }
  1074. $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
  1075. if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
  1076. $e = oci_error( $stmt );
  1077. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  1078. return false;
  1079. }
  1080. } else {
  1081. /** @var OCI_Lob[] $lob */
  1082. $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
  1083. if ( $lob[$col] === false ) {
  1084. $e = oci_error( $stmt );
  1085. throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
  1086. }
  1087. if ( is_object( $val ) ) {
  1088. $val = $val->getData();
  1089. }
  1090. if ( $col_type == 'BLOB' ) {
  1091. $lob[$col]->writeTemporary( $val );
  1092. oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, SQLT_BLOB );
  1093. } else {
  1094. $lob[$col]->writeTemporary( $val );
  1095. oci_bind_by_name( $stmt, ":$col", $lob[$col], -1, OCI_B_CLOB );
  1096. }
  1097. }
  1098. }
  1099. Wikimedia\suppressWarnings();
  1100. if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
  1101. $e = oci_error( $stmt );
  1102. if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
  1103. $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
  1104. return false;
  1105. } else {
  1106. $this->mAffectedRows = oci_num_rows( $stmt );
  1107. }
  1108. } else {
  1109. $this->mAffectedRows = oci_num_rows( $stmt );
  1110. }
  1111. Wikimedia\restoreWarnings();
  1112. if ( isset( $lob ) ) {
  1113. foreach ( $lob as $lob_v ) {
  1114. $lob_v->free();
  1115. }
  1116. }
  1117. if ( !$this->trxLevel ) {
  1118. oci_commit( $this->conn );
  1119. }
  1120. return oci_free_statement( $stmt );
  1121. }
  1122. function bitNot( $field ) {
  1123. // expecting bit-fields smaller than 4bytes
  1124. return 'BITNOT(' . $field . ')';
  1125. }
  1126. function bitAnd( $fieldLeft, $fieldRight ) {
  1127. return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
  1128. }
  1129. function bitOr( $fieldLeft, $fieldRight ) {
  1130. return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
  1131. }
  1132. function getDBname() {
  1133. return $this->dbName;
  1134. }
  1135. function getServer() {
  1136. return $this->server;
  1137. }
  1138. public function buildGroupConcatField(
  1139. $delim, $table, $field, $conds = '', $join_conds = []
  1140. ) {
  1141. $fld = "LISTAGG($field," . $this->addQuotes( $delim ) . ") WITHIN GROUP (ORDER BY $field)";
  1142. return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
  1143. }
  1144. public function buildSubstring( $input, $startPosition, $length = null ) {
  1145. $this->assertBuildSubstringParams( $startPosition, $length );
  1146. $params = [ $input, $startPosition ];
  1147. if ( $length !== null ) {
  1148. $params[] = $length;
  1149. }
  1150. return 'SUBSTR(' . implode( ',', $params ) . ')';
  1151. }
  1152. /**
  1153. * @param string $field Field or column to cast
  1154. * @return string
  1155. * @since 1.28
  1156. */
  1157. public function buildStringCast( $field ) {
  1158. return 'CAST ( ' . $field . ' AS VARCHAR2 )';
  1159. }
  1160. public function getInfinity() {
  1161. return '31-12-2030 12:00:00.000000';
  1162. }
  1163. }