// 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;
}
}
}