Bug #7198 Prepared command not woking properly.
Submitted: 11 Dec 2004 18:50 Modified: 16 Dec 2004 18:41
Reporter: Lon Palmer Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / NET Severity:S1 (Critical)
Version:1.0.2.18525 OS:Windows (Windows XP / Server 2003)
Assigned to: Reggie Burnett CPU Architecture:Any

[11 Dec 2004 18:50] Lon Palmer
Description:
A parameterized update query that worked fine with ByteFX 0.76 is no longer working with Connector.NET 1.0.2.18525.

The reported error is that a null value is not allowed in one of the fields.  When I debug the project, the field in question is not null, it holds a proper value (string).

My guess is that previous parameters in the parameterized query is being escaped incorrectly, causing the compiled query string to be invalid.

I've included the class that throws the exception and the exception text.  Notice that in one of the parameterized queries I've used a RegEx to excape the user input.  This was necessary in ByteFx version of the driver but not anymore.  I've tried both using and not using it with the same effect.

MySql.Data.MySqlClient.MySqlException: #23000Column 'web_active' cannot be null at MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery() at BHMailAgent.Data.UserIMailSaveCmd.Save() in c:\inetpub\wwwroot\bhmailagent\data\userimailsavecmd.cs:line 56 at BHMailAgent.index.btnSubmit_Click(Object sender, EventArgs e) in c:\inetpub\wwwroot\bhmailagent\index.aspx.cs:line 265
at MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery() at BHMailAgent.Data.UserIMailSaveCmd.Save() in c:\inetpub\wwwroot\bhmailagent\data\userimailsavecmd.cs:line 56 at BHMailAgent.index.btnSubmit_Click(Object sender, EventArgs e) in c:\inetpub\wwwroot\bhmailagent\index.aspx.cs:line 265

using System;
using System.Data;
using System.Text.RegularExpressions;

namespace BHMailAgent.Data
{
	/// <summary>
	/// Summary description for UserIMailDataCmd.
	/// 
	/// Use Like:
	/// 
	/// UserIMailSaveCmd saveCmd = new UserIMailSaveCmd( con, user );
	/// saveCmd.Save();
	/// 
	/// </summary>
	public class UserIMailSaveCmd
	{
		public UserIMailSaveCmd( IDbConnection con, UserIMail user )
		{
			this.con = con;
			this.user = user;

			if ( user.DbId == -1 )									// -1 is new user.
			{
				isNew = true;
			}
			else
			{
				isNew = false;
			}

			save = con.CreateCommand();
			save.CommandType = CommandType.Text;

			if ( isNew )
			{	
				save.CommandText = saveNewSql;
				PrepareAsNew();
			}
			else
			{
				save.CommandText = saveExisting;
				PrepareAsExisting();
			}
		}

		public bool Save()
		{
			bool ret = false;
			bool wasNew = user.DbId == -1;			// -1 means the user is new.
			PopUser();

			if ( !wasNew || !UserExists() )   // If we're updating a user or the user does not already exist we can procede.
			{
				save.ExecuteNonQuery();

				if ( wasNew )
				{
					user.DbId = GetDbId( user );
				}
				ret = true;
			}
			return ret;
		}

		private bool UserExists() 
		{
			bool ret = false;
			IDbCommand ueCmd = con.CreateCommand();
			ueCmd.CommandText = userExistsSql;
			ueCmd.CommandType = CommandType.Text;
 
			ueCmd.Parameters.Add( snUserId );
			ueCmd.Parameters.Add( snAgentId );
			ueCmd.Parameters.Add( snMailAddr );

			ueCmd.Prepare();

			IDataReader reader = ueCmd.ExecuteReader();
			if ( reader.Read() )
				ret = reader.GetInt32(0) > 0;
			reader.Close();

			return ret;
		}

		public int GetDbId( UserIMail user )
		{
			int ret = 0;

			try
			{
				IDbCommand dbIdCmd = con.CreateCommand();
				dbIdCmd.CommandText = getDbIdSql;
				dbIdCmd.CommandType = CommandType.Text;

				IDbDataParameter snUserId = dbIdCmd.CreateParameter();
				snUserId.DbType = DbType.AnsiString;
				snUserId.ParameterName = "@UserID";
				snUserId.Value = user.UserId;
				dbIdCmd.Parameters.Add( snUserId );

				dbIdCmd.Prepare();

				IDataReader reader = dbIdCmd.ExecuteReader();
				/**
				if ( reader.Read() )
					ret = reader.GetInt32(0);
				**/
				while( reader.Read() ) ret++;
				reader.Close();
			}
			catch ( Exception e )
			{
				/// TODO Log this error.
			}
			return ret;
		}

