* @ingroup Database
*/
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Timestamp\ConvertibleTimestamp;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\DatabaseDomain;
use Wikimedia\Rdbms\Blob;
use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
use Wikimedia\Rdbms\DBConnectionError;
use Wikimedia\Rdbms\DBUnexpectedError;
use Wikimedia\Rdbms\DBExpectedError;
/** @var array */
private $mFieldInfoCache = [];
- function __construct( array $p ) {
- $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
- parent::__construct( $p );
- Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
+ /** @var string[] Map of (reserved table name => alternate table name) */
+ private $keywordTableMap = [];
+
+ /**
+ * @see Database::__construct()
+ * @param array $params Additional parameters include:
+ * - keywordTableMap : Map of reserved table names to alternative table names to use
+ */
+ function __construct( array $params ) {
+ $this->keywordTableMap = $params['keywordTableMap'] ?? [];
+ $params['tablePrefix'] = strtoupper( $params['tablePrefix'] );
+ parent::__construct( $params );
}
function __destruct() {
}
protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
- global $wgDBOracleDRCP;
-
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError(
$this,
return null;
}
- if ( $wgDBOracleDRCP ) {
- $this->setFlag( DBO_PERSISTENT );
- }
-
$session_mode = ( $this->flags & DBO_SYSDBA ) ? OCI_SYSDBA : OCI_DEFAULT;
Wikimedia\suppressWarnings();
* @return bool|mixed|ORAResult
*/
protected function doQuery( $sql ) {
- wfDebug( "SQL: [$sql]\n" );
- if ( !StringUtils::isUtf8( $sql ) ) {
- throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
+ if ( !mb_check_encoding( (string)$sql, 'UTF-8' ) ) {
+ throw new DBUnexpectedError( $this, "SQL encoding is invalid\n$sql" );
}
// handle some oracle specifics
/**
* Frees resources associated with the LOB descriptor
- * @param ResultWrapper|ORAResult $res
+ * @param IResultWrapper|ORAResult $res
*/
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
}
/**
- * @param ResultWrapper|ORAResult $res
- * @return mixed
+ * @param IResultWrapper|ORAResult $res
+ * @return stdClass|bool
*/
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
}
/**
- * @param ResultWrapper|ORAResult $res
- * @return mixed
+ * @param IResultWrapper|ORAResult $res
+ * @return stdClass|bool
*/
function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
}
/**
- * @param ResultWrapper|ORAResult $res
+ * @param IResultWrapper|ORAResult $res
* @return int
*/
function numRows( $res ) {
}
/**
- * @param ResultWrapper|ORAResult $res
+ * @param IResultWrapper|ORAResult $res
* @return int
*/
function numFields( $res ) {
}
if ( $val === null ) {
- if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
+ if (
+ $col_info != false &&
+ $col_info->isNullable() == 0 &&
+ $col_info->defaultValue() != null
+ ) {
$bind .= 'DEFAULT';
} else {
$bind .= 'NULL';
$this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
if ( $stmt === false ) {
$e = oci_error( $this->conn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
return false;
}
}
// backward compatibility
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ if (
+ preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+ strtolower( $val ) == 'infinity'
+ ) {
$val = $this->getInfinity();
}
- $val = MediaWikiServices::getInstance()->getContentLanguage()->
- checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
$e = oci_error( $stmt );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
return false;
}
$lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ throw new DBUnexpectedError(
+ $this,
+ "Cannot create LOB descriptor: " . $e['message']
+ );
}
if ( is_object( $val ) ) {
if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
return false;
} else {
if ( $sequenceData !== false &&
!isset( $varMap[$sequenceData['column']] )
) {
- $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
+ $varMap[$sequenceData['column']] =
+ 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
}
// count-alias subselect fields to avoid abigious definition errors
$selectJoinConds
);
- $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
+ $sql = "INSERT INTO $destTable (" .
+ implode( ',', array_keys( $varMap ) ) . ') ' . $selectSql;
if ( in_array( 'IGNORE', $insertOptions ) ) {
$this->ignoreDupValOnIndex = true;
return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
}
- function tableName( $name, $format = 'quoted' ) {
- /*
- Replace reserved words with better ones
- Using uppercase because that's the only way Oracle can handle
- quoted tablenames
- */
- switch ( $name ) {
- case 'user':
- $name = 'MWUSER';
- break;
- case 'text':
- $name = 'PAGECONTENT';
- break;
- }
+ public function tableName( $name, $format = 'quoted' ) {
+ // Replace reserved words with better ones
+ $name = $this->remappedTableName( $name );
return strtoupper( parent::tableName( $name, $format ) );
}
+ /**
+ * @param string $name
+ * @return string Value of $name or remapped name if $name is a reserved keyword
+ */
+ public function remappedTableName( $name ) {
+ return $this->keywordTableMap[$name] ?? $name;
+ }
+
function tableNameInternal( $name ) {
$name = $this->tableName( $name );
* Return sequence_name if table has a sequence
*
* @param string $table
- * @return bool
+ * @return string[]|bool
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
- $result = $this->doQuery( "SELECT lower(asq.sequence_name),
- lower(atc.table_name),
- lower(atc.column_name)
- FROM all_sequences asq, all_tab_columns atc
- WHERE decode(
- atc.table_name,
- '{$this->tablePrefix}MWUSER',
- '{$this->tablePrefix}USER',
- atc.table_name
- ) || '_' ||
- atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->getDBname()}')
- AND atc.owner = upper('{$this->getDBname()}')" );
+ $dbname = $this->currentDomain->getDatabase();
+ $prefix = $this->currentDomain->getTablePrefix();
+ // See https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm
+ $decodeArgs = [ 'atc.table_name' ]; // the switch
+ foreach ( $this->keywordTableMap as $reserved => $alternative ) {
+ $search = strtoupper( $prefix . $alternative ); // case
+ $replace = strtoupper( $prefix . $reserved ); // result
+ $decodeArgs[] = $this->addQuotes( $search );
+ $decodeArgs[] = $this->addQuotes( $replace );
+ }
+ $decodeArgs[] = [ 'atc.table_name' ]; // default
+ $decodeArgs = implode( ', ', $decodeArgs );
+
+ $result = $this->doQuery(
+ "SELECT lower(asq.sequence_name), lower(atc.table_name), lower(atc.column_name)
+ FROM all_sequences asq, all_tab_columns atc
+ WHERE decode({$decodeArgs}) || '_' ||
+ atc.column_name || '_SEQ' = '{$prefix}' || asq.sequence_name
+ AND asq.sequence_owner = upper('{$dbname}')
+ AND atc.owner = upper('{$dbname}')"
+ );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = [
$fname = __METHOD__
) {
$temporary = $temporary ? 'TRUE' : 'FALSE';
+ $tablePrefix = $this->currentDomain->getTablePrefix();
$newName = strtoupper( $newName );
$oldName = strtoupper( $oldName );
- $tabName = substr( $newName, strlen( $this->tablePrefix ) );
+ $tabName = substr( $newName, strlen( $tablePrefix ) );
$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
- $newPrefix = strtoupper( $this->tablePrefix );
+ $newPrefix = strtoupper( $tablePrefix );
return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
"'$oldPrefix', '$newPrefix', $temporary ); END;" );
return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
}
- function timestamp( $ts = 0 ) {
- return wfTimestamp( TS_ORACLE, $ts );
+ public function timestamp( $ts = 0 ) {
+ $t = new ConvertibleTimestamp( $ts );
+ // Let errors bubble up to avoid putting garbage in the DB
+ return $t->getTimestamp( TS_ORACLE );
}
/**
*/
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
+ throw new DBUnexpectedError(
+ $this,
+ 'DatabaseOracle::fieldInfo called with table array!'
+ );
}
return $this->fieldInfoMulti( $table, $field );
}
function addQuotes( $s ) {
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- if ( isset( $contLang->mLoaded ) && $contLang->mLoaded ) {
- $s = $contLang->checkTitleEncoding( $s );
- }
-
- return "'" . $this->strencode( $s ) . "'";
+ return "'" . $this->strencode( $this->getVerifiedUTF8( $s ) ) . "'";
}
public function addIdentifierQuotes( $s ) {
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
if ( $col_type == 'CLOB' ) {
$col = 'TO_CHAR(' . $col . ')';
- $val =
- MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
} elseif ( $col_type == 'VARCHAR2' ) {
- $val =
- MediaWikiServices::getInstance()->getContentLanguage()->checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
}
}
$val = $val->getData();
}
- if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ if (
+ preg_match( '/^timestamp.*/i', $col_type ) == 1 &&
+ strtolower( $val ) == 'infinity'
+ ) {
$val = '31-12-2030 12:00:00.000000';
}
- $val = MediaWikiServices::getInstance()->getContentLanguage()->
- checkTitleEncoding( $val );
+ $val = $this->getVerifiedUTF8( $val );
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
$lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
- throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ throw new DBUnexpectedError(
+ $this,
+ "Cannot create LOB descriptor: " . $e['message']
+ );
}
if ( is_object( $val ) ) {
public function getInfinity() {
return '31-12-2030 12:00:00.000000';
}
+
+ /**
+ * @param string $s
+ * @return string
+ */
+ private function getVerifiedUTF8( $s ) {
+ if ( mb_check_encoding( (string)$s, 'UTF-8' ) ) {
+ return $s; // valid
+ }
+
+ throw new DBUnexpectedError( $this, "Non BLOB/CLOB field must be UTF-8." );
+ }
}