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.

206 lines
7.5 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.Collections;
  25. using System.Text;
  26. using System.Collections.Generic;
  27. using System.Data;
  28. using Externals.MySql.Data.Common;
  29. namespace Externals.MySql.Data.MySqlClient
  30. {
  31. /// <summary>
  32. /// Summary description for PreparedStatement.
  33. /// </summary>
  34. internal class PreparableStatement : Statement
  35. {
  36. BitArray _nullMap;
  37. readonly List<MySqlParameter> _parametersToSend = new List<MySqlParameter>();
  38. MySqlPacket _packet;
  39. int _dataPosition;
  40. int _nullMapPosition;
  41. public PreparableStatement(MySqlCommand command, string text)
  42. : base(command, text)
  43. {
  44. }
  45. #region Properties
  46. public int ExecutionCount { get; set; }
  47. public bool IsPrepared => StatementId > 0;
  48. public int StatementId { get; private set; }
  49. #endregion
  50. public virtual void Prepare()
  51. {
  52. // strip out names from parameter markers
  53. string text;
  54. List<string> parameterNames = PrepareCommandText(out text);
  55. // ask our connection to send the prepare command
  56. MySqlField[] paramList = null;
  57. StatementId = Driver.PrepareStatement(text, ref paramList);
  58. // now we need to assign our field names since we stripped them out
  59. // for the prepare
  60. for (int i = 0; i < parameterNames.Count; i++)
  61. {
  62. //paramList[i].ColumnName = (string) parameter_names[i];
  63. string parameterName = (string)parameterNames[i];
  64. MySqlParameter p = Parameters.GetParameterFlexible(parameterName, false);
  65. if (p == null)
  66. throw new InvalidOperationException(
  67. String.Format(Resources.ParameterNotFoundDuringPrepare, parameterName));
  68. p.Encoding = paramList[i].Encoding;
  69. _parametersToSend.Add(p);
  70. }
  71. // now prepare our null map
  72. int numNullBytes = 0;
  73. if (paramList != null && paramList.Length > 0)
  74. {
  75. _nullMap = new BitArray(paramList.Length);
  76. numNullBytes = (_nullMap.Length + 7) / 8;
  77. }
  78. _packet = new MySqlPacket(Driver.Encoding);
  79. // write out some values that do not change run to run
  80. _packet.WriteByte(0);
  81. _packet.WriteInteger(StatementId, 4);
  82. _packet.WriteByte((byte)0); // flags; always 0 for 4.1
  83. _packet.WriteInteger(1, 4); // interation count; 1 for 4.1
  84. _nullMapPosition = _packet.Position;
  85. _packet.Position += numNullBytes; // leave room for our null map
  86. _packet.WriteByte(1); // rebound flag
  87. // write out the parameter types
  88. foreach (MySqlParameter p in _parametersToSend)
  89. _packet.WriteInteger(p.GetPSType(), 2);
  90. _dataPosition = _packet.Position;
  91. }
  92. public override void Execute()
  93. {
  94. // if we are not prepared, then call down to our base
  95. if (!IsPrepared)
  96. {
  97. base.Execute();
  98. return;
  99. }
  100. //TODO: support long data here
  101. // create our null bitmap
  102. // we check this because Mono doesn't ignore the case where nullMapBytes
  103. // is zero length.
  104. // if (nullMapBytes.Length > 0)
  105. // {
  106. // byte[] bits = packet.Buffer;
  107. // nullMap.CopyTo(bits,
  108. // nullMap.CopyTo(nullMapBytes, 0);
  109. // start constructing our packet
  110. // if (Parameters.Count > 0)
  111. // nullMap.CopyTo(packet.Buffer, nullMapPosition);
  112. //if (parameters != null && parameters.Count > 0)
  113. //else
  114. // packet.WriteByte( 0 );
  115. //TODO: only send rebound if parms change
  116. // now write out all non-null values
  117. _packet.Position = _dataPosition;
  118. for (int i = 0; i < _parametersToSend.Count; i++)
  119. {
  120. MySqlParameter p = _parametersToSend[i];
  121. _nullMap[i] = (p.Value == DBNull.Value || p.Value == null) ||
  122. p.Direction == ParameterDirection.Output;
  123. if (_nullMap[i]) continue;
  124. _packet.Encoding = p.Encoding;
  125. p.Serialize(_packet, true, Connection.Settings);
  126. }
  127. _nullMap?.CopyTo(_packet.Buffer, _nullMapPosition);
  128. ExecutionCount++;
  129. Driver.ExecuteStatement(_packet);
  130. }
  131. public override bool ExecuteNext()
  132. {
  133. if (!IsPrepared)
  134. return base.ExecuteNext();
  135. return false;
  136. }
  137. /// <summary>
  138. /// Prepares CommandText for use with the Prepare method
  139. /// </summary>
  140. /// <returns>Command text stripped of all paramter names</returns>
  141. /// <remarks>
  142. /// Takes the output of TokenizeSql and creates a single string of SQL
  143. /// that only contains '?' markers for each parameter. It also creates
  144. /// the parameterMap array list that includes all the paramter names in the
  145. /// order they appeared in the SQL
  146. /// </remarks>
  147. private List<string> PrepareCommandText(out string stripped_sql)
  148. {
  149. StringBuilder newSQL = new StringBuilder();
  150. List<string> parameterMap = new List<string>();
  151. int startPos = 0;
  152. string sql = ResolvedCommandText;
  153. MySqlTokenizer tokenizer = new MySqlTokenizer(sql);
  154. string parameter = tokenizer.NextParameter();
  155. while (parameter != null)
  156. {
  157. if (parameter.IndexOf(StoredProcedure.ParameterPrefix) == -1)
  158. {
  159. newSQL.Append(sql.Substring(startPos, tokenizer.StartIndex - startPos));
  160. newSQL.Append("?");
  161. parameterMap.Add(parameter);
  162. startPos = tokenizer.StopIndex;
  163. }
  164. parameter = tokenizer.NextParameter();
  165. }
  166. newSQL.Append(sql.Substring(startPos));
  167. stripped_sql = newSQL.ToString();
  168. return parameterMap;
  169. }
  170. public virtual void CloseStatement()
  171. {
  172. if (!IsPrepared) return;
  173. Driver.CloseStatement(StatementId);
  174. StatementId = 0;
  175. }
  176. }
  177. }
  178. #endif