Bug #98947 mysql_fetch_row_nonblocking() incorrectly sets errno to CR_COMMANDS on last call
Submitted: 13 Mar 2020 18:46 Modified: 25 Mar 2020 16:35
Reporter: Jay Edgar Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: C API (client library) Severity:S3 (Non-critical)
Version:8.0.19 OS:Any
Assigned to: CPU Architecture:Any
Tags: async_client

[13 Mar 2020 18:46] Jay Edgar
Description:
The mysql_fetch_row_nonblocking() function is intended to be called multiple times.  On the last time (once all rows have been fetched) it is supposed to set the row pointer to null.  Unfortunately it also sets the errno for the connection to CR_COMMANDS_OUT_OF_SYNC.

Note that this occurs when using mysql_use_result but not when using mysql_store_result[_nonblocking].

How to repeat:
To repeat this I made some changes to the mysql_client_test.c, replacing the call to mysql_store_result_nonblocking with a call to mysql_use_result and then adding an additional call to mysql_fetch_row_nonblocking() that I expect to set `row` to null.  If I comment out the check for errno == 0 the test succeeds.  Once I add the suggested fix the test also passes.

```
diff --git a/testclients/mysql_client_test.cc b/testclients/mysql_client_test.cc
index 2c45d41da89..0653c691f8e 100644
--- a/testclients/mysql_client_test.cc
+++ b/testclients/mysql_client_test.cc
@@ -20225,18 +20225,7 @@ static void test_wl11381() {
   } else {
     fprintf(stdout, "\n mysql_real_query_nonblocking() passed");
   }
-  status = mysql_store_result_nonblocking(mysql_local, &result);
-  /* do some other task */
-  perform_arithmatic();
-  while (status == NET_ASYNC_NOT_READY) {
-    status = mysql_store_result_nonblocking(mysql_local, &result);
-  }
-  if (!result) {
-    fprintf(stdout, "\n mysql_store_result_nonblocking() fetched 0 records");
-    exit(1);
-  } else {
-    fprintf(stdout, "\n mysql_store_result_nonblocking() passed");
-  }
+  result = mysql_use_result(mysql_local);

   row = mysql_fetch_row(result);
   DIE_UNLESS(strcmp(row[0], "10") == 0);
@@ -20263,6 +20252,14 @@ static void test_wl11381() {
     fprintf(stdout, "\n mysql_fetch_row_nonblocking() passed");
   }

+  while ((status = mysql_fetch_row_nonblocking(result, &row)) == NET_ASYNC_NOT_READY) {
+    /* do some other task */
+    perform_arithmatic();
+  }
+
+  DIE_UNLESS(row == nullptr);
+  DIE_UNLESS(mysql_errno(mysql_local) == 0);
+
   while ((status = mysql_free_result_nonblocking(result)) != NET_ASYNC_COMPLETE)
     ;
   fprintf(stdout, "\n mysql_free_result_nonblocking() passed");
```

Suggested fix:
diff --git a/sql-common/client.cc b/sql-common/client.cc
index fd36e9950cf..b5575af69ba 100644
--- a/sql-common/client.cc
+++ b/sql-common/client.cc
@@ -7538,11 +7538,13 @@ net_async_status STDCALL mysql_fetch_row_nonblocking(MYSQL_RES *res,
           goto end;
         }
       }
+      else {
+        set_mysql_error(mysql,
+                        res->unbuffered_fetch_cancelled ? CR_FETCH_CANCELED
+                                                        : CR_COMMANDS_OUT_OF_SYNC,
+                        unknown_sqlstate);
+      }

-      set_mysql_error(mysql,
-                      res->unbuffered_fetch_cancelled ? CR_FETCH_CANCELED
-                                                      : CR_COMMANDS_OUT_OF_SYNC,
-                      unknown_sqlstate);
       DBUG_PRINT("info", ("end of data"));
       res->eof = true;
       mysql->status = MYSQL_STATUS_READY;
[18 Mar 2020 12:37] MySQL Verification Team
Hello Jay Edgar,

Thank you for the report and feedback.

Thanks,
Umesh
[25 Mar 2020 16:35] Paul DuBois
Posted by developer:
 
Fixed in 8.0.21.

The last call to the mysql_fetch_row_nonblocking() C API function to
return the null row was setting an error when it should not have.