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.

321 lines
10 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2009, 2018, 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.Generic;
  25. using System.Data;
  26. using System.Diagnostics;
  27. using Externals.MySql.Data.Types;
  28. namespace Externals.MySql.Data.MySqlClient
  29. {
  30. internal class ResultSet
  31. {
  32. private Driver _driver;
  33. private bool[] _uaFieldsUsed;
  34. private Dictionary<string, int> _fieldHashCi;
  35. private int _rowIndex;
  36. private bool _readDone;
  37. private bool _isSequential;
  38. private int _seqIndex;
  39. private readonly int _statementId;
  40. private bool _cached;
  41. private List<IMySqlValue[]> _cachedValues;
  42. public ResultSet(int affectedRows, long insertedId)
  43. {
  44. AffectedRows = affectedRows;
  45. InsertedId = insertedId;
  46. _readDone = true;
  47. }
  48. public ResultSet(Driver d, int statementId, int numCols)
  49. {
  50. AffectedRows = -1;
  51. InsertedId = -1;
  52. _driver = d;
  53. _statementId = statementId;
  54. _rowIndex = -1;
  55. LoadColumns(numCols);
  56. IsOutputParameters = IsOutputParameterResultSet();
  57. HasRows = GetNextRow();
  58. _readDone = !HasRows;
  59. }
  60. #region Properties
  61. public bool HasRows { get; }
  62. public int Size => Fields?.Length ?? 0;
  63. public MySqlField[] Fields { get; private set; }
  64. public IMySqlValue[] Values { get; private set; }
  65. public bool IsOutputParameters { get; set; }
  66. public int AffectedRows { get; private set; }
  67. public long InsertedId { get; private set; }
  68. public int TotalRows { get; private set; }
  69. public int SkippedRows { get; private set; }
  70. public bool Cached
  71. {
  72. get { return _cached; }
  73. set
  74. {
  75. _cached = value;
  76. if (_cached && _cachedValues == null)
  77. _cachedValues = new List<IMySqlValue[]>();
  78. }
  79. }
  80. #endregion
  81. /// <summary>
  82. /// return the ordinal for the given column name
  83. /// </summary>
  84. /// <param name="name"></param>
  85. /// <returns></returns>
  86. public int GetOrdinal(string name)
  87. {
  88. int ordinal;
  89. // quick hash lookup using CI hash
  90. if (_fieldHashCi.TryGetValue(name, out ordinal))
  91. return ordinal;
  92. // Throw an exception if the ordinal cannot be found.
  93. throw new IndexOutOfRangeException(
  94. String.Format(Resources.CouldNotFindColumnName, name));
  95. }
  96. /// <summary>
  97. /// Retrieve the value as the given column index
  98. /// </summary>
  99. /// <param name="index">The column value to retrieve</param>
  100. /// <returns>The value as the given column</returns>
  101. public IMySqlValue this[int index]
  102. {
  103. get
  104. {
  105. if (_rowIndex < 0)
  106. throw new MySqlException(Resources.AttemptToAccessBeforeRead);
  107. // keep count of how many columns we have left to access
  108. _uaFieldsUsed[index] = true;
  109. if (_isSequential && index != _seqIndex)
  110. {
  111. if (index < _seqIndex)
  112. throw new MySqlException(Resources.ReadingPriorColumnUsingSeqAccess);
  113. while (_seqIndex < (index - 1))
  114. _driver.SkipColumnValue(Values[++_seqIndex]);
  115. Values[index] = _driver.ReadColumnValue(index, Fields[index], Values[index]);
  116. _seqIndex = index;
  117. }
  118. return Values[index];
  119. }
  120. }
  121. private bool GetNextRow()
  122. {
  123. bool fetched = _driver.FetchDataRow(_statementId, Size);
  124. if (fetched)
  125. TotalRows++;
  126. return fetched;
  127. }
  128. public bool NextRow(CommandBehavior behavior)
  129. {
  130. if (_readDone)
  131. {
  132. if (Cached) return CachedNextRow(behavior);
  133. return false;
  134. }
  135. if ((behavior & CommandBehavior.SingleRow) != 0 && _rowIndex == 0)
  136. return false;
  137. _isSequential = (behavior & CommandBehavior.SequentialAccess) != 0;
  138. _seqIndex = -1;
  139. // if we are at row index >= 0 then we need to fetch the data row and load it
  140. if (_rowIndex >= 0)
  141. {
  142. bool fetched = false;
  143. try
  144. {
  145. fetched = GetNextRow();
  146. }
  147. catch (MySqlException ex)
  148. {
  149. if (ex.IsQueryAborted)
  150. {
  151. // avoid hanging on Close()
  152. _readDone = true;
  153. }
  154. throw;
  155. }
  156. if (!fetched)
  157. {
  158. _readDone = true;
  159. return false;
  160. }
  161. }
  162. if (!_isSequential) ReadColumnData(false);
  163. _rowIndex++;
  164. return true;
  165. }
  166. private bool CachedNextRow(CommandBehavior behavior)
  167. {
  168. if ((behavior & CommandBehavior.SingleRow) != 0 && _rowIndex == 0)
  169. return false;
  170. if (_rowIndex == (TotalRows - 1)) return false;
  171. _rowIndex++;
  172. Values = _cachedValues[_rowIndex];
  173. return true;
  174. }
  175. /// <summary>
  176. /// Closes the current resultset, dumping any data still on the wire
  177. /// </summary>
  178. public void Close()
  179. {
  180. if (!_readDone)
  181. {
  182. // if we have rows but the user didn't read the first one then mark it as skipped
  183. if (HasRows && _rowIndex == -1)
  184. SkippedRows++;
  185. try
  186. {
  187. while (_driver.IsOpen && _driver.SkipDataRow())
  188. {
  189. TotalRows++;
  190. SkippedRows++;
  191. }
  192. }
  193. catch (System.IO.IOException)
  194. {
  195. // it is ok to eat IO exceptions here, we just want to
  196. // close the result set
  197. }
  198. _readDone = true;
  199. }
  200. else if (_driver == null)
  201. CacheClose();
  202. _driver = null;
  203. if (Cached) CacheReset();
  204. }
  205. private void CacheClose()
  206. {
  207. SkippedRows = TotalRows - _rowIndex - 1;
  208. }
  209. private void CacheReset()
  210. {
  211. if (!Cached) return;
  212. _rowIndex = -1;
  213. AffectedRows = -1;
  214. InsertedId = -1;
  215. SkippedRows = 0;
  216. }
  217. public bool FieldRead(int index)
  218. {
  219. Debug.Assert(Size > index);
  220. return _uaFieldsUsed[index];
  221. }
  222. public void SetValueObject(int i, IMySqlValue valueObject)
  223. {
  224. Debug.Assert(Values != null);
  225. Debug.Assert(i < Values.Length);
  226. Values[i] = valueObject;
  227. }
  228. private bool IsOutputParameterResultSet()
  229. {
  230. if (_driver.HasStatus(ServerStatusFlags.OutputParameters)) return true;
  231. if (Fields.Length == 0) return false;
  232. for (int x = 0; x < Fields.Length; x++)
  233. if (!Fields[x].ColumnName.StartsWith("@" + StoredProcedure.ParameterPrefix, StringComparison.OrdinalIgnoreCase)) return false;
  234. return true;
  235. }
  236. /// <summary>
  237. /// Loads the column metadata for the current resultset
  238. /// </summary>
  239. private void LoadColumns(int numCols)
  240. {
  241. Fields = _driver.GetColumns(numCols);
  242. Values = new IMySqlValue[numCols];
  243. _uaFieldsUsed = new bool[numCols];
  244. _fieldHashCi = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  245. for (int i = 0; i < Fields.Length; i++)
  246. {
  247. string columnName = Fields[i].ColumnName;
  248. if (!_fieldHashCi.ContainsKey(columnName))
  249. _fieldHashCi.Add(columnName, i);
  250. Values[i] = Fields[i].GetValueObject();
  251. }
  252. }
  253. private void ReadColumnData(bool outputParms)
  254. {
  255. for (int i = 0; i < Size; i++)
  256. Values[i] = _driver.ReadColumnValue(i, Fields[i], Values[i]);
  257. // if we are caching then we need to save a copy of this row of data values
  258. if (Cached)
  259. _cachedValues.Add((IMySqlValue[])Values.Clone());
  260. // we don't need to worry about caching the following since you won't have output
  261. // params with TableDirect commands
  262. if (!outputParms) return;
  263. bool rowExists = _driver.FetchDataRow(_statementId, Fields.Length);
  264. _rowIndex = 0;
  265. if (rowExists)
  266. throw new MySqlException(Resources.MoreThanOneOPRow);
  267. }
  268. }
  269. }
  270. #endif