protected $mBinaryColumnCache = null;
protected $mBitColumnCache = null;
protected $mIgnoreDupKeyErrors = false;
+ protected $mIgnoreErrors = [];
protected $mPort;
$success = (bool)$stmt;
}
+ // make a copy so that anything we add below does not get reflected in future queries
+ $ignoreErrors = $this->mIgnoreErrors;
+
if ( $this->mIgnoreDupKeyErrors ) {
- // ignore duplicate key errors, but nothing else
+ // ignore duplicate key errors
// this emulates INSERT IGNORE in MySQL
- if ( $success === false ) {
- $errors = sqlsrv_errors( SQLSRV_ERR_ERRORS );
- $success = true;
-
- foreach ( $errors as $err ) {
- if ( $err['SQLSTATE'] == '23000' && $err['code'] == '2601' ) {
- continue; // duplicate key error caused by unique index
- } elseif ( $err['SQLSTATE'] == '23000' && $err['code'] == '2627' ) {
- continue; // duplicate key error caused by primary key
- } elseif ( $err['SQLSTATE'] == '01000' && $err['code'] == '3621' ) {
- continue; // generic "the statement has been terminated" error
- }
+ $ignoreErrors[] = '2601'; // duplicate key error caused by unique index
+ $ignoreErrors[] = '2627'; // duplicate key error caused by primary key
+ $ignoreErrors[] = '3621'; // generic "the statement has been terminated" error
+ }
- $success = false; // getting here means we got an error we weren't expecting
- break;
- }
+ if ( $success === false ) {
+ $errors = sqlsrv_errors();
+ $success = true;
- if ( $success ) {
- $this->mAffectedRows = 0;
- return $stmt;
+ foreach ( $errors as $err ) {
+ if ( !in_array( $err['code'], $ignoreErrors ) ) {
+ $success = false;
+ break;
}
}
- }
- if ( $success === false ) {
- return false;
+ if ( $success === false ) {
+ return false;
+ }
}
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $stmt );
$res = $res->result;
}
- return sqlsrv_num_rows( $res );
+ $ret = sqlsrv_num_rows( $res );
+
+ if ( $ret === false ) {
+ // we cannot get an amount of rows from this cursor type
+ // has_rows returns bool true/false if the result has rows
+ $ret = (int)sqlsrv_has_rows( $res );
+ }
+
+ return $ret;
}
/**
# This does not return the same info as MYSQL would, but that's OK
# because MediaWiki never uses the returned value except to check for
# the existance of indexes.
- $sql = "sp_helpindex '" . $table . "'";
+ $sql = "sp_helpindex '" . $this->tableName( $table ) . "'";
$res = $this->query( $sql, $fname );
+
if ( !$res ) {
return null;
}
$row = $ret->fetchObject();
if ( is_object( $row ) ) {
$this->mInsertId = $row->$identity;
+
+ // it seems that mAffectedRows is -1 sometimes when OUTPUT INSERTED.identity is used
+ // if we got an identity back, we know for sure a row was affected, so adjust that here
+ if ( $this->mAffectedRows == -1 ) {
+ $this->mAffectedRows = 1;
+ }
}
}
}
return strlen( $name ) && $name[0] == '[' && substr( $name, -1, 1 ) == ']';
}
+ /**
+ * MS SQL supports more pattern operators than other databases (ex: [,],^)
+ *
+ * @param string $s
+ * @return string
+ */
+ protected function escapeLikeInternal( $s ) {
+ return addcslashes( $s, '\%_[]^' );
+ }
+
+ /**
+ * MS SQL requires specifying the escape character used in a LIKE query
+ * or using Square brackets to surround characters that are to be escaped
+ * http://msdn.microsoft.com/en-us/library/ms179859.aspx
+ * Here we take the Specify-Escape-Character approach since it's less
+ * invasive, renders a query that is closer to other DB's and better at
+ * handling square bracket escaping
+ *
+ * @return string Fully built LIKE statement
+ */
+ public function buildLike() {
+ $params = func_get_args();
+ if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+
+ return parent::buildLike( $params ) . " ESCAPE '\' ";
+ }
+
/**
* @param string $db
* @return bool
return $table;
}
+ /**
+ * Delete a table
+ * @param string $tableName
+ * @param string $fName
+ * @return bool|ResultWrapper
+ * @since 1.18
+ */
+ public function dropTable( $tableName, $fName = __METHOD__ ) {
+ if ( !$this->tableExists( $tableName, $fName ) ) {
+ return false;
+ }
+
+ // parent function incorrectly appends CASCADE, which we don't want
+ $sql = "DROP TABLE " . $this->tableName( $tableName );
+
+ return $this->query( $sql, $fName );
+ }
+
/**
* Called in the installer and updater.
* Probably doesn't need to be called anywhere else in the codebase.
public function scrollableCursor( $value = null ) {
return wfSetVar( $this->mScrollableCursor, $value );
}
+
+ /**
+ * Called in the installer and updater.
+ * Probably doesn't need to be called anywhere else in the codebase.
+ * @param array|null $value
+ * @return array|null
+ */
+ public function ignoreErrors( array $value = null ) {
+ return wfSetVar( $this->mIgnoreErrors, $value );
+ }
} // end DatabaseMssql class
/**