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.

1083 lines
37 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright ?2004, 2019, 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 Externals.MySql.Data.Types;
  24. using System;
  25. using System.Collections;
  26. using System.Data;
  27. using System.Data.Common;
  28. using System.Globalization;
  29. using System.Threading;
  30. namespace Externals.MySql.Data.MySqlClient
  31. {
  32. internal sealed partial class MySqlDataReader : DbDataReader, IDataReader, IDataRecord, IDisposable
  33. {
  34. // The DataReader should always be open when returned to the user.
  35. private bool _isOpen = true;
  36. internal long affectedRows;
  37. internal Driver driver;
  38. // Used in special circumstances with stored procs to avoid exceptions from DbDataAdapter
  39. // If set, AffectedRows returns -1 instead of 0.
  40. private readonly bool _disableZeroAffectedRows;
  41. /*
  42. * Keep track of the connection in order to implement the
  43. * CommandBehavior.CloseConnection flag. A null reference means
  44. * normal behavior (do not automatically close).
  45. */
  46. private MySqlConnection _connection;
  47. /*
  48. * Because the user should not be able to directly create a
  49. * DataReader object, the constructors are
  50. * marked as internal.
  51. */
  52. internal MySqlDataReader(MySqlCommand cmd, PreparableStatement statement, CommandBehavior behavior)
  53. {
  54. this.Command = cmd;
  55. _connection = Command.Connection;
  56. CommandBehavior = behavior;
  57. driver = _connection.driver;
  58. affectedRows = -1;
  59. this.Statement = statement;
  60. if (cmd.CommandType == CommandType.StoredProcedure
  61. && cmd.UpdatedRowSource == UpdateRowSource.FirstReturnedRecord
  62. )
  63. {
  64. _disableZeroAffectedRows = true;
  65. }
  66. }
  67. #region Properties
  68. internal PreparableStatement Statement { get; }
  69. internal MySqlCommand Command { get; private set; }
  70. internal ResultSet ResultSet { get; private set; }
  71. internal CommandBehavior CommandBehavior { get; private set; }
  72. /// <summary>
  73. /// Gets the number of columns in the current row.
  74. /// </summary>
  75. public override int FieldCount => ResultSet?.Size ?? 0;
  76. /// <summary>
  77. /// Gets a value indicating whether the MySqlDataReader contains one or more rows.
  78. /// </summary>
  79. public override bool HasRows => ResultSet?.HasRows ?? false;
  80. /// <summary>
  81. /// Gets a value indicating whether the data reader is closed.
  82. /// </summary>
  83. public override bool IsClosed => !_isOpen;
  84. /// <summary>
  85. /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
  86. /// </summary>
  87. public override int RecordsAffected
  88. {
  89. // RecordsAffected returns the number of rows affected in batch
  90. // statments from insert/delete/update statments. This property
  91. // is not completely accurate until .Close() has been called.
  92. get
  93. {
  94. if (!_disableZeroAffectedRows) return (int)affectedRows;
  95. // In special case of updating stored procedure called from
  96. // within data adapter, we return -1 to avoid exceptions
  97. // (s. Bug#54895)
  98. if (affectedRows == 0)
  99. return -1;
  100. return (int)affectedRows;
  101. }
  102. }
  103. /// <summary>
  104. /// Overloaded. Gets the value of a column in its native format.
  105. /// In C#, this property is the indexer for the MySqlDataReader class.
  106. /// </summary>
  107. public override object this[int i] => GetValue(i);
  108. /// <summary>
  109. /// Gets the value of a column in its native format.
  110. /// [C#] In C#, this property is the indexer for the MySqlDataReader class.
  111. /// </summary>
  112. public override object this[String name] => this[GetOrdinal(name)];
  113. /// <summary>
  114. /// Gets a value indicating the depth of nesting for the current row. This method is not
  115. /// supported currently and always returns 0.
  116. /// </summary>
  117. public override int Depth => 0;
  118. #endregion
  119. /// <summary>
  120. /// Closes the MySqlDataReader object.
  121. /// </summary>
  122. #if NETSTANDARD1_3
  123. public void Close()
  124. #else
  125. public override void Close()
  126. #endif
  127. {
  128. if (!_isOpen) return;
  129. bool shouldCloseConnection = (CommandBehavior & CommandBehavior.CloseConnection) != 0;
  130. CommandBehavior originalBehavior = CommandBehavior;
  131. // clear all remaining resultsets
  132. try
  133. {
  134. // Temporarily change to Default behavior to allow NextResult to finish properly.
  135. if (!originalBehavior.Equals(CommandBehavior.SchemaOnly))
  136. CommandBehavior = CommandBehavior.Default;
  137. while (NextResult()) { }
  138. }
  139. catch (MySqlException ex)
  140. {
  141. // Ignore aborted queries
  142. if (!ex.IsQueryAborted)
  143. {
  144. // ignore IO exceptions.
  145. // We are closing or disposing reader, and do not
  146. // want exception to be propagated to used. If socket is
  147. // is closed on the server side, next query will run into
  148. // IO exception. If reader is closed by GC, we also would
  149. // like to avoid any exception here.
  150. bool isIOException = false;
  151. for (Exception exception = ex; exception != null;
  152. exception = exception.InnerException)
  153. {
  154. if (exception is System.IO.IOException)
  155. {
  156. isIOException = true;
  157. break;
  158. }
  159. }
  160. if (!isIOException)
  161. {
  162. // Ordinary exception (neither IO nor query aborted)
  163. throw;
  164. }
  165. }
  166. }
  167. catch (System.IO.IOException)
  168. {
  169. // eat, on the same reason we eat IO exceptions wrapped into
  170. // MySqlExceptions reasons, described above.
  171. }
  172. finally
  173. {
  174. // always ensure internal reader is null (Bug #55558)
  175. _connection.Reader = null;
  176. CommandBehavior = originalBehavior;
  177. }
  178. // we now give the command a chance to terminate. In the case of
  179. // stored procedures it needs to update out and inout parameters
  180. Command.Close(this);
  181. CommandBehavior = CommandBehavior.Default;
  182. if (this.Command.Canceled && _connection.driver.Version.isAtLeast(5, 1, 0))
  183. {
  184. // Issue dummy command to clear kill flag
  185. ClearKillFlag();
  186. }
  187. if (shouldCloseConnection)
  188. _connection.Close();
  189. Command = null;
  190. _connection.IsInUse = false;
  191. _connection = null;
  192. _isOpen = false;
  193. }
  194. #region TypeSafe Accessors
  195. /// <summary>
  196. /// Gets the value of the specified column as a Boolean.
  197. /// </summary>
  198. /// <param name="name"></param>
  199. /// <returns></returns>
  200. public bool GetBoolean(string name)
  201. {
  202. return GetBoolean(GetOrdinal(name));
  203. }
  204. /// <summary>
  205. /// Gets the value of the specified column as a Boolean.
  206. /// </summary>
  207. /// <param name="i"></param>
  208. /// <returns></returns>
  209. public override bool GetBoolean(int i)
  210. {
  211. var asValue = GetValue(i);
  212. int numericValue;
  213. if (int.TryParse(asValue as string, out numericValue))
  214. return Convert.ToBoolean(numericValue);
  215. return Convert.ToBoolean(asValue);
  216. }
  217. /// <summary>
  218. /// Gets the value of the specified column as a byte.
  219. /// </summary>
  220. /// <param name="name"></param>
  221. /// <returns></returns>
  222. public byte GetByte(string name)
  223. {
  224. return GetByte(GetOrdinal(name));
  225. }
  226. /// <summary>
  227. /// Gets the value of the specified column as a byte.
  228. /// </summary>
  229. /// <param name="i"></param>
  230. /// <returns></returns>
  231. public override byte GetByte(int i)
  232. {
  233. IMySqlValue v = GetFieldValue(i, false);
  234. if (v is MySqlUByte)
  235. return ((MySqlUByte)v).Value;
  236. else
  237. return (byte)((MySqlByte)v).Value;
  238. }
  239. /// <summary>
  240. /// Gets the value of the specified column as a sbyte.
  241. /// </summary>
  242. /// <param name="name"></param>
  243. /// <returns></returns>
  244. public sbyte GetSByte(string name)
  245. {
  246. return GetSByte(GetOrdinal(name));
  247. }
  248. /// <summary>
  249. /// Gets the value of the specified column as a sbyte.
  250. /// </summary>
  251. /// <param name="i"></param>
  252. /// <returns></returns>
  253. public sbyte GetSByte(int i)
  254. {
  255. IMySqlValue v = GetFieldValue(i, false);
  256. if (v is MySqlByte)
  257. return ((MySqlByte)v).Value;
  258. else
  259. return (sbyte)((MySqlByte)v).Value;
  260. }
  261. /// <summary>
  262. /// Reads a stream of bytes from the specified column offset into the buffer an array starting at the given buffer offset.
  263. /// </summary>
  264. /// <param name="i">The zero-based column ordinal. </param>
  265. /// <param name="fieldOffset">The index within the field from which to begin the read operation. </param>
  266. /// <param name="buffer">The buffer into which to read the stream of bytes. </param>
  267. /// <param name="bufferoffset">The index for buffer to begin the read operation. </param>
  268. /// <param name="length">The maximum length to copy into the buffer. </param>
  269. /// <returns>The actual number of bytes read.</returns>
  270. public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
  271. {
  272. if (i >= FieldCount)
  273. Throw(new IndexOutOfRangeException());
  274. IMySqlValue val = GetFieldValue(i, false);
  275. if (!(val is MySqlBinary) && !(val is MySqlGuid))
  276. Throw(new MySqlException("GetBytes can only be called on binary or guid columns"));
  277. byte[] bytes = null;
  278. if (val is MySqlBinary)
  279. bytes = ((MySqlBinary)val).Value;
  280. else
  281. bytes = ((MySqlGuid)val).Bytes;
  282. if (buffer == null)
  283. return bytes.Length;
  284. if (bufferoffset >= buffer.Length || bufferoffset < 0)
  285. Throw(new IndexOutOfRangeException("Buffer index must be a valid index in buffer"));
  286. if (buffer.Length < (bufferoffset + length))
  287. Throw(new ArgumentException("Buffer is not large enough to hold the requested data"));
  288. if (fieldOffset < 0 ||
  289. ((ulong)fieldOffset >= (ulong)bytes.Length && (ulong)bytes.Length > 0))
  290. Throw(new IndexOutOfRangeException("Data index must be a valid index in the field"));
  291. // adjust the length so we don't run off the end
  292. if ((ulong)bytes.Length < (ulong)(fieldOffset + length))
  293. {
  294. length = (int)((ulong)bytes.Length - (ulong)fieldOffset);
  295. }
  296. Buffer.BlockCopy(bytes, (int)fieldOffset, buffer, (int)bufferoffset, (int)length);
  297. return length;
  298. }
  299. private object ChangeType(IMySqlValue value, int fieldIndex, Type newType)
  300. {
  301. ResultSet.Fields[fieldIndex].AddTypeConversion(newType);
  302. return Convert.ChangeType(value.Value, newType, CultureInfo.InvariantCulture);
  303. }
  304. /// <summary>
  305. /// Gets the value of the specified column as a single character.
  306. /// </summary>
  307. /// <param name="name"></param>
  308. /// <returns></returns>
  309. public char GetChar(string name)
  310. {
  311. return GetChar(GetOrdinal(name));
  312. }
  313. /// <summary>
  314. /// Gets the value of the specified column as a single character.
  315. /// </summary>
  316. /// <param name="i"></param>
  317. /// <returns></returns>
  318. public override char GetChar(int i)
  319. {
  320. string s = GetString(i);
  321. return s[0];
  322. }
  323. /// <summary>
  324. /// Reads a stream of characters from the specified column offset into the buffer as an array starting at the given buffer offset.
  325. /// </summary>
  326. /// <param name="i"></param>
  327. /// <param name="fieldoffset"></param>
  328. /// <param name="buffer"></param>
  329. /// <param name="bufferoffset"></param>
  330. /// <param name="length"></param>
  331. /// <returns></returns>
  332. public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
  333. {
  334. if (i >= FieldCount)
  335. Throw(new IndexOutOfRangeException());
  336. string valAsString = GetString(i);
  337. if (buffer == null) return valAsString.Length;
  338. if (bufferoffset >= buffer.Length || bufferoffset < 0)
  339. Throw(new IndexOutOfRangeException("Buffer index must be a valid index in buffer"));
  340. if (buffer.Length < (bufferoffset + length))
  341. Throw(new ArgumentException("Buffer is not large enough to hold the requested data"));
  342. if (fieldoffset < 0 || fieldoffset >= valAsString.Length)
  343. Throw(new IndexOutOfRangeException("Field offset must be a valid index in the field"));
  344. if (valAsString.Length < length)
  345. length = valAsString.Length;
  346. valAsString.CopyTo((int)fieldoffset, buffer, bufferoffset, length);
  347. return length;
  348. }
  349. /// <summary>
  350. /// Gets the name of the source data type.
  351. /// </summary>
  352. /// <param name="i"></param>
  353. /// <returns></returns>
  354. public override String GetDataTypeName(int i)
  355. {
  356. if (!_isOpen)
  357. Throw(new Exception("No current query in data reader"));
  358. if (i >= FieldCount)
  359. Throw(new IndexOutOfRangeException());
  360. // return the name of the type used on the backend
  361. IMySqlValue v = ResultSet.Values[i];
  362. return v.MySqlTypeName;
  363. }
  364. public MySqlDateTime GetMySqlDateTime(string column)
  365. {
  366. return GetMySqlDateTime(GetOrdinal(column));
  367. }
  368. public MySqlDateTime GetMySqlDateTime(int column)
  369. {
  370. return (MySqlDateTime)GetFieldValue(column, true);
  371. }
  372. public DateTime GetDateTime(string column)
  373. {
  374. return GetDateTime(GetOrdinal(column));
  375. }
  376. public override DateTime GetDateTime(int i)
  377. {
  378. IMySqlValue val = GetFieldValue(i, true);
  379. MySqlDateTime dt;
  380. if (val is MySqlDateTime)
  381. dt = (MySqlDateTime)val;
  382. else
  383. {
  384. // we need to do this because functions like date_add return string
  385. string s = GetString(i);
  386. dt = MySqlDateTime.Parse(s);
  387. }
  388. dt.TimezoneOffset = driver.timeZoneOffset;
  389. if (_connection.Settings.ConvertZeroDateTime && !dt.IsValidDateTime)
  390. return DateTime.MinValue;
  391. else
  392. return dt.GetDateTime();
  393. }
  394. /// <summary>
  395. /// Gets the value of the specified column as a <see cref="MySqlDecimal"/>.
  396. /// </summary>
  397. /// <param name="column">The name of the colum.</param>
  398. /// <returns>The value of the specified column as a <see cref="MySqlDecimal"/>.</returns>
  399. public MySqlDecimal GetMySqlDecimal(string column)
  400. {
  401. return GetMySqlDecimal(GetOrdinal(column));
  402. }
  403. /// <summary>
  404. /// Gets the value of the specified column as a <see cref="MySqlDecimal"/>.
  405. /// </summary>
  406. /// <param name="i">The index of the colum.</param>
  407. /// <returns>The value of the specified column as a <see cref="MySqlDecimal"/>.</returns>
  408. public MySqlDecimal GetMySqlDecimal(int i)
  409. {
  410. return (MySqlDecimal)GetFieldValue(i, false);
  411. }
  412. public Decimal GetDecimal(string column)
  413. {
  414. return GetDecimal(GetOrdinal(column));
  415. }
  416. public override Decimal GetDecimal(int i)
  417. {
  418. IMySqlValue v = GetFieldValue(i, true);
  419. if (v is MySqlDecimal)
  420. return ((MySqlDecimal)v).Value;
  421. return Convert.ToDecimal(v.Value);
  422. }
  423. public double GetDouble(string column)
  424. {
  425. return GetDouble(GetOrdinal(column));
  426. }
  427. public override double GetDouble(int i)
  428. {
  429. IMySqlValue v = GetFieldValue(i, true);
  430. if (v is MySqlDouble)
  431. return ((MySqlDouble)v).Value;
  432. return Convert.ToDouble(v.Value);
  433. }
  434. public Type GetFieldType(string column)
  435. {
  436. return GetFieldType(GetOrdinal(column));
  437. }
  438. /// <summary>
  439. /// Gets the Type that is the data type of the object.
  440. /// </summary>
  441. /// <param name="i"></param>
  442. /// <returns></returns>
  443. public override Type GetFieldType(int i)
  444. {
  445. if (!_isOpen)
  446. Throw(new Exception("No current query in data reader"));
  447. if (i >= FieldCount)
  448. Throw(new IndexOutOfRangeException());
  449. // we have to use the values array directly because we can't go through
  450. // GetValue
  451. IMySqlValue v = ResultSet.Values[i];
  452. if (v is MySqlDateTime)
  453. {
  454. if (!_connection.Settings.AllowZeroDateTime)
  455. return typeof(DateTime);
  456. return typeof(MySqlDateTime);
  457. }
  458. return v.SystemType;
  459. }
  460. public float GetFloat(string column)
  461. {
  462. return GetFloat(GetOrdinal(column));
  463. }
  464. public override float GetFloat(int i)
  465. {
  466. IMySqlValue v = GetFieldValue(i, true);
  467. if (v is MySqlSingle)
  468. return ((MySqlSingle)v).Value;
  469. return Convert.ToSingle(v.Value);
  470. }
  471. /// <summary>
  472. /// Gets the value of the specified column as a globally-unique identifier(GUID).
  473. /// </summary>
  474. /// <param name="column">The name of the column.</param>
  475. /// <returns></returns>
  476. public Guid GetGuid(string column)
  477. {
  478. return GetGuid(GetOrdinal(column));
  479. }
  480. public override Guid GetGuid(int i)
  481. {
  482. object v = GetValue(i);
  483. if (v is Guid)
  484. return (Guid)v;
  485. if (v is string)
  486. return new Guid(v as string);
  487. if (v is byte[])
  488. {
  489. byte[] bytes = (byte[])v;
  490. if (bytes.Length == 16)
  491. return new Guid(bytes);
  492. }
  493. Throw(new MySqlException(Resources.ValueNotSupportedForGuid));
  494. return Guid.Empty; // just to silence compiler
  495. }
  496. public Int16 GetInt16(string column)
  497. {
  498. return GetInt16(GetOrdinal(column));
  499. }
  500. public override Int16 GetInt16(int i)
  501. {
  502. IMySqlValue v = GetFieldValue(i, true);
  503. if (v is MySqlInt16)
  504. return ((MySqlInt16)v).Value;
  505. return (short)ChangeType(v, i, typeof(short));
  506. }
  507. public Int32 GetInt32(string column)
  508. {
  509. return GetInt32(GetOrdinal(column));
  510. }
  511. public override Int32 GetInt32(int i)
  512. {
  513. IMySqlValue v = GetFieldValue(i, true);
  514. if (v is MySqlInt32)
  515. return ((MySqlInt32)v).Value;
  516. return (Int32)ChangeType(v, i, typeof(Int32));
  517. }
  518. public Int64 GetInt64(string column)
  519. {
  520. return GetInt64(GetOrdinal(column));
  521. }
  522. public override Int64 GetInt64(int i)
  523. {
  524. IMySqlValue v = GetFieldValue(i, true);
  525. if (v is MySqlInt64)
  526. return ((MySqlInt64)v).Value;
  527. return (Int64)ChangeType(v, i, typeof(Int64));
  528. }
  529. /// <summary>
  530. /// Gets the name of the specified column.
  531. /// </summary>
  532. /// <param name="i"></param>
  533. /// <returns></returns>
  534. public override String GetName(int i)
  535. {
  536. if (!_isOpen)
  537. Throw(new Exception("No current query in data reader"));
  538. if (i >= FieldCount)
  539. Throw(new IndexOutOfRangeException());
  540. return ResultSet.Fields[i].ColumnName;
  541. }
  542. /// <summary>
  543. /// Gets the column ordinal, given the name of the column.
  544. /// </summary>
  545. /// <param name="name"></param>
  546. /// <returns></returns>
  547. public override int GetOrdinal(string name)
  548. {
  549. if (!_isOpen || ResultSet == null)
  550. Throw(new Exception("No current query in data reader"));
  551. return ResultSet.GetOrdinal(name);
  552. }
  553. public string GetString(string column)
  554. {
  555. return GetString(GetOrdinal(column));
  556. }
  557. public override String GetString(int i)
  558. {
  559. IMySqlValue val = GetFieldValue(i, true);
  560. if (val is MySqlBinary)
  561. {
  562. byte[] v = ((MySqlBinary)val).Value;
  563. return ResultSet.Fields[i].Encoding.GetString(v, 0, v.Length);
  564. }
  565. return val.Value.ToString();
  566. }
  567. public TimeSpan GetTimeSpan(string column)
  568. {
  569. return GetTimeSpan(GetOrdinal(column));
  570. }
  571. public TimeSpan GetTimeSpan(int column)
  572. {
  573. IMySqlValue val = GetFieldValue(column, true);
  574. MySqlTimeSpan ts = (MySqlTimeSpan)val;
  575. return ts.Value;
  576. }
  577. /// <summary>
  578. /// Gets the value of the specified column in its native format.
  579. /// </summary>
  580. /// <param name="i"></param>
  581. /// <returns></returns>
  582. public override object GetValue(int i)
  583. {
  584. if (!_isOpen)
  585. Throw(new Exception("No current query in data reader"));
  586. if (i >= FieldCount)
  587. Throw(new IndexOutOfRangeException());
  588. IMySqlValue val = GetFieldValue(i, false);
  589. if (val.IsNull)
  590. {
  591. if (!(val.MySqlDbType == MySqlDbType.Time && val.Value.ToString() == "00:00:00"))
  592. return DBNull.Value;
  593. }
  594. // if the column is a date/time, then we return a MySqlDateTime
  595. // so .ToString() will print '0000-00-00' correctly
  596. if (val is MySqlDateTime)
  597. {
  598. MySqlDateTime dt = (MySqlDateTime)val;
  599. if (!dt.IsValidDateTime && _connection.Settings.ConvertZeroDateTime)
  600. return DateTime.MinValue;
  601. else if (_connection.Settings.AllowZeroDateTime)
  602. return val;
  603. else
  604. return dt.GetDateTime();
  605. }
  606. return val.Value;
  607. }
  608. /// <summary>
  609. /// Gets all attribute columns in the collection for the current row.
  610. /// </summary>
  611. /// <param name="values"></param>
  612. /// <returns></returns>
  613. public override int GetValues(object[] values)
  614. {
  615. int numCols = Math.Min(values.Length, FieldCount);
  616. for (int i = 0; i < numCols; i++)
  617. values[i] = GetValue(i);
  618. return numCols;
  619. }
  620. public UInt16 GetUInt16(string column)
  621. {
  622. return GetUInt16(GetOrdinal(column));
  623. }
  624. public UInt16 GetUInt16(int column)
  625. {
  626. IMySqlValue v = GetFieldValue(column, true);
  627. if (v is MySqlUInt16)
  628. return ((MySqlUInt16)v).Value;
  629. return (UInt16)ChangeType(v, column, typeof(UInt16));
  630. }
  631. public UInt32 GetUInt32(string column)
  632. {
  633. return GetUInt32(GetOrdinal(column));
  634. }
  635. public UInt32 GetUInt32(int column)
  636. {
  637. IMySqlValue v = GetFieldValue(column, true);
  638. if (v is MySqlUInt32)
  639. return ((MySqlUInt32)v).Value;
  640. return (uint)ChangeType(v, column, typeof(UInt32));
  641. }
  642. public UInt64 GetUInt64(string column)
  643. {
  644. return GetUInt64(GetOrdinal(column));
  645. }
  646. public UInt64 GetUInt64(int column)
  647. {
  648. IMySqlValue v = GetFieldValue(column, true);
  649. if (v is MySqlUInt64)
  650. return ((MySqlUInt64)v).Value;
  651. return (UInt64)ChangeType(v, column, typeof(UInt64));
  652. }
  653. #endregion
  654. #if !NETSTANDARD1_3
  655. IDataReader IDataRecord.GetData(int i)
  656. {
  657. return base.GetData(i);
  658. }
  659. #else
  660. public new DbDataReader GetData(int i)
  661. {
  662. return base.GetData(i);
  663. }
  664. #endif
  665. /// <summary>
  666. /// Gets a value indicating whether the column contains non-existent or missing values.
  667. /// </summary>
  668. /// <param name="i"></param>
  669. /// <returns></returns>
  670. public override bool IsDBNull(int i)
  671. {
  672. return DBNull.Value == GetValue(i);
  673. }
  674. /// <summary>
  675. /// Advances the data reader to the next result, when reading the results of batch SQL statements.
  676. /// </summary>
  677. /// <returns></returns>
  678. public override bool NextResult()
  679. {
  680. if (!_isOpen)
  681. Throw(new MySqlException(Resources.NextResultIsClosed));
  682. bool isCaching = Command.CommandType == CommandType.TableDirect && Command.EnableCaching &&
  683. (CommandBehavior & CommandBehavior.SequentialAccess) == 0;
  684. // this will clear out any unread data
  685. if (ResultSet != null)
  686. {
  687. ResultSet.Close();
  688. if (isCaching)
  689. TableCache.AddToCache(Command.CommandText, ResultSet);
  690. }
  691. // single result means we only return a single resultset. If we have already
  692. // returned one, then we return false
  693. // TableDirect is basically a select * from a single table so it will generate
  694. // a single result also
  695. if (ResultSet != null &&
  696. ((CommandBehavior & CommandBehavior.SingleResult) != 0 || isCaching))
  697. return false;
  698. // next load up the next resultset if any
  699. try
  700. {
  701. do
  702. {
  703. ResultSet = null;
  704. // if we are table caching, then try to retrieve the resultSet from the cache
  705. if (isCaching)
  706. ResultSet = TableCache.RetrieveFromCache(Command.CommandText,
  707. Command.CacheAge);
  708. if (ResultSet == null)
  709. {
  710. ResultSet = driver.NextResult(Statement.StatementId, false);
  711. if (ResultSet == null) return false;
  712. if (ResultSet.IsOutputParameters && Command.CommandType == CommandType.StoredProcedure)
  713. {
  714. StoredProcedure sp = Statement as StoredProcedure;
  715. sp.ProcessOutputParameters(this);
  716. ResultSet.Close();
  717. if (!sp.ServerProvidingOutputParameters) return false;
  718. // if we are using server side output parameters then we will get our ok packet
  719. // *after* the output parameters resultset
  720. ResultSet = driver.NextResult(Statement.StatementId, true);
  721. }
  722. ResultSet.Cached = isCaching;
  723. }
  724. if (ResultSet.Size == 0)
  725. {
  726. Command.LastInsertedId = ResultSet.InsertedId;
  727. if (affectedRows == -1)
  728. affectedRows = ResultSet.AffectedRows;
  729. else
  730. affectedRows += ResultSet.AffectedRows;
  731. }
  732. } while (ResultSet.Size == 0);
  733. return true;
  734. }
  735. catch (MySqlException ex)
  736. {
  737. if (ex.IsFatal)
  738. _connection.Abort();
  739. if (ex.Number == 0)
  740. throw new MySqlException(Resources.FatalErrorReadingResult, ex);
  741. if ((CommandBehavior & CommandBehavior.CloseConnection) != 0)
  742. Close();
  743. throw;
  744. }
  745. }
  746. /// <summary>
  747. /// Advances the MySqlDataReader to the next record.
  748. /// </summary>
  749. /// <returns></returns>
  750. public override bool Read()
  751. {
  752. if (!_isOpen)
  753. Throw(new MySqlException("Invalid attempt to Read when reader is closed."));
  754. if (ResultSet == null)
  755. return false;
  756. try
  757. {
  758. return ResultSet.NextRow(CommandBehavior);
  759. }
  760. catch (TimeoutException tex)
  761. {
  762. _connection.HandleTimeoutOrThreadAbort(tex);
  763. throw; // unreached
  764. }
  765. #if !NETSTANDARD1_3
  766. catch (ThreadAbortException taex)
  767. {
  768. _connection.HandleTimeoutOrThreadAbort(taex);
  769. throw;
  770. }
  771. #endif
  772. catch (MySqlException ex)
  773. {
  774. if (ex.IsFatal)
  775. _connection.Abort();
  776. if (ex.IsQueryAborted)
  777. {
  778. throw;
  779. }
  780. throw new MySqlException(Resources.FatalErrorDuringRead, ex);
  781. }
  782. }
  783. /// <summary>
  784. /// Gets the value of the specified column as a <see cref="MySqlGeometry"/>.
  785. /// </summary>
  786. /// <param name="i">The index of the colum.</param>
  787. /// <returns>The value of the specified column as a <see cref="MySqlGeometry"/>.</returns>
  788. public MySqlGeometry GetMySqlGeometry(int i)
  789. {
  790. try
  791. {
  792. IMySqlValue v = GetFieldValue(i, false);
  793. if (v is MySqlGeometry || v is MySqlBinary)
  794. return new MySqlGeometry(MySqlDbType.Geometry, (Byte[])v.Value);
  795. }
  796. catch
  797. {
  798. Throw(new Exception("Can't get MySqlGeometry from value"));
  799. }
  800. return new MySqlGeometry(true);
  801. }
  802. /// <summary>
  803. /// Gets the value of the specified column as a <see cref="MySqlGeometry"/>.
  804. /// </summary>
  805. /// <param name="column">The name of the colum.</param>
  806. /// <returns>The value of the specified column as a <see cref="MySqlGeometry"/>.</returns>
  807. public MySqlGeometry GetMySqlGeometry(string column)
  808. {
  809. return GetMySqlGeometry(GetOrdinal(column));
  810. }
  811. /// <summary>
  812. /// Returns an <see cref="IEnumerator"/> that iterates through the <see cref="MySqlDataReader"/>.
  813. /// </summary>
  814. public override IEnumerator GetEnumerator()
  815. {
  816. return new DbEnumerator(this, (CommandBehavior & CommandBehavior.CloseConnection) != 0);
  817. }
  818. private IMySqlValue GetFieldValue(int index, bool checkNull)
  819. {
  820. if (index < 0 || index >= FieldCount)
  821. Throw(new ArgumentException(Resources.InvalidColumnOrdinal));
  822. IMySqlValue v = ResultSet[index];
  823. if (!(v.MySqlDbType is MySqlDbType.Time && v.Value.ToString() == "00:00:00"))
  824. {
  825. if (checkNull && v.IsNull)
  826. throw new System.Data.SqlTypes.SqlNullValueException();
  827. }
  828. return v;
  829. }
  830. private void ClearKillFlag()
  831. {
  832. // This query will silently crash because of the Kill call that happened before.
  833. string dummyStatement = "SELECT * FROM bogus_table LIMIT 0"; /* dummy query used to clear kill flag */
  834. MySqlCommand dummyCommand = new MySqlCommand(dummyStatement, _connection) { InternallyCreated = true };
  835. try
  836. {
  837. dummyCommand.ExecuteReader(); // ExecuteReader catches the exception and returns null, which is expected.
  838. }
  839. catch (MySqlException ex)
  840. {
  841. if (ex.Number != (int)MySqlErrorCode.NoSuchTable) throw;
  842. }
  843. }
  844. private void ProcessOutputParameters()
  845. {
  846. // if we are not 5.5 or later or we are not prepared then we are simulating output parameters
  847. // with user variables and they are also string so we have to work some magic with out
  848. // column types before we read the data
  849. if (!driver.SupportsOutputParameters || !Command.IsPrepared)
  850. AdjustOutputTypes();
  851. // now read the output parameters data row
  852. if ((CommandBehavior & CommandBehavior.SchemaOnly) != 0) return;
  853. ResultSet.NextRow(CommandBehavior);
  854. string prefix = "@" + StoredProcedure.ParameterPrefix;
  855. for (int i = 0; i < FieldCount; i++)
  856. {
  857. string fieldName = GetName(i);
  858. if (fieldName.StartsWith(prefix))
  859. fieldName = fieldName.Remove(0, prefix.Length);
  860. MySqlParameter parameter = Command.Parameters.GetParameterFlexible(fieldName, true);
  861. parameter.Value = GetValue(i);
  862. }
  863. }
  864. private void AdjustOutputTypes()
  865. {
  866. // since MySQL likes to return user variables as strings
  867. // we reset the types of the readers internal value objects
  868. // this will allow those value objects to parse the string based
  869. // return values
  870. for (int i = 0; i < FieldCount; i++)
  871. {
  872. string fieldName = GetName(i);
  873. fieldName = fieldName.Remove(0, StoredProcedure.ParameterPrefix.Length + 1);
  874. MySqlParameter parameter = Command.Parameters.GetParameterFlexible(fieldName, true);
  875. IMySqlValue v = MySqlField.GetIMySqlValue(parameter.MySqlDbType);
  876. if (v is MySqlBit)
  877. {
  878. MySqlBit bit = (MySqlBit)v;
  879. bit.ReadAsString = true;
  880. ResultSet.SetValueObject(i, bit);
  881. }
  882. else
  883. ResultSet.SetValueObject(i, v);
  884. }
  885. }
  886. #if NET40
  887. public T GetFieldValue<T>(int ordinal)
  888. {
  889. if (typeof(T).Equals(typeof(DateTimeOffset)))
  890. {
  891. var dtValue = new DateTime();
  892. var result = DateTime.TryParse(this.GetValue(ordinal).ToString(), out dtValue);
  893. DateTime datetime = result ? dtValue : DateTime.MinValue;
  894. return (T)Convert.ChangeType(new DateTimeOffset(datetime), typeof(T));
  895. }
  896. else return (T)GetValue(ordinal);
  897. }
  898. #else
  899. public override T GetFieldValue<T>(int ordinal)
  900. {
  901. if (typeof(T).Equals(typeof(DateTimeOffset)))
  902. {
  903. var dtValue = new DateTime();
  904. var result = DateTime.TryParse(this.GetValue(ordinal).ToString(), out dtValue);
  905. DateTime datetime = result ? dtValue : DateTime.MinValue;
  906. return (T)Convert.ChangeType(new DateTimeOffset(datetime), typeof(T));
  907. }
  908. else
  909. return base.GetFieldValue<T>(ordinal);
  910. }
  911. #endif
  912. private void Throw(Exception ex)
  913. {
  914. _connection?.Throw(ex);
  915. throw ex;
  916. }
  917. public new void Dispose()
  918. {
  919. Dispose(true);
  920. GC.SuppressFinalize(this);
  921. }
  922. internal new void Dispose(bool disposing)
  923. {
  924. if (disposing)
  925. {
  926. Close();
  927. }
  928. }
  929. #region Destructor
  930. ~MySqlDataReader()
  931. {
  932. Dispose(false);
  933. }
  934. #endregion
  935. }
  936. }
  937. #endif