rdbms: make Database query error handling more strict
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMssql.php
index 885880a..4c187f2 100644 (file)
@@ -359,6 +359,28 @@ class DatabaseMssql extends Database {
                }
        }
 
+       protected function wasKnownStatementRollbackError() {
+               $errors = sqlsrv_errors( SQLSRV_ERR_ALL );
+               if ( !$errors ) {
+                       return false;
+               }
+               // The transaction vs statement rollback behavior depends on XACT_ABORT, so make sure
+               // that the "statement has been terminated" error (3621) is specifically present.
+               // https://docs.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql
+               $statementOnly = false;
+               $codeWhitelist = [ '2601', '2627', '547' ];
+               foreach ( $errors as $error ) {
+                       if ( $error['code'] == '3621' ) {
+                               $statementOnly = true;
+                       } elseif ( !in_array( $error['code'], $codeWhitelist ) ) {
+                               $statementOnly = false;
+                               break;
+                       }
+               }
+
+               return $statementOnly;
+       }
+
        /**
         * @return int
         */
@@ -505,20 +527,26 @@ class DatabaseMssql extends Database {
         * Returns -1 if count cannot be found
         * Takes same arguments as Database::select()
         * @param string $table
-        * @param string $vars
+        * @param string $var
         * @param string $conds
         * @param string $fname
         * @param array $options
         * @param array $join_conds
         * @return int
         */
-       public function estimateRowCount( $table, $vars = '*', $conds = '',
+       public function estimateRowCount( $table, $var = '*', $conds = '',
                $fname = __METHOD__, $options = [], $join_conds = []
        ) {
+               $conds = $this->normalizeConditions( $conds, $fname );
+               $column = $this->extractSingleFieldFromList( $var );
+               if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
+                       $conds[] = "$column IS NOT NULL";
+               }
+
                // http://msdn2.microsoft.com/en-us/library/aa259203.aspx
                $options['EXPLAIN'] = true;
                $options['FOR COUNT'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
+               $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
 
                $rows = -1;
                if ( $res ) {
@@ -1047,6 +1075,19 @@ class DatabaseMssql extends Database {
                return false;
        }
 
+       protected function doSavepoint( $identifier, $fname ) {
+               $this->query( 'SAVE TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
+       }
+
+       protected function doReleaseSavepoint( $identifier, $fname ) {
+               // Not supported. Also not really needed, a new doSavepoint() for the
+               // same identifier will overwrite the old.
+       }
+
+       protected function doRollbackToSavepoint( $identifier, $fname ) {
+               $this->query( 'ROLLBACK TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
+       }
+
        /**
         * Begin a transaction, committing any previously open transaction
         * @param string $fname