context: // Copyright (c) 2004-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
  //
  // MySQL Connector/NET is licensed under the terms of the GPLv2
  // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most 
  // MySQL Connectors. There are special exceptions to the terms and 
  // conditions of the GPLv2 as it is applied to this software, see the 
  // FLOSS License Exception
  // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  //
  // This program is free software; you can redistribute it and/or modify 
  // it under the terms of the GNU General Public License as published 
  // by the Free Software Foundation; version 2 of the License.
  //
  // 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., 
  // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  
  using System;
  using System.Collections;
  using System.Text;
  using System.Collections.Generic;
  using MySql.Data.MySqlClient.Properties;
  using System.Data;
  
  namespace MySql.Data.MySqlClient
  {
      /// <summary>
      /// Summary description for PreparedStatement.
      /// </summary>
      internal class PreparableStatement : Statement
      {
          private int executionCount;
          private int statementId;
          BitArray nullMap;
          List<MySqlParameter> parametersToSend = new List<MySqlParameter>();
          MySqlPacket packet;
          int dataPosition;
          int nullMapPosition;
  
          public PreparableStatement(MySqlCommand command, string text)
              : base(command, text)
          {
          }
  
          #region Properties
  
          public int ExecutionCount
          {
              get { return executionCount; }
              set { executionCount = value; }
          }
  
          public bool IsPrepared
          {
              get { return statementId > 0; }
          }
  
          public int StatementId
          {
              get { return statementId; }
          }
  
          #endregion
  
          public virtual void Prepare()
          {
              // strip out names from parameter markers
              string text;
              List<string> parameter_names = PrepareCommandText(out text);
  
              // ask our connection to send the prepare command
              MySqlField[] paramList = null;
              statementId = Driver.PrepareStatement(text, ref paramList);
  
              // now we need to assign our field names since we stripped them out
              // for the prepare
              for (int i = 0; i < parameter_names.Count; i++)
              {
                  //paramList[i].ColumnName = (string) parameter_names[i];
                  string parameterName = (string)parameter_names[i];
                  MySqlParameter p = Parameters.GetParameterFlexible(parameterName, false);
                  if (p == null)
                      throw new InvalidOperationException(
                          String.Format(Resources.ParameterNotFoundDuringPrepare, parameterName));
                  p.Encoding = paramList[i].Encoding;
                  parametersToSend.Add(p);
              }
  
              // now prepare our null map
              int numNullBytes = 0;
              if (paramList != null && paramList.Length > 0)
              {
                  nullMap = new BitArray(paramList.Length);
                  numNullBytes = (nullMap.Count + 7) / 8;
              }
  
              packet = new MySqlPacket(Driver.Encoding);
              
              // write out some values that do not change run to run
              packet.WriteByte(0);
              packet.WriteInteger(statementId, 4);
              packet.WriteByte((byte)0); // flags; always 0 for 4.1
              packet.WriteInteger(1, 4); // interation count; 1 for 4.1
              nullMapPosition = packet.Position;
              packet.Position += numNullBytes;  // leave room for our null map
              packet.WriteByte(1); // rebound flag
              // write out the parameter types
              foreach (MySqlParameter p in parametersToSend)
                  packet.WriteInteger(p.GetPSType(), 2);
              dataPosition = packet.Position;
          }
  
          public override void Execute()
          {
              // if we are not prepared, then call down to our base
              if (!IsPrepared)
              {
                  base.Execute();
                  return;
              }
  
              //TODO: support long data here
              // create our null bitmap
  
              // we check this because Mono doesn't ignore the case where nullMapBytes
              // is zero length.
  //            if (nullMapBytes.Length > 0)
    //          {
      //            byte[] bits = packet.Buffer;
        //          nullMap.CopyTo(bits, 
          //        nullMap.CopyTo(nullMapBytes, 0);
  
              // start constructing our packet
  //            if (Parameters.Count > 0)
    //              nullMap.CopyTo(packet.Buffer, nullMapPosition);
              //if (parameters != null && parameters.Count > 0)
              //else
              //	packet.WriteByte( 0 );
              //TODO:  only send rebound if parms change
  
              // now write out all non-null values
              packet.Position = dataPosition;
              for (int i = 0; i < parametersToSend.Count; i++)
              {
                  MySqlParameter p = parametersToSend[i];
                  nullMap[i] = (p.Value == DBNull.Value || p.Value == null) ||
                      p.Direction == ParameterDirection.Output;
                  if (nullMap[i]) continue;
                  packet.Encoding = p.Encoding;
                  p.Serialize(packet, true, Connection.Settings);
              }
              if (nullMap != null)
                  nullMap.CopyTo(packet.Buffer, nullMapPosition);
  
              executionCount++;
  
              Driver.ExecuteStatement(packet);
          }
  
          public override bool ExecuteNext()
          {
              if (!IsPrepared)
                  return base.ExecuteNext();
              return false;
          }
  
          /// <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 List<string> PrepareCommandText(out string stripped_sql)
          {
              StringBuilder newSQL = new StringBuilder();
              List<string> parameterMap = new List<string>();
  
              int startPos = 0;
              string sql = ResolvedCommandText;
              MySqlTokenizer tokenizer = new MySqlTokenizer(sql);
              string parameter = tokenizer.NextParameter();
              while (parameter != null)
              {
                  if (parameter.IndexOf(StoredProcedure.ParameterPrefix) == -1)
                  {
                      newSQL.Append(sql.Substring(startPos, tokenizer.StartIndex - startPos));
                      newSQL.Append("?");
                      parameterMap.Add(parameter);
                      startPos = tokenizer.StopIndex;
                  }
                  parameter = tokenizer.NextParameter();
              }
              newSQL.Append(sql.Substring(startPos));
  
gt:             stripped_sql = newSQL.ToString();