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.

328 lines
11 KiB

#if MYSQL_6_9
// 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.Data;
using System.Globalization;
using System.Text;
using Externals.MySql.Data.Types;
using Externals.MySql.Data.MySqlClient.Properties;
namespace Externals.MySql.Data.MySqlClient
{
/// <summary>
/// Summary description for StoredProcedure.
/// </summary>
internal class StoredProcedure : PreparableStatement
{
private string outSelect;
private string resolvedCommandText;
private bool serverProvidingOutputParameters;
// Prefix used for to generate inout or output parameters names
internal const string ParameterPrefix = "_cnet_param_";
public StoredProcedure(MySqlCommand cmd, string text)
: base(cmd, text)
{
}
private MySqlParameter GetReturnParameter()
{
if (Parameters != null)
foreach (MySqlParameter p in Parameters)
if (p.Direction == ParameterDirection.ReturnValue)
return p;
return null;
}
public bool ServerProvidingOutputParameters
{
get { return serverProvidingOutputParameters; }
}
public override string ResolvedCommandText
{
get { return resolvedCommandText; }
}
internal string GetCacheKey(string spName)
{
string retValue = String.Empty;
StringBuilder key = new StringBuilder(spName);
key.Append("(");
string delimiter = "";
foreach (MySqlParameter p in command.Parameters)
{
if (p.Direction == ParameterDirection.ReturnValue)
retValue = "?=";
else
{
key.AppendFormat(CultureInfo.InvariantCulture, "{0}?", delimiter);
delimiter = ",";
}
}
key.Append(")");
return retValue + key.ToString();
}
private ProcedureCacheEntry GetParameters(string procName)
{
string procCacheKey = GetCacheKey(procName);
ProcedureCacheEntry entry = Connection.ProcedureCache.GetProcedure(Connection, procName, procCacheKey);
return entry;
}
public static string GetFlags(string dtd)
{
int x = dtd.Length - 1;
while (x > 0 && (Char.IsLetterOrDigit(dtd[x]) || dtd[x] == ' '))
x--;
string dtdSubstring = dtd.Substring(x);
return StringUtility.ToUpperInvariant(dtdSubstring);
}
private string FixProcedureName(string name)
{
string[] parts = name.Split('.');
for (int i = 0; i < parts.Length; i++)
if (!parts[i].StartsWith("`", StringComparison.Ordinal))
parts[i] = String.Format("`{0}`", parts[i]);
if (parts.Length == 1) return parts[0];
return String.Format("{0}.{1}", parts[0], parts[1]);
}
private MySqlParameter GetAndFixParameter(string spName, MySqlSchemaRow param, bool realAsFloat, MySqlParameter returnParameter)
{
string mode = (string)param["PARAMETER_MODE"];
string pName = (string)param["PARAMETER_NAME"];
if (param["ORDINAL_POSITION"].Equals(0))
{
if (returnParameter == null)
throw new InvalidOperationException(
String.Format(Resources.RoutineRequiresReturnParameter, spName));
pName = returnParameter.ParameterName;
}
// make sure the parameters given to us have an appropriate type set if it's not already
MySqlParameter p = command.Parameters.GetParameterFlexible(pName, true);
if (!p.TypeHasBeenSet)
{
string datatype = (string)param["DATA_TYPE"];
bool unsigned = GetFlags(param["DTD_IDENTIFIER"].ToString()).IndexOf("UNSIGNED") != -1;
p.MySqlDbType = MetaData.NameToType(datatype, unsigned, realAsFloat, Connection);
}
return p;
}
private MySqlParameterCollection CheckParameters(string spName)
{
MySqlParameterCollection newParms = new MySqlParameterCollection(command);
MySqlParameter returnParameter = GetReturnParameter();
ProcedureCacheEntry entry = GetParameters(spName);
if (entry.procedure == null || entry.procedure.Rows.Count == 0)
throw new InvalidOperationException(String.Format(Resources.RoutineNotFound, spName));
bool realAsFloat = entry.procedure.Rows[0]["SQL_MODE"].ToString().IndexOf("REAL_AS_FLOAT") != -1;
foreach (MySqlSchemaRow param in entry.parameters.Rows)
newParms.Add(GetAndFixParameter(spName, param, realAsFloat, returnParameter));
return newParms;
}
public override void Resolve(bool preparing)
{
// check to see if we are already resolved
if (resolvedCommandText != null) return;
serverProvidingOutputParameters = Driver.SupportsOutputParameters && preparing;
// first retrieve the procedure definition from our
// procedure cache
string spName = commandText;
if (spName.IndexOf(".") == -1 && !String.IsNullOrEmpty(Connection.Database))
spName = Connection.Database + "." + spName;
spName = FixProcedureName(spName);
MySqlParameter returnParameter = GetReturnParameter();
MySqlParameterCollection parms = command.Connection.Settings.CheckParameters ?
CheckParameters(spName) : Parameters;
string setSql = SetUserVariables(parms, preparing);
string callSql = CreateCallStatement(spName, returnParameter, parms);
string outSql = CreateOutputSelect(parms, preparing);
resolvedCommandText = String.Format("{0}{1}{2}", setSql, callSql, outSql);
}
private string SetUserVariables(MySqlParameterCollection parms, bool preparing)
{
StringBuilder setSql = new StringBuilder();
if (serverProvidingOutputParameters) return setSql.ToString();
string delimiter = String.Empty;
foreach (MySqlParameter p in parms)
{
if (p.Direction != ParameterDirection.InputOutput) continue;
string pName = "@" + p.BaseName;
string uName = "@" + ParameterPrefix + p.BaseName;
string sql = String.Format("SET {0}={1}", uName, pName);
if (command.Connection.Settings.AllowBatch && !preparing)
{
setSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, sql);
delimiter = "; ";
}
else
{
MySqlCommand cmd = new MySqlCommand(sql, command.Connection);
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
}
if (setSql.Length > 0)
setSql.Append("; ");
return setSql.ToString();
}
private string CreateCallStatement(string spName, MySqlParameter returnParameter, MySqlParameterCollection parms)
{
StringBuilder callSql = new StringBuilder();
string delimiter = String.Empty;
foreach (MySqlParameter p in parms)
{
if (p.Direction == ParameterDirection.ReturnValue) continue;
string pName = "@" + p.BaseName;
string uName = "@" + ParameterPrefix + p.BaseName;
bool useRealVar = p.Direction == ParameterDirection.Input || serverProvidingOutputParameters;
callSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, useRealVar ? pName : uName);
delimiter = ", ";
}
if (returnParameter == null)
return String.Format("CALL {0}({1})", spName, callSql.ToString());
else
return String.Format("SET @{0}{1}={2}({3})", ParameterPrefix, returnParameter.BaseName, spName, callSql.ToString());
}
private string CreateOutputSelect(MySqlParameterCollection parms, bool preparing)
{
StringBuilder outSql = new StringBuilder();
string delimiter = String.Empty;
foreach (MySqlParameter p in parms)
{
if (p.Direction == ParameterDirection.Input) continue;
if ((p.Direction == ParameterDirection.InputOutput ||
p.Direction == ParameterDirection.Output) &&
serverProvidingOutputParameters) continue;
string pName = "@" + p.BaseName;
string uName = "@" + ParameterPrefix + p.BaseName;
outSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, uName);
delimiter = ", ";
}
if (outSql.Length == 0) return String.Empty;
if (command.Connection.Settings.AllowBatch && !preparing)
return String.Format(";SELECT {0}", outSql.ToString());
outSelect = String.Format("SELECT {0}", outSql.ToString());
return String.Empty;
}
internal void ProcessOutputParameters(MySqlDataReader reader)
{
// We apparently need to always adjust our output types since the server
// provided data types are not always right
AdjustOutputTypes(reader);
if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0)
return;
// now read the output parameters data row
reader.Read();
string prefix = "@" + StoredProcedure.ParameterPrefix;
for (int i = 0; i < reader.FieldCount; i++)
{
string fieldName = reader.GetName(i);
if (fieldName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
fieldName = fieldName.Remove(0, prefix.Length);
MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
parameter.Value = reader.GetValue(i);
}
}
private void AdjustOutputTypes(MySqlDataReader reader)
{
// since MySQL likes to return user variables as strings
// we reset the types of the readers internal value objects
// this will allow those value objects to parse the string based
// return values
for (int i = 0; i < reader.FieldCount; i++)
{
string fieldName = reader.GetName(i);
if (fieldName.IndexOf(StoredProcedure.ParameterPrefix) != -1)
fieldName = fieldName.Remove(0, StoredProcedure.ParameterPrefix.Length + 1);
MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
IMySqlValue v = MySqlField.GetIMySqlValue(parameter.MySqlDbType);
if (v is MySqlBit)
{
MySqlBit bit = (MySqlBit)v;
bit.ReadAsString = true;
reader.ResultSet.SetValueObject(i, bit);
}
else
reader.ResultSet.SetValueObject(i, v);
}
}
public override void Close(MySqlDataReader reader)
{
base.Close(reader);
if (String.IsNullOrEmpty(outSelect)) return;
if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0) return;
MySqlCommand cmd = new MySqlCommand(outSelect, command.Connection);
using (MySqlDataReader rdr = cmd.ExecuteReader(reader.CommandBehavior))
{
ProcessOutputParameters(rdr);
}
}
}
}
#endif