Click here to Skip to main content
15,886,873 members
Articles / Database Development / MySQL

MySqlUtil - TableAdapters for MySql

Rate me:
Please Sign up or sign in to vote.
4.59/5 (7 votes)
3 Aug 20063 min read 69.3K   2.1K   47  
A program which generates Typed DataSets and TableAdapters for MySQL databases
// Copyright (C) 2004-2005 MySQL AB
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as published by
// the Free Software Foundation
//
// There are special exceptions to the terms and conditions of the GPL 
// as it is applied to this software. View the full text of the 
// exception in file EXCEPTIONS in the directory of this software 
// distribution.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 

using System;
using System.Data;
using System.ComponentModel;
using System.IO;
using System.Collections;
using System.Text;
using MySql.Data.Common;

namespace MySql.Data.MySqlClient
{
	/// <include file='docs/mysqlcommand.xml' path='docs/ClassSummary/*'/>
#if WINDOWS
	[System.Drawing.ToolboxBitmap( typeof(MySqlCommand), "MySqlClient.resources.command.bmp")]
#endif
	[System.ComponentModel.DesignerCategory("Code")]
	public sealed class MySqlCommand : Component, IDbCommand, ICloneable
	{
		MySqlConnection				connection;
		MySqlTransaction			curTransaction;
		string						cmdText;
		CommandType					cmdType;
		long						updateCount;
		UpdateRowSource				updatedRowSource;
		MySqlParameterCollection	parameters;
		private ArrayList			sqlBuffers;
		private PreparedStatement	preparedStatement;
		private ArrayList			parameterMap;
		private StoredProcedure		storedProcedure;
		private CommandResult		lastResult;

