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

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright (c) 2004-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Data;
  25. using System.Globalization;
  26. using System.Text;
  27. using Externals.MySql.Data.Types;
  28. using Externals.MySql.Data.MySqlClient.Properties;
  29. namespace Externals.MySql.Data.MySqlClient
  30. {
  31. /// <summary>
  32. /// Summary description for StoredProcedure.
  33. /// </summary>
  34. internal class StoredProcedure : PreparableStatement
  35. {
  36. private string outSelect;
  37. private string resolvedCommandText;
  38. private bool serverProvidingOutputParameters;
  39. // Prefix used for to generate inout or output parameters names
  40. internal const string ParameterPrefix = "_cnet_param_";
  41. public StoredProcedure(MySqlCommand cmd, string text)
  42. : base(cmd, text)
  43. {
  44. }
  45. private MySqlParameter GetReturnParameter()
  46. {
  47. if (Parameters != null)
  48. foreach (MySqlParameter p in Parameters)
  49. if (p.Direction == ParameterDirection.ReturnValue)
  50. return p;
  51. return null;
  52. }
  53. public bool ServerProvidingOutputParameters
  54. {
  55. get { return serverProvidingOutputParameters; }
  56. }
  57. public override string ResolvedCommandText
  58. {
  59. get { return resolvedCommandText; }
  60. }
  61. internal string GetCacheKey(string spName)
  62. {
  63. string retValue = String.Empty;
  64. StringBuilder key = new StringBuilder(spName);
  65. key.Append("(");
  66. string delimiter = "";
  67. foreach (MySqlParameter p in command.Parameters)
  68. {
  69. if (p.Direction == ParameterDirection.ReturnValue)
  70. retValue = "?=";
  71. else
  72. {
  73. key.AppendFormat(CultureInfo.InvariantCulture, "{0}?", delimiter);
  74. delimiter = ",";
  75. }
  76. }
  77. key.Append(")");
  78. return retValue + key.ToString();
  79. }
  80. private ProcedureCacheEntry GetParameters(string procName)
  81. {
  82. string procCacheKey = GetCacheKey(procName);
  83. ProcedureCacheEntry entry = Connection.ProcedureCache.GetProcedure(Connection, procName, procCacheKey);
  84. return entry;
  85. }
  86. public static string GetFlags(string dtd)
  87. {
  88. int x = dtd.Length - 1;
  89. while (x > 0 && (Char.IsLetterOrDigit(dtd[x]) || dtd[x] == ' '))
  90. x--;
  91. string dtdSubstring = dtd.Substring(x);
  92. return StringUtility.ToUpperInvariant(dtdSubstring);
  93. }
  94. private string FixProcedureName(string name)
  95. {
  96. string[] parts = name.Split('.');
  97. for (int i = 0; i < parts.Length; i++)
  98. if (!parts[i].StartsWith("`", StringComparison.Ordinal))
  99. parts[i] = String.Format("`{0}`", parts[i]);
  100. if (parts.Length == 1) return parts[0];
  101. return String.Format("{0}.{1}", parts[0], parts[1]);
  102. }
  103. private MySqlParameter GetAndFixParameter(string spName, MySqlSchemaRow param, bool realAsFloat, MySqlParameter returnParameter)
  104. {
  105. string mode = (string)param["PARAMETER_MODE"];
  106. string pName = (string)param["PARAMETER_NAME"];
  107. if (param["ORDINAL_POSITION"].Equals(0))
  108. {
  109. if (returnParameter == null)
  110. throw new InvalidOperationException(
  111. String.Format(Resources.RoutineRequiresReturnParameter, spName));
  112. pName = returnParameter.ParameterName;
  113. }
  114. // make sure the parameters given to us have an appropriate type set if it's not already
  115. MySqlParameter p = command.Parameters.GetParameterFlexible(pName, true);
  116. if (!p.TypeHasBeenSet)
  117. {
  118. string datatype = (string)param["DATA_TYPE"];
  119. bool unsigned = GetFlags(param["DTD_IDENTIFIER"].ToString()).IndexOf("UNSIGNED") != -1;
  120. p.MySqlDbType = MetaData.NameToType(datatype, unsigned, realAsFloat, Connection);
  121. }
  122. return p;
  123. }
  124. private MySqlParameterCollection CheckParameters(string spName)
  125. {
  126. MySqlParameterCollection newParms = new MySqlParameterCollection(command);
  127. MySqlParameter returnParameter = GetReturnParameter();
  128. ProcedureCacheEntry entry = GetParameters(spName);
  129. if (entry.procedure == null || entry.procedure.Rows.Count == 0)
  130. throw new InvalidOperationException(String.Format(Resources.RoutineNotFound, spName));
  131. bool realAsFloat = entry.procedure.Rows[0]["SQL_MODE"].ToString().IndexOf("REAL_AS_FLOAT") != -1;
  132. foreach (MySqlSchemaRow param in entry.parameters.Rows)
  133. newParms.Add(GetAndFixParameter(spName, param, realAsFloat, returnParameter));
  134. return newParms;
  135. }
  136. public override void Resolve(bool preparing)
  137. {
  138. // check to see if we are already resolved
  139. if (resolvedCommandText != null) return;
  140. serverProvidingOutputParameters = Driver.SupportsOutputParameters && preparing;
  141. // first retrieve the procedure definition from our
  142. // procedure cache
  143. string spName = commandText;
  144. if (spName.IndexOf(".") == -1 && !String.IsNullOrEmpty(Connection.Database))
  145. spName = Connection.Database + "." + spName;
  146. spName = FixProcedureName(spName);
  147. MySqlParameter returnParameter = GetReturnParameter();
  148. MySqlParameterCollection parms = command.Connection.Settings.CheckParameters ?
  149. CheckParameters(spName) : Parameters;
  150. string setSql = SetUserVariables(parms, preparing);
  151. string callSql = CreateCallStatement(spName, returnParameter, parms);
  152. string outSql = CreateOutputSelect(parms, preparing);
  153. resolvedCommandText = String.Format("{0}{1}{2}", setSql, callSql, outSql);
  154. }
  155. private string SetUserVariables(MySqlParameterCollection parms, bool preparing)
  156. {
  157. StringBuilder setSql = new StringBuilder();
  158. if (serverProvidingOutputParameters) return setSql.ToString();
  159. string delimiter = String.Empty;
  160. foreach (MySqlParameter p in parms)
  161. {
  162. if (p.Direction != ParameterDirection.InputOutput) continue;
  163. string pName = "@" + p.BaseName;
  164. string uName = "@" + ParameterPrefix + p.BaseName;
  165. string sql = String.Format("SET {0}={1}", uName, pName);
  166. if (command.Connection.Settings.AllowBatch && !preparing)
  167. {
  168. setSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, sql);
  169. delimiter = "; ";
  170. }
  171. else
  172. {
  173. MySqlCommand cmd = new MySqlCommand(sql, command.Connection);
  174. cmd.Parameters.Add(p);
  175. cmd.ExecuteNonQuery();
  176. }
  177. }
  178. if (setSql.Length > 0)
  179. setSql.Append("; ");
  180. return setSql.ToString();
  181. }
  182. private string CreateCallStatement(string spName, MySqlParameter returnParameter, MySqlParameterCollection parms)
  183. {
  184. StringBuilder callSql = new StringBuilder();
  185. string delimiter = String.Empty;
  186. foreach (MySqlParameter p in parms)
  187. {
  188. if (p.Direction == ParameterDirection.ReturnValue) continue;
  189. string pName = "@" + p.BaseName;
  190. string uName = "@" + ParameterPrefix + p.BaseName;
  191. bool useRealVar = p.Direction == ParameterDirection.Input || serverProvidingOutputParameters;
  192. callSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, useRealVar ? pName : uName);
  193. delimiter = ", ";
  194. }
  195. if (returnParameter == null)
  196. return String.Format("CALL {0}({1})", spName, callSql.ToString());
  197. else
  198. return String.Format("SET @{0}{1}={2}({3})", ParameterPrefix, returnParameter.BaseName, spName, callSql.ToString());
  199. }
  200. private string CreateOutputSelect(MySqlParameterCollection parms, bool preparing)
  201. {
  202. StringBuilder outSql = new StringBuilder();
  203. string delimiter = String.Empty;
  204. foreach (MySqlParameter p in parms)
  205. {
  206. if (p.Direction == ParameterDirection.Input) continue;
  207. if ((p.Direction == ParameterDirection.InputOutput ||
  208. p.Direction == ParameterDirection.Output) &&
  209. serverProvidingOutputParameters) continue;
  210. string pName = "@" + p.BaseName;
  211. string uName = "@" + ParameterPrefix + p.BaseName;
  212. outSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, uName);
  213. delimiter = ", ";
  214. }
  215. if (outSql.Length == 0) return String.Empty;
  216. if (command.Connection.Settings.AllowBatch && !preparing)
  217. return String.Format(";SELECT {0}", outSql.ToString());
  218. outSelect = String.Format("SELECT {0}", outSql.ToString());
  219. return String.Empty;
  220. }
  221. internal void ProcessOutputParameters(MySqlDataReader reader)
  222. {
  223. // We apparently need to always adjust our output types since the server
  224. // provided data types are not always right
  225. AdjustOutputTypes(reader);
  226. if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0)
  227. return;
  228. // now read the output parameters data row
  229. reader.Read();
  230. string prefix = "@" + StoredProcedure.ParameterPrefix;
  231. for (int i = 0; i < reader.FieldCount; i++)
  232. {
  233. string fieldName = reader.GetName(i);
  234. if (fieldName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
  235. fieldName = fieldName.Remove(0, prefix.Length);
  236. MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
  237. parameter.Value = reader.GetValue(i);
  238. }
  239. }
  240. private void AdjustOutputTypes(MySqlDataReader reader)
  241. {
  242. // since MySQL likes to return user variables as strings
  243. // we reset the types of the readers internal value objects
  244. // this will allow those value objects to parse the string based
  245. // return values
  246. for (int i = 0; i < reader.FieldCount; i++)
  247. {
  248. string fieldName = reader.GetName(i);
  249. if (fieldName.IndexOf(StoredProcedure.ParameterPrefix) != -1)
  250. fieldName = fieldName.Remove(0, StoredProcedure.ParameterPrefix.Length + 1);
  251. MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
  252. IMySqlValue v = MySqlField.GetIMySqlValue(parameter.MySqlDbType);
  253. if (v is MySqlBit)
  254. {
  255. MySqlBit bit = (MySqlBit)v;
  256. bit.ReadAsString = true;
  257. reader.ResultSet.SetValueObject(i, bit);
  258. }
  259. else
  260. reader.ResultSet.SetValueObject(i, v);
  261. }
  262. }
  263. public override void Close(MySqlDataReader reader)
  264. {
  265. base.Close(reader);
  266. if (String.IsNullOrEmpty(outSelect)) return;
  267. if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0) return;
  268. MySqlCommand cmd = new MySqlCommand(outSelect, command.Connection);
  269. using (MySqlDataReader rdr = cmd.ExecuteReader(reader.CommandBehavior))
  270. {
  271. ProcessOutputParameters(rdr);
  272. }
  273. }
  274. }
  275. }
  276. #endif