// Copyright (c) 2004-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. // // 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.Collections; using System.Text; using System.Collections.Generic; using MySql.Data.MySqlClient.Properties; using System.Data; namespace MySql.Data.MySqlClient { /// /// Summary description for PreparedStatement. /// internal class PreparableStatement : Statement { private int executionCount; private int statementId; BitArray nullMap; List parametersToSend = new List(); 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 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]; int index = Parameters.IndexOf(parameterName); if (index == -1) throw new InvalidOperationException( String.Format(Resources.ParameterNotFoundDuringPrepare, parameterName)); MySqlParameter p = Parameters[index]; 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; } /// /// Prepares CommandText for use with the Prepare method /// /// Command text stripped of all paramter names /// /// 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 /// private List PrepareCommandText(out string stripped_sql) { StringBuilder newSQL = new StringBuilder(); List parameterMap = new List(); int startPos = 0; string sql = ResolvedCommandText; MySqlTokenizer tokenizer = new MySqlTokenizer(sql); string parameter = tokenizer.NextParameter(); while (parameter != null) { newSQL.Append(sql.Substring(startPos, tokenizer.StartIndex - startPos)); newSQL.Append("?"); parameterMap.Add(parameter); startPos = tokenizer.StopIndex; parameter = tokenizer.NextParameter(); } newSQL.Append(sql.Substring(startPos)); stripped_sql = newSQL.ToString(); return parameterMap; } public virtual void CloseStatement() { if (!IsPrepared) return; Driver.CloseStatement(statementId); statementId = 0; } } }