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.
 
 
 
 
 
 

402 lines
14 KiB

// Copyright (c) 2004, 2020 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.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using MySql.Data.Common;
using MySql.Data.Types;
using Sog.Properties;
namespace MySql.Data.MySqlClient
{
/// <summary>
/// Summary description for StoredProcedure.
/// </summary>
internal class StoredProcedure : PreparableStatement
{
private string _outSelect;
// Prefix used for to generate inout or output parameters names
internal const string ParameterPrefix = "_cnet_param_";
private string resolvedCommandText;
public StoredProcedure(MySqlCommand cmd, string text)
: base(cmd, text)
{
}
private MySqlParameter GetReturnParameter()
{
return this.Parameters?.Cast<MySqlParameter>().FirstOrDefault(p => p.Direction == ParameterDirection.ReturnValue);
}
public bool ServerProvidingOutputParameters { get; private set; }
public override string ResolvedCommandText
{
get { return this.resolvedCommandText; }
}
internal string GetCacheKey(string spName)
{
string retValue = String.Empty;
StringBuilder key = new StringBuilder(spName);
key.Append("(");
string delimiter = "";
foreach (MySqlParameter p in this.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 = this.GetCacheKey(procName);
ProcedureCacheEntry entry = this.Connection.ProcedureCache.GetProcedure(this.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);
}
internal static string FixProcedureName(string spName, string db)
{
string spNameWithoutQuotes = spName.Replace("`", string.Empty);
if (!string.IsNullOrEmpty(db))
{
if (spNameWithoutQuotes.Contains(db + "."))
{
spNameWithoutQuotes = spNameWithoutQuotes.Remove(0, db.Length + 1);
}
}
else
{
string[] parts = spNameWithoutQuotes.Split('.');
if (parts.Length == 2)
{
db = parts[0];
spNameWithoutQuotes = parts[1];
}
}
return string.IsNullOrEmpty(db)
? spName
: string.Format("`{0}`.`{1}`", db, spNameWithoutQuotes);
}
private MySqlParameter GetAndFixParameter(string spName, MySqlSchemaRow param, bool realAsFloat, MySqlParameter returnParameter)
{
string mode = (string)param["PARAMETER_MODE"];
string pName = (string)param["PARAMETER_NAME"];
string datatype = (string)param["DATA_TYPE"];
bool unsigned = GetFlags(param["DTD_IDENTIFIER"].ToString()).IndexOf("UNSIGNED") != -1;
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 = this.command.Parameters.GetParameterFlexible(pName, true);
if (!p.TypeHasBeenSet)
{
p.MySqlDbType = MetaData.NameToType(datatype, unsigned, realAsFloat, this.Connection);
}
return p;
}
private MySqlParameterCollection CheckParameters(string spName)
{
MySqlParameterCollection newParms = new MySqlParameterCollection(this.command);
MySqlParameter returnParameter = this.GetReturnParameter();
ProcedureCacheEntry entry = this.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(this.GetAndFixParameter(spName, param, realAsFloat, returnParameter));
}
return newParms;
}
public override void Resolve(bool preparing)
{
// check to see if we are already resolved
if (this.ResolvedCommandText != null)
{
return;
}
this.ServerProvidingOutputParameters = this.Driver.SupportsOutputParameters && preparing;
// first retrieve the procedure definition from our
// procedure cache
string spName = this.commandText;
spName = FixProcedureName(spName, this.Connection.Database);
MySqlParameter returnParameter = this.GetReturnParameter();
MySqlParameterCollection parms = this.command.Connection.Settings.CheckParameters ?
this.CheckParameters(spName) : this.Parameters;
string setSql = this.SetUserVariables(parms, preparing);
string callSql = this.CreateCallStatement(spName, returnParameter, parms);
string outSql = this.CreateOutputSelect(parms, preparing);
this.resolvedCommandText = String.Format("{0}{1}{2}", setSql, callSql, outSql);
}
private string SetUserVariables(MySqlParameterCollection parms, bool preparing)
{
StringBuilder setSql = new StringBuilder();
if (this.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 (this.command.Connection.Settings.AllowBatch && !preparing)
{
setSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, sql);
delimiter = "; ";
}
else
{
MySqlCommand cmd = new MySqlCommand(sql, this.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 || this.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) &&
this.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 (this.command.Connection.Settings.AllowBatch && !preparing)
{
return String.Format(";SELECT {0}", outSql.ToString());
}
this._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
this.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 = this.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 = this.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(this._outSelect))
{
return;
}
if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0)
{
return;
}
MySqlCommand cmd = new MySqlCommand(this._outSelect, this.command.Connection);
using (MySqlDataReader rdr = cmd.ExecuteReader(reader.CommandBehavior))
{
this.ProcessOutputParameters(rdr);
}
}
}
}