1 // ByteFX.Data data access components for .Net
2 // Copyright (C) 2002-2003 ByteFX, Inc.
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 using System.Collections;
22 namespace ByteFX.Data.MySqlClient
24 public sealed class MySqlDataReader : MarshalByRefObject, IEnumerable, IDataReader, IDisposable, IDataRecord
26 // The DataReader should always be open when returned to the user.
27 private bool isOpen = true;
29 // Keep track of the results and position
30 // within the resultset (starts prior to first record).
31 private MySqlField[] _fields;
32 private CommandBehavior commandBehavior;
33 private MySqlCommand command;
36 // private Packet rowPacket = null;
39 * Keep track of the connection in order to implement the
40 * CommandBehavior.CloseConnection flag. A null reference means
41 * normal behavior (do not automatically close).
43 private MySqlConnection connection = null;
46 * Because the user should not be able to directly create a
47 * DataReader object, the constructors are
50 internal MySqlDataReader( MySqlCommand cmd, CommandBehavior behavior)
53 connection = (MySqlConnection)command.Connection;
54 commandBehavior = behavior;
58 * METHODS / PROPERTIES FROM IDataReader.
63 * Always return a value of zero if nesting is not supported.
69 /// Gets a value indicating whether the data reader is closed.
73 get { return ! isOpen; }
83 /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
85 public int RecordsAffected
87 // RecordsAffected returns the number of rows affected in batch
88 // statments from insert/delete/update statments. This property
89 // is not completely accurate until .Close() has been called.
90 get { return command.UpdateCount; }
94 /// Gets a value indicating whether the MySqlDataReader contains one or more rows.
98 get { return hasRows; }
102 /// Closes the MySqlDataReader object.
106 // finish any current command
107 ClearCurrentResult();
108 command.ExecuteBatch(false);
109 connection.InternalConnection.Driver.ClearPeekedPacket();
111 connection.Reader = null;
112 if (0 != (commandBehavior & CommandBehavior.CloseConnection))
122 /// Gets the number of columns in the current row.
124 public int FieldCount
126 // Return the count of the number of columns, which in
127 // this case is the size of the column metadata
132 return _fields.Length;
138 /// Overloaded. Gets the value of a column in its native format.
139 /// In C#, this property is the indexer for the MySqlDataReader class.
141 public object this [ int i ]
145 return this.GetValue(i);
149 public object this [ String name ]
151 // Look up the ordinal and return
152 // the value at that position.
153 get { return this[GetOrdinal(name)]; }
156 private MySqlField GetField(int i)
158 if (i >= _fields.Length) throw new IndexOutOfRangeException();
162 #region TypeSafe Accessors
164 /// Gets the value of the specified column as a Boolean.
166 /// <param name="i"></param>
167 /// <returns></returns>
168 public bool GetBoolean(int i)
170 return Convert.ToBoolean(GetValue(i));
174 /// Gets the value of the specified column as a byte.
176 /// <param name="i"></param>
177 /// <returns></returns>
178 public byte GetByte(int i)
180 return Convert.ToByte(GetValue(i));
184 /// Reads a stream of bytes from the specified column offset into the buffer an array starting at the given buffer offset.
186 /// <param name="i"></param>
187 /// <param name="fieldOffset"></param>
188 /// <param name="buffer"></param>
189 /// <param name="bufferoffset"></param>
190 /// <param name="length"></param>
191 /// <returns></returns>
192 public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
194 if (i >= _fields.Length)
195 throw new IndexOutOfRangeException();
197 byte[] bytes = (byte[])GetValue(i);
202 /// adjust the length so we don't run off the end
203 if (bytes.Length < (fieldOffset+length))
205 length = (int)(bytes.Length - fieldOffset);
208 for (int x=0; x < length; x++)
210 buffer[bufferoffset+x] = bytes[fieldOffset+x];
217 /// Gets the value of the specified column as a single character.
219 /// <param name="i"></param>
220 /// <returns></returns>
221 public char GetChar(int i)
223 return Convert.ToChar(GetValue(i));
227 /// Reads a stream of characters from the specified column offset into the buffer as an array starting at the given buffer offset.
229 /// <param name="i"></param>
230 /// <param name="fieldOffset"></param>
231 /// <param name="buffer"></param>
232 /// <param name="bufferoffset"></param>
233 /// <param name="length"></param>
234 /// <returns></returns>
235 public long GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length)
237 if (i >= _fields.Length)
238 throw new IndexOutOfRangeException();
240 // retrieve the bytes of the column
241 long bytesize = GetBytes(i, 0, null, 0, 0);
242 byte[] bytes = new byte[bytesize];
243 GetBytes(i, 0, bytes, 0, (int)bytesize);
245 char[] chars = System.Text.Encoding.UTF8.GetChars(bytes, 0, (int)bytesize);
250 /// adjust the length so we don't run off the end
251 if (chars.Length < (fieldOffset+length))
253 length = (int)(chars.Length - fieldOffset);
256 for (int x=0; x < length; x++)
258 buffer[bufferoffset+x] = chars[fieldOffset+x];
265 /// Gets the name of the source data type.
267 /// <param name="i"></param>
268 /// <returns></returns>
269 public String GetDataTypeName(int i)
271 if (! isOpen) throw new Exception("No current query in data reader");
272 if (i >= _fields.Length) throw new IndexOutOfRangeException();
274 // return the name of the type used on the backend
275 return _fields[i].GetFieldTypeName();
279 /// Gets the value of the specified column as a DateTime object.
281 /// <param name="i"></param>
282 /// <returns></returns>
283 public DateTime GetDateTime(int i)
285 return Convert.ToDateTime(GetValue(i));
289 /// Gets the value of the specified column as a Decimal object.
291 /// <param name="i"></param>
292 /// <returns></returns>
293 public Decimal GetDecimal(int i)
295 return Convert.ToDecimal(GetValue(i));
299 /// Gets the value of the specified column as a double-precision floating point number.
301 /// <param name="i"></param>
302 /// <returns></returns>
303 public double GetDouble(int i)
305 return Convert.ToDouble(GetValue(i));
309 /// Gets the Type that is the data type of the object.
311 /// <param name="i"></param>
312 /// <returns></returns>
313 public Type GetFieldType(int i)
315 if (! isOpen) throw new Exception("No current query in data reader");
316 if (i >= _fields.Length) throw new IndexOutOfRangeException();
318 return _fields[i].GetFieldType();
322 /// Gets the value of the specified column as a single-precision floating point number.
324 /// <param name="i"></param>
325 /// <returns></returns>
326 public float GetFloat(int i)
328 return Convert.ToSingle(GetValue(i));
332 /// Gets the value of the specified column as a globally-unique identifier (GUID).
333 /// This is currently not supported.
335 /// <param name="i"></param>
336 /// <returns></returns>
337 public Guid GetGuid(int i)
340 * Force the cast to return the type. InvalidCastException
341 * should be thrown if the data is not already of the correct type.
343 // The sample does not support this method.
344 throw new NotSupportedException("GetGUID not supported.");
348 /// Gets the value of the specified column as a 16-bit signed integer.
350 /// <param name="i"></param>
351 /// <returns></returns>
352 public Int16 GetInt16(int i)
354 return Convert.ToInt16(GetValue(i));
358 /// Gets the value of the specified column as a 32-bit signed integer.
360 /// <param name="i"></param>
361 /// <returns></returns>
362 public Int32 GetInt32(int i)
364 return Convert.ToInt32(GetValue(i));
368 /// Gets the value of the specified column as a 64-bit signed integer.
370 /// <param name="i"></param>
371 /// <returns></returns>
372 public Int64 GetInt64(int i)
374 return Convert.ToInt64(GetValue(i));
378 /// Gets the name of the specified column.
380 /// <param name="i"></param>
381 /// <returns></returns>
382 public String GetName(int i)
384 return _fields[i].ColumnName;
388 /// Gets the column ordinal, given the name of the column.
390 /// <param name="name"></param>
391 /// <returns></returns>
392 public int GetOrdinal(string name)
395 throw new Exception("No current query in data reader");
397 for (int i=0; i < _fields.Length; i ++)
399 if (_fields[i].ColumnName.ToLower().Equals(name.ToLower()))
403 // Throw an exception if the ordinal cannot be found.
404 throw new IndexOutOfRangeException("Could not find specified column in results");
408 /// Returns a DataTable that describes the column metadata of the MySqlDataReader.
410 /// <returns></returns>
411 public DataTable GetSchemaTable()
413 // Only Results from SQL SELECT Queries
414 // get a DataTable for schema of the result
415 // otherwise, DataTable is null reference
416 if (_fields.Length == 0) return null;
418 DataTable dataTableSchema = new DataTable ("SchemaTable");
420 dataTableSchema.Columns.Add ("ColumnName", typeof (string));
421 dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int));
422 dataTableSchema.Columns.Add ("ColumnSize", typeof (int));
423 dataTableSchema.Columns.Add ("NumericPrecision", typeof (int));
424 dataTableSchema.Columns.Add ("NumericScale", typeof (int));
425 dataTableSchema.Columns.Add ("IsUnique", typeof (bool));
426 dataTableSchema.Columns.Add ("IsKey", typeof (bool));
427 DataColumn dc = dataTableSchema.Columns["IsKey"];
428 dc.AllowDBNull = true; // IsKey can have a DBNull
429 dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string));
430 dataTableSchema.Columns.Add ("BaseColumnName", typeof (string));
431 dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string));
432 dataTableSchema.Columns.Add ("BaseTableName", typeof (string));
433 dataTableSchema.Columns.Add ("DataType", typeof(Type));
434 dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool));
435 dataTableSchema.Columns.Add ("ProviderType", typeof (int));
436 dataTableSchema.Columns.Add ("IsAliased", typeof (bool));
437 dataTableSchema.Columns.Add ("IsExpression", typeof (bool));
438 dataTableSchema.Columns.Add ("IsIdentity", typeof (bool));
439 dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool));
440 dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool));
441 dataTableSchema.Columns.Add ("IsHidden", typeof (bool));
442 dataTableSchema.Columns.Add ("IsLong", typeof (bool));
443 dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool));
446 foreach (MySqlField f in _fields)
448 DataRow r = dataTableSchema.NewRow();
449 r["ColumnName"] = f.ColumnName;
450 r["ColumnOrdinal"] = ord++;
451 r["ColumnSize"] = f.ColumnLength;
452 int prec = f.NumericPrecision();
453 int pscale = f.NumericScale();
455 r["NumericPrecision"] = (short)prec;
457 r["NumericScale"] = (short)pscale;
458 r["DataType"] = f.GetFieldType();
459 r["ProviderType"] = (int)f.GetMySqlDbType();
460 r["IsLong"] = f.IsBlob() && f.ColumnLength > 255;
461 r["AllowDBNull"] = f.AllowsNull();
462 r["IsReadOnly"] = false;
463 r["IsRowVersion"] = false;
464 r["IsUnique"] = f.IsUnique();
465 r["IsKey"] = f.IsPrimaryKey();
466 r["IsAutoIncrement"] = f.IsAutoIncrement();
467 r["BaseSchemaName"] = null;
468 r["BaseCatalogName"] = null;
469 r["BaseTableName"] = f.TableName;
470 r["BaseColumnName"] = f.ColumnName;
472 dataTableSchema.Rows.Add( r );
475 return dataTableSchema;
479 /// Gets the value of the specified column as a string.
481 /// <param name="i"></param>
482 /// <returns></returns>
483 public String GetString(int i)
485 return GetValue(i).ToString();
489 /// Gets the value of the specified column in its native format.
491 /// <param name="i"></param>
492 /// <returns></returns>
493 public object GetValue(int i)
495 if (! isOpen) throw new Exception("No current query in data reader");
496 if (i >= _fields.Length) throw new IndexOutOfRangeException();
498 return _fields[i].GetValue();
502 /// Gets all attribute columns in the collection for the current row.
504 /// <param name="values"></param>
505 /// <returns></returns>
506 public int GetValues(object[] values)
508 for (int i=0; i < _fields.Length; i ++)
510 values[i] = GetValue(i);
518 /// Gets the value of the specified column as a 16-bit unsigned integer.
520 /// <param name="i"></param>
521 /// <returns></returns>
522 public UInt16 GetUInt16( int i )
524 return Convert.ToUInt16(GetValue(i));
528 /// Gets the value of the specified column as a 32-bit unsigned integer.
530 /// <param name="i"></param>
531 /// <returns></returns>
532 public UInt32 GetUInt32( int i )
534 return Convert.ToUInt32(GetValue(i));
538 /// Gets the value of the specified column as a 64-bit unsigned integer.
540 /// <param name="i"></param>
541 /// <returns></returns>
542 public UInt64 GetUInt64( int i )
544 return Convert.ToUInt64(GetValue(i));
550 public IDataReader GetData(int i)
553 * The sample code does not support this method. Normally,
554 * this would be used to expose nested tables and
555 * other hierarchical data.
557 throw new NotSupportedException("GetData not supported.");
561 /// Gets a value indicating whether the column contains non-existent or missing values.
563 /// <param name="i"></param>
564 /// <returns></returns>
565 public bool IsDBNull(int i)
567 return DBNull.Value == GetValue(i);
571 /// Advances the data reader to the next result, when reading the results of batch SQL statements.
573 /// <returns></returns>
574 public bool NextResult()
577 throw new MySqlException("Invalid attempt to NextResult when reader is closed.");
579 Driver driver = connection.InternalConnection.Driver;
581 ClearCurrentResult();
583 // tell our command to execute the next sql batch
584 Packet packet = command.ExecuteBatch(true);
586 // if there was no more batches, then signal done
587 if (packet == null) return false;
589 // When executing query statements, the result byte that is returned
590 // from MySql is the column count. That is why we reference the LastResult
591 // property here to dimension our field array
592 connection.SetState( ConnectionState.Fetching );
594 _fields = new MySqlField[ packet.ReadLenInteger() ];
595 for (int x=0; x < _fields.Length; x++)
597 _fields[x] = new MySqlField();
598 _fields[x].ReadSchemaInfo( packet );
601 // now take a quick peek at the next packet to see if we have rows
603 packet = driver.PeekPacket();
604 hasRows = packet.Type != PacketType.Last;
607 connection.SetState( ConnectionState.Open );
612 /// Advances the MySqlDataReader to the next record.
614 /// <returns></returns>
618 throw new MySqlException("Invalid attempt to Read when reader is closed.");
620 if (! canRead) return false;
622 Driver driver = connection.InternalConnection.Driver;
623 connection.SetState( ConnectionState.Fetching );
627 Packet rowPacket = driver.ReadPacket();
628 if (rowPacket.Type == PacketType.Last)
633 rowPacket.Position = 0;
635 for (int col=0; col < _fields.Length; col++)
637 int len = (int)rowPacket.ReadLenInteger();
638 _fields[col].SetValueData( rowPacket.GetBytes(), (int)rowPacket.Position, len, driver.Encoding );
639 rowPacket.Position += len;
644 System.Diagnostics.Trace.WriteLine("MySql error: " + ex.Message);
649 connection.SetState( ConnectionState.Open );
656 * Implementation specific methods.
658 private int _cultureAwareCompare(string strA, string strB)
660 // return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
664 #region Private Methods
666 private void ClearCurrentResult()
668 if (! canRead) return;
670 Packet packet = connection.InternalConnection.Driver.ReadPacket();
671 // clean out any current resultset
672 while (packet.Type != PacketType.Last)
673 packet = connection.InternalConnection.Driver.ReadPacket();
679 public IEnumerator GetEnumerator()
681 return new System.Data.Common.DbEnumerator(this);