		/// <include file='docs/mysqlcommand.xml' path='docs/ctor1/*'/>
		public MySqlCommand()
		{
			cmdType = CommandType.Text;
			parameterMap = new ArrayList();
			parameters = new MySqlParameterCollection();
			updatedRowSource = UpdateRowSource.Both;
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ctor2/*'/>
		public MySqlCommand(string cmdText) : this()
		{
			CommandText = cmdText;
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ctor3/*'/>
		public MySqlCommand(string cmdText, MySqlConnection connection) : this(cmdText)
		{
			Connection = connection;
			if (connection != null)
				parameters.ParameterMarker = connection.ParameterMarker;
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ctor4/*'/>
		public MySqlCommand(string cmdText, MySqlConnection connection, 
			MySqlTransaction transaction) : this(cmdText, connection)
		{
			curTransaction = transaction;
		} 

		#region Properties

		/// <include file='docs/mysqlcommand.xml' path='docs/CommandText/*'/>
		[Category("Data")]
		[Description("Command text to execute")]
#if WINDOWS
		[Editor("MySql.Data.Common.Design.SqlCommandTextEditor,MySqlClient.Design", typeof(System.Drawing.Design.UITypeEditor))]
#endif
		public string CommandText
		{
			get { return cmdText; }
			set { cmdText = value;  this.preparedStatement=null; }
		}

		internal int UpdateCount 
		{
			get { return (int)updateCount; }
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/CommandTimeout/*'/>
		[Category("Misc")]
		[Description("Time to wait for command to execute")]
		public int CommandTimeout
		{
			// TODO: support this
			get  { return 0; }
			set  { if (value != 0) throw new NotSupportedException(); }
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/CommandType/*'/>
		[Category("Data")]
		public CommandType CommandType
		{
			get { return cmdType; }
			set { cmdType = value; }
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/IsPrepared/*'/>
		[Browsable(false)]
		public bool IsPrepared 
		{
			get { return preparedStatement != null; }
		}

		IDbConnection IDbCommand.Connection 
		{
			get { return connection; }
			set { Connection = (MySqlConnection)value; }
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/Connection/*'/>
		[Category("Behavior")]
		[Description("Connection used by the command")]
		public MySqlConnection Connection
		{
			get { return connection;  }
			set
			{
				/*
				* The connection is associated with the transaction
				* so set the transaction object to return a null reference if the connection 
				* is reset.
				*/
				if (connection != value)
					this.Transaction = null;

				connection = (MySqlConnection)value;
				if (connection != null)
					parameters.ParameterMarker = connection.ParameterMarker;
			}
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/Parameters/*'/>
		[Category("Data")]
		[Description("The parameters collection")]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		public MySqlParameterCollection Parameters
		{
			get  { return parameters; }
		}

		IDataParameterCollection IDbCommand.Parameters
		{
			get  { return parameters; }
		}

		IDbTransaction IDbCommand.Transaction 
		{
			get { return Transaction; }
			set { Transaction = (MySqlTransaction)value; }
		}


		/// <include file='docs/mysqlcommand.xml' path='docs/Transaction/*'/>
		[Browsable(false)]
		public MySqlTransaction Transaction
		{
			get { return curTransaction; }
			set { curTransaction = (MySqlTransaction)value; }
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/UpdatedRowSource/*'/>
		[Category("Behavior")]
		public UpdateRowSource UpdatedRowSource
		{
			get 
			{ 
				return updatedRowSource;  
			}
			set 
			{ 
				updatedRowSource = value; 
			}
		}
		#endregion

		#region Methods

		/// <summary>
		/// Attempts to cancel the execution of a MySqlCommand.  This operation is not supported.
		/// </summary>
		/// <remarks>
		/// Cancelling an executing command is currently not supported on any version of MySQL.
		/// </remarks>
		/// <exception cref="NotSupportedException">This operation is not supported.</exception>
		public void Cancel()
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Creates a new instance of a <see cref="MySqlParameter"/> object.
		/// </summary>
		/// <remarks>
		/// This method is a strongly-typed version of <see cref="IDbCommand.CreateParameter"/>.
		/// </remarks>
		/// <returns>A <see cref="MySqlParameter"/> object.</returns>
		/// 
		public MySqlParameter CreateParameter()
		{
			return new MySqlParameter();
		}

		IDbDataParameter IDbCommand.CreateParameter()
		{
			return this.CreateParameter();
		}

		/// <summary>
		/// Executes all remaining command buffers
		/// </summary>
		internal void Consume()
		{
			CommandResult result = GetNextResultSet(null);
			while (result != null)
			{
				result.Consume();
				result = GetNextResultSet(null);
			}

			// if we were executing a stored procedure and we are out of sql buffers to execute, 
			// then we need to perform some additional work to get our inout and out parameters
			if (storedProcedure != null && sqlBuffers.Count == 0)
				storedProcedure.UpdateParameters(Parameters);
		}

		/// <summary>
		/// Executes command buffers until we hit the next resultset
		/// </summary>
		/// <returns>CommandResult containing the next resultset when hit
		/// or null if no more resultsets were found</returns>
		internal CommandResult GetNextResultSet(MySqlDataReader reader)
		{
			// if  we are supposed to return only a single resultset and our reader
			// is calling us back again, then return null
			if (reader != null && 
				(reader.Behavior & CommandBehavior.SingleResult) != 0 &&
				lastResult != null) return null;

			// if the last result we returned has more results
			if (lastResult != null && lastResult.ReadNextResult(false) )
				return lastResult;
			lastResult = null;

			CommandResult result = null;

			// if we haven't prepared a statement and don't have any sql buffers
			// to execute, we are done
			if (preparedStatement == null)
			{
				if (sqlBuffers == null || sqlBuffers.Count == 0)
					return null;
			}
			else if (preparedStatement.ExecutionCount > 0)
				return null;


			// if we have a prepared statement, we execute it instead
			if (preparedStatement != null)
			{
				result = preparedStatement.Execute( parameters );

				if (! result.IsResultSet) 
				{
					if (updateCount == -1) updateCount = 0;
					updateCount += (long)result.AffectedRows;
				}
			}
			else while (sqlBuffers.Count > 0)
			{
				MemoryStream sqlStream = (MemoryStream)sqlBuffers[0];

				using (sqlStream) 
				{
					result = connection.driver.SendQuery( sqlStream.GetBuffer(), (int)sqlStream.Length, false );
					sqlBuffers.RemoveAt( 0 );
				}
	
				if (result.AffectedRows != -1)
				{
					if (updateCount == -1) updateCount = 0;
					updateCount += (long)result.AffectedRows;
				}

				// if this is a resultset, then we break out of our execution loop
				if (result.IsResultSet)
					break;
			}

			if (result.IsResultSet) 
			{
				lastResult = result;
				return result;
			}
			return null;
		}

		/// <summary>
		/// Check the connection to make sure
		///		- it is open
		///		- it is not currently being used by a reader
		///		- and we have the right version of MySQL for the requested command type
		/// </summary>
		private void CheckState()
		{
			// There must be a valid and open connection.
			if (connection == null || connection.State != ConnectionState.Open)
				throw new InvalidOperationException(Resources.GetString("ConnectionMustBeOpen"));

			// Data readers have to be closed first
			if (connection.Reader != null)
				throw new MySqlException(Resources.GetString("DataReaderOpen"));

			if (CommandType == CommandType.StoredProcedure && ! connection.driver.Version.isAtLeast(5,0,0))
				throw new MySqlException(Resources.GetString("SPNotSupported"));
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ExecuteNonQuery/*'/>
		public int ExecuteNonQuery()
		{
			CheckState();

			updateCount = 0;

			if (preparedStatement == null)
				sqlBuffers = PrepareSqlBuffers(CommandText);
			else
				preparedStatement.ExecutionCount = 0;

			try 
			{
				Consume();
			}
			catch (MySqlException ex) 
			{
				if (ex.IsFatal) connection.Terminate();
				throw;
			}

			return (int)updateCount;
		}

		IDataReader IDbCommand.ExecuteReader ()
		{
			return ExecuteReader ();
		}

		IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
		{
			return ExecuteReader (behavior);
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ExecuteReader/*'/>
		public MySqlDataReader ExecuteReader()
		{
			return ExecuteReader(CommandBehavior.Default);
		}


		/// <include file='docs/mysqlcommand.xml' path='docs/ExecuteReader1/*'/>
		public MySqlDataReader ExecuteReader(CommandBehavior behavior)
		{
			CheckState();

			string sql = TrimSemicolons(cmdText);

			if (0 != (behavior & CommandBehavior.SchemaOnly))
			{
				sql = "SET SQL_SELECT_LIMIT=0;" + sql + ";SET sql_select_limit=-1";
			}

			if (0 != (behavior & CommandBehavior.SingleRow))
			{
				sql = "SET SQL_SELECT_LIMIT=1;" + sql + ";SET sql_select_limit=-1";
			}

			updateCount = -1;
			MySqlDataReader reader = new MySqlDataReader(this, behavior);

			// if we don't have a prepared statement, then prepare our sql for execution
			if (preparedStatement == null)
				sqlBuffers = PrepareSqlBuffers(sql);
			else
				preparedStatement.ExecutionCount = 0;

			reader.NextResult();
			connection.Reader = reader;
			return reader;
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/ExecuteScalar/*'/>
		public object ExecuteScalar()
		{
			// ExecuteReader will check out state

			updateCount = -1;

			object val = null;
			MySqlDataReader reader = ExecuteReader();
			if (reader.Read())
				val = reader.GetValue(0);
			reader.Close();

			return val;
		}

		/// <include file='docs/mysqlcommand.xml' path='docs/Prepare/*'/>
		public void Prepare()
		{
			if (connection == null)
				throw new InvalidOperationException(Resources.GetString("ConnectionNotSet"));
			if (connection.State != ConnectionState.Open)
				throw new InvalidOperationException(Resources.GetString("ConnectionNotOpen"));
			if (! connection.driver.Version.isAtLeast( 4,1,0)) 
				return;

			// strip out names from parameter markers
			string psSQL = CommandText;

			if (CommandType == CommandType.StoredProcedure)
			{
				if (storedProcedure == null)
					storedProcedure = new StoredProcedure(connection);
				psSQL = storedProcedure.Prepare(this);
			}
			psSQL = PrepareCommandText(psSQL);

			// ask our connection to send the prepare command
			preparedStatement = connection.driver.Prepare(psSQL, (string[])parameterMap.ToArray(typeof(string)));
		}
		#endregion


		#region Private Methods

		private string TrimSemicolons(string sql)
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder(sql);
			int start = 0;
			while (sb[start] == ';')
				start++;

			int end = sb.Length-1;
			while (sb[end] == ';')
				end--;
			return sb.ToString(start, end-start+1);
		}

		/// <summary>
		/// Serializes the given parameter to the given memory stream
		/// </summary>
		/// <param name="writer">PacketWriter to stream parameter data to</param>
		/// <param name="parmName">Name of the parameter to serialize</param>
		/// <remarks>
		/// <para>This method is called by PrepareSqlBuffers to convert the given
		/// parameter to bytes and write those bytes to the given memory stream.
		/// </para>
		/// </remarks>
		/// <returns>True if the parameter was successfully serialized, false otherwise.</returns>
		private bool SerializeParameter( PacketWriter writer, string parmName )
		{
			int index = parameters.IndexOf(parmName);
			if (index == -1)
			{
				// if we are using old syntax, we can't throw exceptions for parameters
				// not defined.
				if (connection.Settings.UseOldSyntax) return false;
				throw new MySqlException("Parameter '" + parmName + "' must be defined");
			}
			MySqlParameter parameter = parameters[index];
			parameter.Serialize( writer, false );
			return true;
		}


		/// <summary>
		/// Prepares the necessary byte buffers from the given CommandText
		/// </summary>
		/// <returns>Array of byte buffers, one for each SQL command</returns>
		/// <remarks>
		/// Converts the CommandText into an array of tokens 
		/// using TokenizeSql and then into one or more byte buffers that can be
		/// sent to the server.  If the server supports batching (and we  have enabled it),
		/// then all commands result in a single byte array, otherwise a single buffer
		/// is created for each SQL command (as separated by ';').
		/// The SQL text is converted to bytes using the active encoding for the server.
		/// </remarks>
		private ArrayList PrepareSqlBuffers(string sql)
		{
			ArrayList buffers = new ArrayList();
			PacketWriter writer = new PacketWriter();
			writer.Encoding = connection.Encoding;
			writer.Version = connection.driver.Version;

			// if we are executing as a stored procedure, then we need to add the call
			// keyword.
			if (CommandType == CommandType.StoredProcedure)
			{
				if (storedProcedure == null)
					storedProcedure = new StoredProcedure(connection);
				sql = storedProcedure.Prepare( this );
			}

			// tokenize the SQL
			sql = sql.TrimStart(';').TrimEnd(';');
			ArrayList tokens = TokenizeSql( sql );

			foreach (string token in tokens)
			{
				if (token.Trim().Length == 0) continue;
				if (token == ";" && ! connection.driver.SupportsBatch)
				{
					MemoryStream ms = (MemoryStream)writer.Stream;
					if (ms.Length > 0)
						buffers.Add( ms );

					writer = new PacketWriter();
					writer.Encoding = connection.Encoding;
					writer.Version = connection.driver.Version;
					continue;
				}
				else if (token[0] == parameters.ParameterMarker) 
				{
					if (SerializeParameter( writer, token )) continue;
				}

				// our fall through case is to write the token to the byte stream
				writer.WriteStringNoNull( token );
			}

			// capture any buffer that is left over
			MemoryStream mStream = (MemoryStream)writer.Stream;
			if (mStream.Length > 0)
				buffers.Add( mStream );

			return buffers;
		}

		/// <summary>
		/// Prepares CommandText for use with the Prepare method
		/// </summary>
		/// <returns>Command text stripped of all paramter names</returns>
		/// <remarks>
		/// Takes the output of TokenizeSql and creates a single string of SQL
		/// that only contains '?' markers for each parameter.  It also creates
		/// the parameterMap array list that includes all the paramter names in the
		/// order they appeared in the SQL
		/// </remarks>
		private string PrepareCommandText(string text)
		{
			StringBuilder	newSQL = new StringBuilder();

			// tokenize the sql first
			ArrayList tokens = TokenizeSql(text);
			parameterMap.Clear();

			foreach (string token in tokens)
			{
				if ( token[0] != parameters.ParameterMarker )
					newSQL.Append( token );
				else
				{
					parameterMap.Add( token );
					newSQL.Append( parameters.ParameterMarker );
				}
			}

			return newSQL.ToString();
		}

		/// <summary>
		/// Breaks the given SQL up into 'tokens' that are easier to output
		/// into another form (bytes, preparedText, etc).
		/// </summary>
		/// <param name="sql">SQL to be tokenized</param>
		/// <returns>Array of tokens</returns>
		/// <remarks>The SQL is tokenized at parameter markers ('?') and at 
		/// (';') sql end markers if the server doesn't support batching.
		/// </remarks>
		private ArrayList TokenizeSql( string sql )
		{
			char			delim = Char.MinValue;
			StringBuilder	sqlPart = new StringBuilder();
			bool			escaped = false;
			ArrayList		tokens = new ArrayList();

			for (int i=0; i < sql.Length; i++)
			{
				char c = sql[i];
				if (escaped)
					escaped = !escaped;
				else if (c == delim) 
					delim = Char.MinValue;
				else if (c == ';' && !escaped && delim == Char.MinValue && !connection.driver.SupportsBatch)
				{
					tokens.Add( sqlPart.ToString() );
					tokens.Add( ";" );
					sqlPart.Remove( 0, sqlPart.Length ); 
					continue;
				}
				else if ((c == '\'' || c == '\"') & ! escaped & delim == Char.MinValue)
					delim=c;
				else if (c == '\\') 
					escaped = ! escaped;
				else if (c == parameters.ParameterMarker && delim == Char.MinValue && ! escaped) 
				{
					tokens.Add( sqlPart.ToString() );
					sqlPart.Remove( 0, sqlPart.Length ); 
				}
				else if (sqlPart.Length > 0 && sqlPart[0] == parameters.ParameterMarker && 
					! Char.IsLetterOrDigit(c) && c != '_' && c != '.' && c != '$' 
					&& c != '@')
				{
					tokens.Add( sqlPart.ToString() );
					sqlPart.Remove( 0, sqlPart.Length ); 
				}

				sqlPart.Append(c);
			}
			tokens.Add( sqlPart.ToString() );
			return tokens;
		}
		#endregion

		#region ICloneable
		/// <summary>
		/// Creates a clone of this MySqlCommand object.  CommandText, Connection, and Transaction properties
		/// are included as well as the entire parameter list.
		/// </summary>
		/// <returns>The cloned MySqlCommand object</returns>
		object ICloneable.Clone() 
		{
			MySqlCommand clone = new MySqlCommand(cmdText, connection, curTransaction);
			foreach (MySqlParameter p in parameters) 
			{
				clone.Parameters.Add((p as ICloneable).Clone());
			}
			return clone;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



Comments and Discussions