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.

320 lines
13 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright ?2004, 2016 Oracle and/or its affiliates. All rights reserved.
  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.Linq;
  27. using System.Text;
  28. using Externals.MySql.Data.Common;
  29. using Externals.MySql.Data.Types;
  30. namespace Externals.MySql.Data.MySqlClient
  31. {
  32. /// <summary>
  33. /// Summary description for StoredProcedure.
  34. /// </summary>
  35. internal class StoredProcedure : PreparableStatement
  36. {
  37. private string _outSelect;
  38. // Prefix used for to generate inout or output parameters names
  39. internal const string ParameterPrefix = "_cnet_param_";
  40. private string resolvedCommandText;
  41. public StoredProcedure(MySqlCommand cmd, string text)
  42. : base(cmd, text)
  43. {
  44. }
  45. private MySqlParameter GetReturnParameter()
  46. {
  47. return Parameters?.Cast<MySqlParameter>().FirstOrDefault(p => p.Direction == ParameterDirection.ReturnValue);
  48. }
  49. public bool ServerProvidingOutputParameters { get; private set; }
  50. public override string ResolvedCommandText
  51. {
  52. get { return resolvedCommandText; }
  53. }
  54. internal string GetCacheKey(string spName)
  55. {
  56. string retValue = String.Empty;
  57. StringBuilder key = new StringBuilder(spName);
  58. key.Append("(");
  59. string delimiter = "";
  60. foreach (MySqlParameter p in command.Parameters)
  61. {
  62. if (p.Direction == ParameterDirection.ReturnValue)
  63. retValue = "?=";
  64. else
  65. {
  66. key.AppendFormat(CultureInfo.InvariantCulture, "{0}?", delimiter);
  67. delimiter = ",";
  68. }
  69. }
  70. key.Append(")");
  71. return retValue + key.ToString();
  72. }
  73. private ProcedureCacheEntry GetParameters(string procName)
  74. {
  75. string procCacheKey = GetCacheKey(procName);
  76. ProcedureCacheEntry entry = Connection.ProcedureCache.GetProcedure(Connection, procName, procCacheKey);
  77. return entry;
  78. }
  79. public static string GetFlags(string dtd)
  80. {
  81. int x = dtd.Length - 1;
  82. while (x > 0 && (Char.IsLetterOrDigit(dtd[x]) || dtd[x] == ' '))
  83. x--;
  84. string dtdSubstring = dtd.Substring(x);
  85. return StringUtility.ToUpperInvariant(dtdSubstring);
  86. }
  87. private string FixProcedureName(string name)
  88. {
  89. string[] parts = name.Split('.');
  90. for (int i = 0; i < parts.Length; i++)
  91. if (!parts[i].StartsWith("`", StringComparison.Ordinal))
  92. parts[i] = String.Format("`{0}`", parts[i]);
  93. if (parts.Length == 1) return parts[0];
  94. return String.Format("{0}.{1}", parts[0], parts[1]);
  95. }
  96. private MySqlParameter GetAndFixParameter(string spName, MySqlSchemaRow param, bool realAsFloat, MySqlParameter returnParameter)
  97. {
  98. string mode = (string)param["PARAMETER_MODE"];
  99. string pName = (string)param["PARAMETER_NAME"];
  100. if (param["ORDINAL_POSITION"].Equals(0))
  101. {
  102. if (returnParameter == null)
  103. throw new InvalidOperationException(
  104. String.Format(Resources.RoutineRequiresReturnParameter, spName));
  105. pName = returnParameter.ParameterName;
  106. }
  107. // make sure the parameters given to us have an appropriate type set if it's not already
  108. MySqlParameter p = command.Parameters.GetParameterFlexible(pName, true);
  109. if (!p.TypeHasBeenSet)
  110. {
  111. string datatype = (string)param["DATA_TYPE"];
  112. bool unsigned = GetFlags(param["DTD_IDENTIFIER"].ToString()).IndexOf("UNSIGNED") != -1;
  113. p.MySqlDbType = MetaData.NameToType(datatype, unsigned, realAsFloat, Connection);
  114. }
  115. return p;
  116. }
  117. private MySqlParameterCollection CheckParameters(string spName)
  118. {
  119. MySqlParameterCollection newParms = new MySqlParameterCollection(command);
  120. MySqlParameter returnParameter = GetReturnParameter();
  121. ProcedureCacheEntry entry = GetParameters(spName);
  122. if (entry.procedure == null || entry.procedure.Rows.Count == 0)
  123. throw new InvalidOperationException(String.Format(Resources.RoutineNotFound, spName));
  124. bool realAsFloat = entry.procedure.Rows[0]["SQL_MODE"].ToString().IndexOf("REAL_AS_FLOAT") != -1;
  125. foreach (MySqlSchemaRow param in entry.parameters.Rows)
  126. newParms.Add(GetAndFixParameter(spName, param, realAsFloat, returnParameter));
  127. return newParms;
  128. }
  129. public override void Resolve(bool preparing)
  130. {
  131. // check to see if we are already resolved
  132. if (ResolvedCommandText != null) return;
  133. ServerProvidingOutputParameters = Driver.SupportsOutputParameters && preparing;
  134. // first retrieve the procedure definition from our
  135. // procedure cache
  136. string spName = commandText;
  137. if (spName.IndexOf(".") == -1 && !String.IsNullOrEmpty(Connection.Database))
  138. spName = Connection.Database + "." + spName;
  139. spName = FixProcedureName(spName);
  140. MySqlParameter returnParameter = GetReturnParameter();
  141. MySqlParameterCollection parms = command.Connection.Settings.CheckParameters ?
  142. CheckParameters(spName) : Parameters;
  143. string setSql = SetUserVariables(parms, preparing);
  144. string callSql = CreateCallStatement(spName, returnParameter, parms);
  145. string outSql = CreateOutputSelect(parms, preparing);
  146. resolvedCommandText = String.Format("{0}{1}{2}", setSql, callSql, outSql);
  147. }
  148. private string SetUserVariables(MySqlParameterCollection parms, bool preparing)
  149. {
  150. StringBuilder setSql = new StringBuilder();
  151. if (ServerProvidingOutputParameters) return setSql.ToString();
  152. string delimiter = String.Empty;
  153. foreach (MySqlParameter p in parms)
  154. {
  155. if (p.Direction != ParameterDirection.InputOutput) continue;
  156. string pName = "@" + p.BaseName;
  157. string uName = "@" + ParameterPrefix + p.BaseName;
  158. string sql = String.Format("SET {0}={1}", uName, pName);
  159. if (command.Connection.Settings.AllowBatch && !preparing)
  160. {
  161. setSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, sql);
  162. delimiter = "; ";
  163. }
  164. else
  165. {
  166. MySqlCommand cmd = new MySqlCommand(sql, command.Connection);
  167. cmd.Parameters.Add(p);
  168. cmd.ExecuteNonQuery();
  169. }
  170. }
  171. if (setSql.Length > 0)
  172. setSql.Append("; ");
  173. return setSql.ToString();
  174. }
  175. private string CreateCallStatement(string spName, MySqlParameter returnParameter, MySqlParameterCollection parms)
  176. {
  177. StringBuilder callSql = new StringBuilder();
  178. string delimiter = String.Empty;
  179. foreach (MySqlParameter p in parms)
  180. {
  181. if (p.Direction == ParameterDirection.ReturnValue) continue;
  182. string pName = "@" + p.BaseName;
  183. string uName = "@" + ParameterPrefix + p.BaseName;
  184. bool useRealVar = p.Direction == ParameterDirection.Input || ServerProvidingOutputParameters;
  185. callSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, useRealVar ? pName : uName);
  186. delimiter = ", ";
  187. }
  188. if (returnParameter == null)
  189. return String.Format("CALL {0}({1})", spName, callSql.ToString());
  190. else
  191. return String.Format("SET @{0}{1}={2}({3})", ParameterPrefix, returnParameter.BaseName, spName, callSql.ToString());
  192. }
  193. private string CreateOutputSelect(MySqlParameterCollection parms, bool preparing)
  194. {
  195. StringBuilder outSql = new StringBuilder();
  196. string delimiter = String.Empty;
  197. foreach (MySqlParameter p in parms)
  198. {
  199. if (p.Direction == ParameterDirection.Input) continue;
  200. if ((p.Direction == ParameterDirection.InputOutput ||
  201. p.Direction == ParameterDirection.Output) &&
  202. ServerProvidingOutputParameters) continue;
  203. string pName = "@" + p.BaseName;
  204. string uName = "@" + ParameterPrefix + p.BaseName;
  205. outSql.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", delimiter, uName);
  206. delimiter = ", ";
  207. }
  208. if (outSql.Length == 0) return String.Empty;
  209. if (command.Connection.Settings.AllowBatch && !preparing)
  210. return String.Format(";SELECT {0}", outSql.ToString());
  211. _outSelect = String.Format("SELECT {0}", outSql.ToString());
  212. return String.Empty;
  213. }
  214. internal void ProcessOutputParameters(MySqlDataReader reader)
  215. {
  216. // We apparently need to always adjust our output types since the server
  217. // provided data types are not always right
  218. AdjustOutputTypes(reader);
  219. if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0)
  220. return;
  221. // now read the output parameters data row
  222. reader.Read();
  223. string prefix = "@" + StoredProcedure.ParameterPrefix;
  224. for (int i = 0; i < reader.FieldCount; i++)
  225. {
  226. string fieldName = reader.GetName(i);
  227. if (fieldName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
  228. fieldName = fieldName.Remove(0, prefix.Length);
  229. MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
  230. parameter.Value = reader.GetValue(i);
  231. }
  232. }
  233. private void AdjustOutputTypes(MySqlDataReader reader)
  234. {
  235. // since MySQL likes to return user variables as strings
  236. // we reset the types of the readers internal value objects
  237. // this will allow those value objects to parse the string based
  238. // return values
  239. for (int i = 0; i < reader.FieldCount; i++)
  240. {
  241. string fieldName = reader.GetName(i);
  242. if (fieldName.IndexOf(StoredProcedure.ParameterPrefix) != -1)
  243. fieldName = fieldName.Remove(0, StoredProcedure.ParameterPrefix.Length + 1);
  244. MySqlParameter parameter = command.Parameters.GetParameterFlexible(fieldName, true);
  245. IMySqlValue v = MySqlField.GetIMySqlValue(parameter.MySqlDbType);
  246. if (v is MySqlBit)
  247. {
  248. MySqlBit bit = (MySqlBit)v;
  249. bit.ReadAsString = true;
  250. reader.ResultSet.SetValueObject(i, bit);
  251. }
  252. else
  253. reader.ResultSet.SetValueObject(i, v);
  254. }
  255. }
  256. public override void Close(MySqlDataReader reader)
  257. {
  258. base.Close(reader);
  259. if (String.IsNullOrEmpty(_outSelect)) return;
  260. if ((reader.CommandBehavior & CommandBehavior.SchemaOnly) != 0) return;
  261. MySqlCommand cmd = new MySqlCommand(_outSelect, command.Connection);
  262. using (MySqlDataReader rdr = cmd.ExecuteReader(reader.CommandBehavior))
  263. {
  264. ProcessOutputParameters(rdr);
  265. }
  266. }
  267. }
  268. }
  269. #endif