Bug #20707 NULLs get passed as parameters to stored procedure.
Submitted: 26 Jun 2006 21:18 Modified: 29 Jun 2006 7:51
Reporter: Tristan Aubrey-Jones Email Updates:
Status: Duplicate Impact on me:
None 
Category:Connector / NET Severity:S2 (Serious)
Version:1.0.7 OS:Windows (Windows XP)
Assigned to: CPU Architecture:Any

[26 Jun 2006 21:18] Tristan Aubrey-Jones
Description:
Ok this has got me completely stumpted. Ive debugged the libaries source and I just cannot work out whats going on!? Basically ive got a simple stored procedure that inserts values passed to it as parameters into a table. I call it and all seems fine, it creates the record only when I take a look using the mysql console - the record is full of NULLs! I then modified the table to NOT NULL the columns - and now I get an exception saying that the UserName column needs a non null value; despite the fact I pass in the parameters. The odd thing is this doesnt occur when I invoke the stored procedure directly using the mysql console. The parameter values seem to disappear deep in the depths of the driver.

How to repeat:
SQL TO RUN BELOW:

DELIMITER |

CREATE TABLE IF NOT EXISTS UserLogin
(
  ID INT UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
  UserName VARCHAR(30) NOT NULL,
  Password_Hash BINARY(20) NOT NULL,
  Password_Salt BINARY(10) NOT NULL,
  IsLockedOut BIT NOT NULL,
  IsOwner BIT NOT NULL,
  IsBetaTester BIT NOT NULL,
  IsAdministrator BIT NOT NULL,
  Status TINYINT UNSIGNED NOT NULL
)|

CREATE PROCEDURE Sp_UserLogin_Create
(
  IN iUserName VARCHAR(30),
  IN iPassword_Hash BINARY(20),
  IN iPassword_Salt BINARY(10),
  IN iIsLockedOut BIT,
  IN iIsOwner BIT,
  IN iIsBetaTester BIT,
  IN iIsAdministrator BIT,
  IN iStatus TINYINT UNSIGNED,
  OUT oID INT UNSIGNED
)
BEGIN
  INSERT INTO UserLogin
  (
    UserName,
    Password_Hash,
    Password_Salt,
    IsLockedOut,
    IsOwner,
    IsBetaTester,
    IsAdministrator,
    Status
  )
  VALUES
  (
    iUserName,
    iPassword_Hash,
    iPassword_Salt,
    iIsLockedOut,
    iIsOwner,
    iIsBetaTester,
    iIsAdministrator,
    iStatus
  );
  SET oID = LAST_INSERT_ID();
END|

C# CODE BELOW:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

using MySql.Data.MySqlClient;

namespace Tester
{
    class Program
    {
        static void Main(string[] args)
        {
            // create connection
            MySqlConnection connection = new MySqlConnection("server=localhost;user id=root; password=password; database=mydb; pooling=true");

            // my nice secure password
            const string pwd = "password";

            // create random salt and password hash
            byte[] salt = new byte[10];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            SHA1Managed sha1 = new SHA1Managed();
            rng.GetBytes(salt);
            byte[] password = ASCIIEncoding.ASCII.GetBytes(pwd);
            byte[] input = new byte[salt.Length + password.Length];
            password.CopyTo(input, 0);
            salt.CopyTo(input, password.Length);
            byte[] hash = sha1.ComputeHash(input);
            uint id = 0;

            // setup parameters
            MySqlCommand command = new MySqlCommand("Sp_UserLogin_Create", connection);
            command.CommandType = System.Data.CommandType.StoredProcedure;
            command.Parameters.Add(new MySqlParameter("iUsername", "user"));
            command.Parameters.Add(new MySqlParameter("iPassword_Hash", hash));
            command.Parameters.Add(new MySqlParameter("iPassword_Salt", salt));
            command.Parameters.Add(new MySqlParameter("iIsLockedOut", false));
            command.Parameters.Add(new MySqlParameter("iIsOwner", true));
            command.Parameters.Add(new MySqlParameter("iIsBetaTester", true));
            command.Parameters.Add(new MySqlParameter("iIsAdministrator", true));
            command.Parameters.Add(new MySqlParameter("iStatus", 0));
            MySqlParameter pout = new MySqlParameter("oID", id);
            pout.Direction = System.Data.ParameterDirection.Output;
            command.Parameters.Add(pout);

            // open connection and execute procedure
            connection.Open();
            command.Prepare();
            command.ExecuteNonQuery();
            connection.Close();
        }
    }
}
[26 Jun 2006 21:19] Tristan Aubrey-Jones
To invoke the stored procedure directly (and see it working) use the following from the mysql console:

