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.

1020 lines
29 KiB

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