		public string EscapeMySqlUserInput( Match sqlEscape )
		{
			string ret;
			
			if ( @"\\".Equals( sqlEscape.ToString() ) )
			{
				ret = @"\\" + sqlEscape.ToString();
			}
			else 
			{
				ret = @"\" + sqlEscape.ToString();
			}

			return ret;
		}

		private void PopUser()
		{	
			snUserId.Value = user.UserId;
			snPassword.Value = user.Password;
			snFullName.Value = user.FullName;
			snFirstName.Value = user.FirstName;
			snLastName.Value = user.LastName;
			snUserDir.Value = Regex.Replace( user.UserDir, MY_SQL_ESCAPES, new MatchEvaluator( this.EscapeMySqlUserInput ) ); // user.UserDir.Replace( @"\", @"\\" );
			snMailAddr.Value = user.MailAddress;
			snAgentId.Value = user.AgentId;
			snOfficeId.Value = user.OfficeId;
			snFirmId.Value = user.FirmId;
			snDesignation.Value = user.Designation;
			snSpeciality.Value = user.Speciality;
			snLink.Value = user.Link;
			snPermissions.Value = user.Permissions;
			snOldLink.Value = user.OldLink;
			if ( user.WebActive )
			{
				snWebActive.Value = "ACTIVE";
			}
			else
			{
				snWebActive.Value = "INACTIVE";
			}
			if ( !isNew )
			{
				snId.Value = user.DbId;
			}
			snDesignation.Value = user.Designation;

			

			save.Prepare();													// Pre-prepare the query for user.
			
			
			
		}

		private void PrepareAsExisting()
		{
			PrepareAsNew();
	
			snId = save.CreateParameter();
			snId.ParameterName = "@Id";
			snId.DbType = DbType.Int32;
			save.Parameters.Add( snId );
		}

		private void PrepareAsNew()
		{
			snUserId = save.CreateParameter();
			snUserId.ParameterName = "@UserID";
			snUserId.DbType = DbType.AnsiString;
			save.Parameters.Add( snUserId );

			snPassword = save.CreateParameter();
			snPassword.ParameterName = "@Password";
			snPassword.DbType = DbType.AnsiString;
			save.Parameters.Add( snPassword );

			snFullName = save.CreateParameter();
			snFullName.ParameterName = "@FullName";
			snFullName.DbType = DbType.AnsiString;
			save.Parameters.Add( snFullName );

			snFirstName = save.CreateParameter();
			snFirstName.ParameterName = "@FirstName";
			snFirstName.DbType = DbType.AnsiString;
			save.Parameters.Add( snFirstName );

			snLastName = save.CreateParameter();
			snLastName.ParameterName = "@LastName";
			snLastName.DbType = DbType.AnsiString;
			save.Parameters.Add( snLastName );

			snUserDir = save.CreateParameter();
			snUserDir.ParameterName = "@UserDir";
			snUserDir.DbType = DbType.AnsiString;
			save.Parameters.Add( snUserDir );

			snMailAddr = save.CreateParameter();
			snMailAddr.ParameterName = "@MailAddr";
			snMailAddr.DbType = DbType.AnsiString;
			save.Parameters.Add( snMailAddr );

			snAgentId = save.CreateParameter();
			snAgentId.ParameterName = "@AgentId";
			snAgentId.DbType = DbType.Int32;
			save.Parameters.Add( snAgentId );

			snOfficeId = save.CreateParameter();
			snOfficeId.ParameterName = "@OfficeId";
			snOfficeId.DbType = DbType.Int32;
			save.Parameters.Add( snOfficeId );

			snFirmId = save.CreateParameter();
			snFirmId.ParameterName = "@FirmId";
			snFirmId.DbType = DbType.Int32;
			save.Parameters.Add( snFirmId );

			snDesignation = save.CreateParameter();
			snDesignation.ParameterName = "@Designation";
			snFirmId.DbType = DbType.AnsiString;
			save.Parameters.Add( snDesignation );

			snSpeciality = save.CreateParameter();
			snSpeciality.ParameterName = "@Speciality";
			snSpeciality.DbType = DbType.AnsiString;
			save.Parameters.Add( snSpeciality );

			snLink = save.CreateParameter();
			snLink.ParameterName = "@Link";
			snLink.DbType = DbType.AnsiString;
			save.Parameters.Add( snLink );

			snPermissions = save.CreateParameter();
			snPermissions.ParameterName = "@Permissions";
			snPermissions.DbType = DbType.AnsiString;
			save.Parameters.Add( snPermissions );

			snWebActive = save.CreateParameter();
			snWebActive.ParameterName = "@WebActive";
			snWebActive.DbType = DbType.AnsiString;
			save.Parameters.Add( snWebActive );

			snOldLink = save.CreateParameter();
			snOldLink.ParameterName = "@OldLink";
			snOldLink.DbType = DbType.AnsiString;
			save.Parameters.Add( snOldLink );
		}

