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.
289 lines
12 KiB
289 lines
12 KiB
// 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.Data;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using MySql.Data.Types;
|
|
using MySql.Data.MySqlClient.Properties;
|
|
|
|
namespace MySql.Data.MySqlClient
|
|
{
|
|
/// <summary>
|
|
/// Summary description for StoredProcedure.
|
|
/// </summary>
|
|
internal class StoredProcedure : PreparableStatement
|
|
{
|
|
private string outSelect;
|
|
private DataTable parametersTable;
|
|
private string resolvedCommandText;
|
|
|
|
// 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 string GetReturnParameter()
|
|
{
|
|
if (Parameters != null)
|
|
foreach (MySqlParameter p in Parameters)
|
|
if (p.Direction == ParameterDirection.ReturnValue)
|
|
return p.ParameterName.Substring(1);
|
|
return null;
|
|
}
|
|
|
|
public override string ResolvedCommandText
|
|
{
|
|
get { return resolvedCommandText; }
|
|
}
|
|
|
|
private DataSet GetParameters(string procName)
|
|
{
|
|
// if we can use mysql.proc, then do so
|
|
//if (Connection.Settings.UseProcedureBodies)
|
|
DataSet ds = Connection.ProcedureCache.GetProcedure(Connection, procName);
|
|
|
|
if(ds.Tables.Count == 2)
|
|
{
|
|
// if we got our parameters and our user says it is ok to use proc bodies
|
|
// then just return them
|
|
if (Connection.Settings.UseProcedureBodies) return ds;
|
|
|
|
// we got the parameters, but ignore them.
|
|
if(ds.Tables.Contains("Procedure Parameters"))
|
|
ds.Tables.Remove("Procedure Parameters");
|
|
}
|
|
|
|
// we were not able to retrieve parameter data so we have to make do by
|
|
// adding the parameters from the command object to our table
|
|
// we use an internal method to create our procedure parameters table.
|
|
ISSchemaProvider sp = new ISSchemaProvider(Connection);
|
|
DataTable pTable = sp.CreateParametersTable();
|
|
ds.Tables.Add(pTable);
|
|
|
|
// now we run through the parameters that were set and fill in the parameters table
|
|
// the best we can
|
|
int pos = 1;
|
|
foreach (MySqlParameter p in command.Parameters)
|
|
{
|
|
// in this mode, all parameters must have their type set
|
|
if (!p.TypeHasBeenSet)
|
|
throw new InvalidOperationException(Resources.NoBodiesAndTypeNotSet);
|
|
|
|
DataRow row = pTable.NewRow();
|
|
row["PARAMETER_NAME"] = p.ParameterName;
|
|
row["PARAMETER_MODE"] = "IN";
|
|
if (p.Direction == ParameterDirection.InputOutput)
|
|
row["PARAMETER_MODE"] = "INOUT";
|
|
else if (p.Direction == ParameterDirection.Output)
|
|
row["PARAMETER_MODE"] = "OUT";
|
|
else if (p.Direction == ParameterDirection.ReturnValue)
|
|
{
|
|
row["PARAMETER_MODE"] = "OUT";
|
|
row["ORDINAL_POSITION"] = 0;
|
|
}
|
|
else
|
|
row["ORDINAL_POSITION"] = pos++;
|
|
pTable.Rows.Add(row);
|
|
}
|
|
return ds;
|
|
}
|
|
|
|
public static string GetFlags(string dtd)
|
|
{
|
|
int x = dtd.Length - 1;
|
|
while (x > 0 && (Char.IsLetterOrDigit(dtd[x]) || dtd[x] == ' '))
|
|
x--;
|
|
return dtd.Substring(x).ToUpper(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private string FixProcedureName(string name)
|
|
{
|
|
string[] parts = name.Split('.');
|
|
for (int i = 0; i < parts.Length; i++)
|
|
if (!parts[i].StartsWith("`"))
|
|
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(DataRow param, bool realAsFloat, string returnParameter)
|
|
{
|
|
string mode = (string)param["PARAMETER_MODE"];
|
|
string pName = (string)param["PARAMETER_NAME"];
|
|
|
|
if (param["ORDINAL_POSITION"].Equals(0))
|
|
pName = returnParameter;
|
|
|
|
if (pName == null) return null;
|
|
|
|
// 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;
|
|
}
|
|
|
|
public override void Resolve(bool preparing)
|
|
{
|
|
// check to see if we are already resolved
|
|
if (resolvedCommandText != null) return;
|
|
|
|
// 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);
|
|
|
|
DataSet ds = GetParameters(spName);
|
|
|
|
DataTable procTable = ds.Tables["procedures"];
|
|
parametersTable = ds.Tables["procedure parameters"];
|
|
|
|
if (procTable.Rows.Count == 0)
|
|
throw new InvalidOperationException(String.Format(Resources.RoutineNotFound, spName));
|
|
|
|
bool realAsFloat = procTable.Rows[0]["SQL_MODE"].ToString().IndexOf("REAL_AS_FLOAT") != -1;
|
|
StringBuilder sqlStr = new StringBuilder();
|
|
StringBuilder outSql = new StringBuilder();
|
|
string sqlDelimiter = "";
|
|
string outDelimiter = "";
|
|
|
|
string retParm = GetReturnParameter();
|
|
foreach (DataRow param in parametersTable.Rows)
|
|
{
|
|
MySqlParameter p = GetAndFixParameter(param, realAsFloat, retParm);
|
|
if (p == null) continue;
|
|
|
|
if (param["ORDINAL_POSITION"].Equals(0))
|
|
continue;
|
|
|
|
string baseName = p.ParameterName;
|
|
string pName = baseName;
|
|
if (baseName.StartsWith("@") || baseName.StartsWith("?"))
|
|
baseName = baseName.Substring(1);
|
|
else
|
|
pName = "@" + pName;
|
|
|
|
string inputVar = pName;
|
|
if (p.Direction != ParameterDirection.Input &&
|
|
!(Connection.driver.SupportsOutputParameters || preparing))
|
|
{
|
|
// set a user variable to our current value
|
|
string sql = String.Format("SET @{0}{1}={2}", ParameterPrefix, baseName, pName);
|
|
MySqlCommand cmd = new MySqlCommand(sql, Connection);
|
|
|
|
cmd.Parameters.Add(p);
|
|
cmd.ExecuteNonQuery();
|
|
|
|
inputVar = String.Format("@{0}{1}", ParameterPrefix, baseName);
|
|
|
|
outSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", outDelimiter, inputVar);
|
|
outDelimiter = ", ";
|
|
}
|
|
sqlStr.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", sqlDelimiter, inputVar);
|
|
sqlDelimiter = ", ";
|
|
}
|
|
|
|
string sqlCmd = sqlStr.ToString().TrimEnd(' ', ',');
|
|
outSelect = outSql.ToString().TrimEnd(' ', ',');
|
|
|
|
if (procTable.Rows[0]["ROUTINE_TYPE"].Equals("PROCEDURE"))
|
|
sqlCmd = String.Format("call {0} ({1})", spName, sqlCmd);
|
|
else
|
|
{
|
|
if (retParm == null)
|
|
retParm = ParameterPrefix + "dummy";
|
|
else
|
|
outSelect = String.Format("@{0}{1}", ParameterPrefix, retParm);
|
|
sqlCmd = String.Format("SET @{0}{1}={2}({3})", ParameterPrefix, retParm, spName, sqlCmd);
|
|
}
|
|
|
|
resolvedCommandText = sqlCmd;
|
|
}
|
|
|
|
private MySqlDataReader GetHackedOuputParameters()
|
|
{
|
|
if (outSelect.Length == 0) return null;
|
|
|
|
MySqlCommand cmd = new MySqlCommand("SELECT " + outSelect, Connection);
|
|
|
|
MySqlDataReader reader = cmd.ExecuteReader();
|
|
// 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
|
|
ResultSet results = reader.ResultSet;
|
|
for (int i = 0; i < reader.FieldCount; i++)
|
|
{
|
|
string fieldName = reader.GetName(i);
|
|
fieldName = fieldName.Remove(0, ParameterPrefix.Length + 1);
|
|
MySqlParameter parameter = Parameters.GetParameterFlexible(fieldName, true);
|
|
results.SetValueObject(i, MySqlField.GetIMySqlValue(parameter.MySqlDbType));
|
|
}
|
|
if (!reader.Read())
|
|
{
|
|
reader.Close();
|
|
return null;
|
|
}
|
|
return reader;
|
|
}
|
|
|
|
public override void Close(MySqlDataReader reader)
|
|
{
|
|
base.Close(reader);
|
|
|
|
ResultSet rs = reader.ResultSet;
|
|
// if our closing reader doesn't have output parameters then we may have to
|
|
// use the user variable hack
|
|
if (rs == null || !rs.IsOutputParameters)
|
|
{
|
|
MySqlDataReader rdr = GetHackedOuputParameters();
|
|
if (rdr == null) return;
|
|
reader = rdr;
|
|
}
|
|
|
|
using (reader)
|
|
{
|
|
string prefix = "@" + ParameterPrefix;
|
|
|
|
for (int i = 0; i < reader.FieldCount; i++)
|
|
{
|
|
string fieldName = reader.GetName(i);
|
|
if (fieldName.StartsWith(prefix))
|
|
fieldName = fieldName.Remove(0, prefix.Length);
|
|
MySqlParameter parameter = Parameters.GetParameterFlexible(fieldName, true);
|
|
parameter.Value = reader.GetValue(i);
|
|
}
|
|
reader.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|