You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
11 KiB
285 lines
11 KiB
// Copyright (c) 2004, 2021, Oracle and/or its affiliates.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License, version 2.0, as
|
|
// published by the Free Software Foundation.
|
|
//
|
|
// This program is also distributed with certain software (including
|
|
// but not limited to OpenSSL) that is licensed under separate terms,
|
|
// as designated in a particular file or component or in included license
|
|
// documentation. The authors of MySQL hereby grant you an
|
|
// additional permission to link the program and your derivative works
|
|
// with the separately licensed software that they have included with
|
|
// MySQL.
|
|
//
|
|
// Without limiting anything contained in the foregoing, this file,
|
|
// which is part of MySQL Connector/NET, is also subject to the
|
|
// Universal FOSS Exception, version 1.0, a copy of which can be found at
|
|
// http://oss.oracle.com/licenses/universal-foss-exception.
|
|
//
|
|
// 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, version 2.0, 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 System.Data;
|
|
using MySql.Data.Common;
|
|
using Sog.Properties;
|
|
|
|
namespace MySql.Data.MySqlClient
|
|
{
|
|
/// <summary>
|
|
/// Summary description for PreparedStatement.
|
|
/// </summary>
|
|
internal class PreparableStatement : Statement
|
|
{
|
|
BitArray _nullMap;
|
|
readonly 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; set; }
|
|
|
|
public bool IsPrepared => this.StatementId > 0;
|
|
|
|
public int StatementId { get; private set; }
|
|
|
|
#endregion
|
|
|
|
public virtual void Prepare()
|
|
{
|
|
// strip out names from parameter markers
|
|
string text;
|
|
List<string> parameterNames = this.PrepareCommandText(out text);
|
|
|
|
// ask our connection to send the prepare command
|
|
MySqlField[] paramList = null;
|
|
this.StatementId = this.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 < parameterNames.Count; i++)
|
|
{
|
|
string parameterName = (string)parameterNames[i];
|
|
MySqlParameter p = this.Parameters.GetParameterFlexible(parameterName, false);
|
|
if (p == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
String.Format(Resources.ParameterNotFoundDuringPrepare, parameterName));
|
|
}
|
|
|
|
p.Encoding = paramList[i].Encoding;
|
|
this._parametersToSend.Add(p);
|
|
}
|
|
|
|
if (this.Attributes.Count > 0 && !this.Driver.SupportsQueryAttributes)
|
|
{
|
|
MySqlTrace.LogWarning(this.Connection.ServerThread, string.Format(Resources.QueryAttributesNotSupported, this.Driver.Version));
|
|
}
|
|
|
|
this._packet = new MySqlPacket(this.Driver.Encoding);
|
|
|
|
// write out some values that do not change run to run
|
|
this._packet.WriteByte(0);
|
|
this._packet.WriteInteger(this.StatementId, 4);
|
|
|
|
// flags; if server supports query attributes, then set PARAMETER_COUNT_AVAILABLE (0x08) in the flags block
|
|
ClientFlags flags;
|
|
flags = this.Driver.SupportsQueryAttributes && this.Driver.Version.isAtLeast(8, 0, 26) ? ClientFlags.PARAMETER_COUNT_AVAILABLE : 0;
|
|
this._packet.WriteInteger((int)flags, 1);
|
|
this._packet.WriteInteger(1, 4); // iteration count; 1 for 4.1
|
|
int num_params = paramList != null ? paramList.Length : 0;
|
|
|
|
// we don't send QA with PS when MySQL Server is not at least 8.0.26
|
|
if (!this.Driver.Version.isAtLeast(8, 0, 26) && this.Attributes.Count > 0)
|
|
{
|
|
MySqlTrace.LogWarning(this.Connection.ServerThread, Resources.QueryAttributesNotSupportedByCnet);
|
|
this.Attributes.Clear();
|
|
}
|
|
|
|
if (num_params > 0 ||
|
|
(this.Driver.SupportsQueryAttributes && (flags & ClientFlags.PARAMETER_COUNT_AVAILABLE) != 0)) // if num_params > 0
|
|
{
|
|
int paramCount = num_params;
|
|
|
|
if (this.Driver.SupportsQueryAttributes) // if CLIENT_QUERY_ATTRIBUTES is on
|
|
{
|
|
paramCount = num_params + this.Attributes.Count;
|
|
this._packet.WriteLength(paramCount);
|
|
}
|
|
|
|
// now prepare our null map
|
|
this._nullMap = new BitArray(paramCount);
|
|
int numNullBytes = (this._nullMap.Length + 7) / 8;
|
|
this._nullMapPosition = this._packet.Position;
|
|
this._packet.Position += numNullBytes; // leave room for our null map
|
|
this._packet.WriteByte(1); // new_params_bind_flag
|
|
|
|
// write out the parameter types and names
|
|
foreach (MySqlParameter p in this._parametersToSend)
|
|
{
|
|
// parameter type
|
|
this._packet.WriteInteger(p.GetPSType(), 2);
|
|
|
|
// parameter name
|
|
if (this.Driver.SupportsQueryAttributes) // if CLIENT_QUERY_ATTRIBUTES is on
|
|
{
|
|
this._packet.WriteLenString(p.BaseName);
|
|
}
|
|
}
|
|
|
|
// write out the attributes types and names
|
|
foreach (MySqlAttribute a in this.Attributes)
|
|
{
|
|
// attribute type
|
|
this._packet.WriteInteger(a.GetPSType(), 2);
|
|
|
|
// attribute name
|
|
if (this.Driver.SupportsQueryAttributes) // if CLIENT_QUERY_ATTRIBUTES is on
|
|
{
|
|
this._packet.WriteLenString(a.AttributeName);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._dataPosition = this._packet.Position;
|
|
}
|
|
|
|
public override void Execute()
|
|
{
|
|
// if we are not prepared, then call down to our base
|
|
if (!this.IsPrepared)
|
|
{
|
|
base.Execute();
|
|
return;
|
|
}
|
|
|
|
// now write out all non-null values
|
|
this._packet.Position = this._dataPosition;
|
|
|
|
// set value for each parameter
|
|
for (int i = 0; i < this._parametersToSend.Count; i++)
|
|
{
|
|
MySqlParameter p = this._parametersToSend[i];
|
|
this._nullMap[i] = (p.Value == DBNull.Value || p.Value == null) ||
|
|
p.Direction == ParameterDirection.Output;
|
|
if (this._nullMap[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this._packet.Encoding = p.Encoding;
|
|
p.Serialize(this._packet, true, this.Connection.Settings);
|
|
}
|
|
|
|
// // set value for each attribute
|
|
for (int i = 0; i < this.Attributes.Count; i++)
|
|
{
|
|
MySqlAttribute attr = this.Attributes[i];
|
|
this._nullMap[i] = (attr.Value == DBNull.Value || attr.Value == null);
|
|
if (this._nullMap[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
attr.Serialize(this._packet, true, this.Connection.Settings);
|
|
}
|
|
|
|
if (this._nullMap != null)
|
|
{
|
|
byte[] tempByteArray = new byte[(this._nullMap.Length + 7) >> 3];
|
|
this._nullMap.CopyTo(tempByteArray, 0);
|
|
|
|
Array.Copy(tempByteArray, 0, this._packet.Buffer, this._nullMapPosition, tempByteArray.Length);
|
|
}
|
|
|
|
this.ExecutionCount++;
|
|
|
|
this.Driver.ExecuteStatement(this._packet);
|
|
}
|
|
|
|
public override bool ExecuteNext()
|
|
{
|
|
if (!this.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 = this.ResolvedCommandText;
|
|
MySqlTokenizer tokenizer = new MySqlTokenizer(sql);
|
|
string parameter = tokenizer.NextParameter();
|
|
int paramIndex = 0;
|
|
while (parameter != null)
|
|
{
|
|
if (parameter.IndexOf(StoredProcedure.ParameterPrefix) == -1)
|
|
{
|
|
newSQL.Append(sql.Substring(startPos, tokenizer.StartIndex - startPos));
|
|
newSQL.Append("?");
|
|
if (parameter.Length == 1 && tokenizer.IsParameterMarker(parameter.ToCharArray()[0]))
|
|
{
|
|
parameterMap.Add(this.Parameters[paramIndex].ParameterName);
|
|
}
|
|
else
|
|
{
|
|
parameterMap.Add(parameter);
|
|
}
|
|
|
|
startPos = tokenizer.StopIndex;
|
|
}
|
|
|
|
parameter = tokenizer.NextParameter();
|
|
paramIndex++;
|
|
}
|
|
|
|
newSQL.Append(sql.Substring(startPos));
|
|
stripped_sql = newSQL.ToString();
|
|
return parameterMap;
|
|
}
|
|
|
|
public virtual void CloseStatement()
|
|
{
|
|
if (!this.IsPrepared)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.Driver.CloseStatement(this.StatementId);
|
|
this.StatementId = 0;
|
|
}
|
|
}
|
|
}
|
|
|