		private IDbCommand save;

		

		private static string saveNewSql = @"INSERT INTO `beverly_hanks_com` (`USERID`,`PASSWORD`,`FULLNAME`,`FIRSTNAME`,`LASTNAME`,`USERDIR`,`MAILADDR`,`MAXSIZE`,`MAXMSGS`,`FLAGS`,`TYPE`,`NUMTRIES`,`ATTEMPTTIME`,`NUMTIMESSUSP`,`LASTLOGIN`,`SPNDUSRACCT`,`ICALENBL`,`SPELLENBL`,`NUMSGSTNS`,`IWDEPTH`,`IWOPTIONS`,`SPELLCHKG`,`PRVWSIZE`,`DEFVIEW`,`TIMEZONE`,`STRTDAY`,`DEFCALNM`,`ENTRYGRNL`,`STARTTIME`,`ENDTIME`,`MAXEVNTDISP`,`MAXTSKDISP`,`MAXNOTEDISP`,`MAXDLYRCURCNT`,`MAXWKLYRCURCNT`,`MAXMONRCURCNT`,`MAXYRLYRCURCNT`,`DISPCMPLTDTSKS`,`HOURSCLOCK`,`MAXSRCHRESPPG`,`MAXEVENTTITLELEN`,`MAXTASKTITLELEN`,`CONFIRMONDELETE`,`NUMNOTIFY`,`LASTNOTIFY`,`agentid`,`officeid`,`firmid`,`designation`,`specialty`,`link`,`permissions`,`web_active`,`old_link`) VALUES (@UserId,@PassWord,@FullName,@FirstName,@LastName,@UserDir,@MailAddr,0,0,128,65547,0,0,0,0,0,-1,-1,4,-1,0,0,0,'MONTHLY','S GMT Standard Time',0,'Personal',60,9,18,0,0,0,365,100,36,5,0,12,10,25,10,0,0,0,@AgentId,@OfficeId,@FirmId,@Designation,@Specialty,@Link,@Permissions,@WebActive,@OldLink)";
		private static string saveExisting = @"UPDATE beverly_hanks_com SET USERID = @UserId, PASSWORD = @Password, FULLNAME = @FullName, FIRSTNAME = @FirstName, LASTNAME = @LastName, USERDIR = @UserDir, MAILADDR = @MailAddr, agentid = @AgentId, officeid = @OfficeId, firmid  = @FirmId, designation = @Designation, specialty = @speciality, link = @Link, permissions = @Permissions, web_active = @WebActive, old_link = @OldLink WHERE id = @Id";

		private static string getDbIdSql = @"SELECT id FROM beverly_hanks_com WHERE USERID = @UserId";

		private static string userExistsSql = @"SELECT id FROM beverly_hanks_com WHERE USERID = @UserId OR MAILADDR = @MailAddr OR agentid = @AgentId";

		IDbDataParameter snUserId = null;
		IDbDataParameter snPassword = null;
		IDbDataParameter snFullName = null;
		IDbDataParameter snFirstName = null;
		IDbDataParameter snLastName = null;
		IDbDataParameter snUserDir = null;
		IDbDataParameter snMailAddr = null;
		IDbDataParameter snAgentId = null;
		IDbDataParameter snOfficeId = null;
		IDbDataParameter snFirmId = null;
		IDbDataParameter snDesignation = null;
		IDbDataParameter snSpeciality = null;
		IDbDataParameter snLink = null;
		IDbDataParameter snPermissions = null;
		IDbDataParameter snWebActive = null;
		IDbDataParameter snOldLink = null;
		IDbDataParameter snId = null;
		
		public const string MY_SQL_ESCAPES = "('|\"|\b|\n|\r|\t|^\\\\\\\\|\\\\|%|_)+"; // The ^// rule is to accomodate windows unc path names.
		
		private IDbConnection con = null;
		private UserIMail user = null;
		private bool isNew = false;
	}
}

How to repeat:
Simply use this class to reproduce the error.

Suggested fix:
Give the developer some way to see the end result of a parameterized query once it's been "Prepare()"ed.  That way if it is an escape problem the developer can work around it.
[16 Dec 2004 18:41] Reggie Burnett
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.mysql.com/documentation/ and the instructions on
how to report a bug at http://bugs.mysql.com/how-to-report.php

Additional info:

You are using old parameter sytnax with the "@" sign. Did you include "old syntax=yes" on your connection string?  It's best to move to the new syntax using "?" but if you can't, then add "old syntax=yes" to your connection string.