CALL Sp_UserLogin_Create("tristan", "passwordhash12345678", "salt012345", 0, 1, 1, 1, 0, @oID);
[28 Jun 2006 10:39] Tristan Aubrey-Jones
Ive isolated the problem though I still cant solve it. If the stored procedure and related code is altered to exclude the iStatus tinyint unsigned parameter, everything works. Unfortuantley I need this parameter - I have looked into the driver's database type selection and discovered that if you declare the parameter as UByte it tried to serialize the parameter as a MySqlBinaryValue - this being Bug number 1. 

            p = new MySqlParameter("iStatus", MySqlDbType.UByte);
            p.Value = 1;
            p.Direction = System.Data.ParameterDirection.Input;
            command.Parameters.Add(p);

This can be simply solved by altering the internal method: MySqlValue.GetMySqlValue in the drivers source to include cases for the unsigned integer types.

However once this was solved and the parameter was being serialized as an unsigned byte value I get a database exception "#HY000Incorrect arguments to mysql_stmt_execute" - Bug number 2 - so it looks like MySql doesnt expect unsigned tinyints to be serialized as unsigned bytes - odd but ok. Now I try passing the parameter as a UInt32 - this is inserted into the table:

mysql> select ID, Username, Password_Hash, IsBetaTester, Status from userlogin;
+----+----------+----------------------+--------------+--------+
| ID | Username | Password_Hash        | IsBetaTester | Status |
+----+----------+----------------------+--------------+--------+
|  1 | 4        | 0                    |              |      0 |
+----+----------+----------------------+--------------+--------+
1 row in set (0.00 sec)

Huh!? Now if I try it as Int32 i get:

mysql> select ID, Username, Password_Hash, IsBetaTester, Status from userlogin;
+----+----------+----------------------+--------------+--------+
| ID | Username | Password_Hash        | IsBetaTester | Status |
+----+----------+----------------------+--------------+--------+
|  1 | 4        | 0                    |              |      0 |
|  2 | 0        | 0                    |              |      0 |
+----+----------+----------------------+--------------+--------+
2 rows in set (0.00 sec)

:S

And the same pattern for UInt64 and Int64 respectively. What is going on? What datatype does the MySql client protocol want UNSIGNED TINYINTS to be transmitted? Any help?
[29 Jun 2006 7:02] Tonci Grgin
Hi again Tristan. Great test case!
Can you tell me what happens if you remove
// open connection and execute procedure
            connection.Open();
//this line command.Prepare();
            command.ExecuteNonQuery();
            connection.Close(); ?
[29 Jun 2006 7:47] Tristan Aubrey-Jones
Thanks! It works - just removing the prepare line seems to work! I can send in the Status parameter as a Byte or UByte and the line appears as it should. So shouldn't I be using the prepare statement? Whats it doing to muck up the final execution? 

P.S. Id still like to see bug 1 (the issue of UByte and UInt32 etc not appearing in various switch statments) addressed in the next release.
[29 Jun 2006 7:51] Tonci Grgin
Trista, glad it works!
This seems to be the duplicate of http://bugs.mysql.com/bug.php?id=18570. Patch is in review.