// Copyright (C) 2004 MySQL AB
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as published by
// the Free Software Foundation
//
// There are special exceptions to the terms and conditions of the GPL
// as it is applied to this software. View the full text of the
// exception in file EXCEPTIONS in the directory of this software
// distribution.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Data;
using System.Collections;
using MySql.Data.Types;
namespace MySql.Data.MySqlClient
{
///
/// Provides a means of reading a forward-only stream of rows from a MySQL database. This class cannot be inherited.
///
///
public sealed class MySqlDataReader : MarshalByRefObject, IEnumerable, IDataReader, IDisposable, IDataRecord
{
// The DataReader should always be open when returned to the user.
private bool isOpen = true;
// Keep track of the results and position
// within the resultset (starts prior to first record).
private MySqlField[] fields;
private CommandBehavior commandBehavior;
private MySqlCommand command;
private bool canRead;
private bool hasRows;
private CommandResult currentResult;
private int readCount;
/*
* Keep track of the connection in order to implement the
* CommandBehavior.CloseConnection flag. A null reference means
* normal behavior (do not automatically close).
*/
private MySqlConnection connection = null;
/*
* Because the user should not be able to directly create a
* DataReader object, the constructors are
* marked as internal.
*/
internal MySqlDataReader( MySqlCommand cmd, CommandBehavior behavior)
{
this.command = cmd;
connection = (MySqlConnection)command.Connection;
commandBehavior = behavior;
}
///
/// Gets a value indicating the depth of nesting for the current row. This method is not
/// supported currently and always returns 0.
///
public int Depth
{
get { return 0; }
}
internal CommandBehavior Behavior
{
get { return commandBehavior; }
}
internal CommandResult CurrentResult
{
get { return currentResult; }
}
///
/// Gets a value indicating whether the data reader is closed.
///
public bool IsClosed
{
get { return ! isOpen; }
}
void IDisposable.Dispose()
{
if (isOpen)
Close();
}
///
/// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
///
public int RecordsAffected
{
// RecordsAffected returns the number of rows affected in batch
// statments from insert/delete/update statments. This property
// is not completely accurate until .Close() has been called.
get { return command.UpdateCount; }
}
///
/// Gets a value indicating whether the MySqlDataReader contains one or more rows.
///
public bool HasRows
{
get { return hasRows; }
}
///
/// Closes the MySqlDataReader object.
///
public void Close()
{
if (! isOpen) return;
// finish any current command
if (currentResult != null)
currentResult.Consume();
connection.Reader = null;
command.Consume();
if (0 != (commandBehavior & CommandBehavior.CloseConnection))
connection.Close();
isOpen = false;
}
///
/// Gets the number of columns in the current row.
///
public int FieldCount
{
// Return the count of the number of columns, which in
// this case is the size of the column metadata
// array.
get
{
if (fields != null)
return fields.Length;
return 0;
}
}
///
/// Overloaded. Gets the value of a column in its native format.
/// In C#, this property is the indexer for the MySqlDataReader class.
///
public object this [ int i ]
{
get { return GetValue(i); }
}
///
/// Gets the value of a column in its native format.
/// [C#] In C#, this property is the indexer for the MySqlDataReader class.
///
public object this [ String name ]
{
// Look up the ordinal and return
// the value at that position.
get { return this[GetOrdinal(name)]; }
}
#region TypeSafe Accessors
///
/// Gets the value of the specified column as a Boolean.
///
///
///
public bool GetBoolean(int i)
{
return Convert.ToBoolean(GetValue(i));
}
///
/// Gets the value of the specified column as a byte.
///
///
///
public byte GetByte(int i)
{
MySqlValue v = GetFieldValue(i);
if (v is MySqlUByte)
return ((MySqlUByte)v).Value;
else
return (byte)((MySqlByte)v).Value;
}
///
/// Reads a stream of bytes from the specified column offset into the buffer an array starting at the given buffer offset.
///
/// The zero-based column ordinal.
/// The index within the field from which to begin the read operation.
/// The buffer into which to read the stream of bytes.
/// The index for buffer to begin the read operation.
/// The maximum length to copy into the buffer.
/// The actual number of bytes read.
///
public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length)
{
if (i >= fields.Length)
throw new IndexOutOfRangeException();
MySqlValue val = GetFieldValue(i);
if (! (val is MySqlBinary))
throw new MySqlException("GetBytes can only be called on binary columns");
MySqlBinary binary = (MySqlBinary)val;
if (buffer == null)
return (long)binary.Value.Length;
if (bufferIndex >= buffer.Length || bufferIndex < 0)
throw new IndexOutOfRangeException("Buffer index must be a valid index in buffer");
if (buffer.Length < (bufferIndex + length))
throw new ArgumentException( "Buffer is not large enough to hold the requested data" );
if (dataIndex < 0 ||
((ulong)dataIndex >= (ulong)binary.Value.Length && (ulong)binary.Value.Length > 0))
throw new IndexOutOfRangeException( "Data index must be a valid index in the field" );
byte[] bytes = (byte[])binary.Value;
// adjust the length so we don't run off the end
if ( (ulong)binary.Value.Length < (ulong)(dataIndex+length))
{
length = (int)((ulong)binary.Value.Length - (ulong)dataIndex);
}
Array.Copy( bytes, (int)dataIndex, buffer, (int)bufferIndex, (int)length );
return length;
}
///
/// Gets the value of the specified column as a single character.
///
///
///
public char GetChar(int i)
{
string s = GetString(i);
return s[0];
}
///
/// Reads a stream of characters from the specified column offset into the buffer as an array starting at the given buffer offset.
///
///
///
///
///
///
///
public long GetChars(int i, long fieldOffset, char[] buffer, int bufferoffset, int length)
{
if (i >= fields.Length)
throw new IndexOutOfRangeException();
string valAsString = GetString(i);
if (buffer == null) return valAsString.Length;
if (bufferoffset >= buffer.Length || bufferoffset < 0)
throw new IndexOutOfRangeException("Buffer index must be a valid index in buffer");
if (buffer.Length < (bufferoffset + length))
throw new ArgumentException( "Buffer is not large enough to hold the requested data" );
if (fieldOffset < 0 || fieldOffset >= valAsString.Length )
throw new IndexOutOfRangeException( "Field offset must be a valid index in the field" );
if (valAsString.Length < length)
length = valAsString.Length;
valAsString.CopyTo( (int)fieldOffset, buffer, bufferoffset, length );
return length;
}
///
/// Gets the name of the source data type.
///
///
///
public String GetDataTypeName(int i)
{
if (! isOpen) throw new Exception("No current query in data reader");
if (i >= fields.Length) throw new IndexOutOfRangeException();
// return the name of the type used on the backend
return currentResult[i].GetMySqlTypeName();
}
///
public MySqlDateTime GetMySqlDateTime(int index)
{
return (MySqlDateTime)GetFieldValue(index);
}
///
public DateTime GetDateTime(int index)
{
MySqlValue val = GetFieldValue(index);
if (val is MySqlDateTime)
return (val as MySqlDateTime).GetDateTime();
if (val is MySqlString)
{
MySqlDateTime d = new MySqlDateTime( MySqlDbType.Datetime );
d = d.ParseMySql( (val as MySqlString).Value, true );
return d.GetDateTime();
}
throw new NotSupportedException( "Unable to convert from type " + val.GetType().ToString() + " to DateTime" );
}
///
public Decimal GetDecimal(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlDecimal)
return ((MySqlDecimal)v).Value;
return Convert.ToDecimal(v.ValueAsObject);
}
///
public double GetDouble(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlDouble)
return ((MySqlDouble)v).Value;
return Convert.ToDouble(v.ValueAsObject);
}
///
/// Gets the Type that is the data type of the object.
///
///
///
public Type GetFieldType(int i)
{
if (! isOpen) throw new Exception("No current query in data reader");
if (i >= fields.Length) throw new IndexOutOfRangeException();
if (currentResult[i] is MySqlDateTime && !connection.Settings.AllowZeroDateTime)
return typeof(DateTime);
return currentResult[i].SystemType;
}
///
public float GetFloat(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlSingle)
return ((MySqlSingle)v).Value;
return Convert.ToSingle(v.ValueAsObject);
}
///
public Guid GetGuid(int index)
{
return new Guid( GetString(index) );
}
///
public Int16 GetInt16(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlInt16)
return ((MySqlInt16)v).Value;
return Convert.ToInt16(v.ValueAsObject);
}
///
public Int32 GetInt32(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlInt32)
return ((MySqlInt32)v).Value;
return Convert.ToInt32(v.ValueAsObject);
}
///
public Int64 GetInt64(int index)
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlInt64)
return ((MySqlInt64)v).Value;
return Convert.ToInt64(v.ValueAsObject);
}
///
/// Gets the name of the specified column.
///
///
///
public String GetName(int i)
{
return fields[i].ColumnName;
}
///
/// Gets the column ordinal, given the name of the column.
///
///
///
public int GetOrdinal(string name)
{
if (! isOpen)
throw new Exception("No current query in data reader");
for (int i=0; i < fields.Length; i ++)
{
if (fields[i].ColumnName.ToLower().Equals(name.ToLower()))
return i;
}
// Throw an exception if the ordinal cannot be found.
throw new IndexOutOfRangeException("Could not find specified column in results");
}
///
/// Returns a DataTable that describes the column metadata of the MySqlDataReader.
///
///
public DataTable GetSchemaTable()
{
// Only Results from SQL SELECT Queries
// get a DataTable for schema of the result
// otherwise, DataTable is null reference
if (fields.Length == 0) return null;
DataTable dataTableSchema = new DataTable ("SchemaTable");
dataTableSchema.Columns.Add ("ColumnName", typeof (string));
dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int));
dataTableSchema.Columns.Add ("ColumnSize", typeof (int));
dataTableSchema.Columns.Add ("NumericPrecision", typeof (int));
dataTableSchema.Columns.Add ("NumericScale", typeof (int));
dataTableSchema.Columns.Add ("IsUnique", typeof (bool));
dataTableSchema.Columns.Add ("IsKey", typeof (bool));
DataColumn dc = dataTableSchema.Columns["IsKey"];
dc.AllowDBNull = true; // IsKey can have a DBNull
dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string));
dataTableSchema.Columns.Add ("BaseColumnName", typeof (string));
dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string));
dataTableSchema.Columns.Add ("BaseTableName", typeof (string));
dataTableSchema.Columns.Add ("DataType", typeof(Type));
dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool));
dataTableSchema.Columns.Add ("ProviderType", typeof (int));
dataTableSchema.Columns.Add ("IsAliased", typeof (bool));
dataTableSchema.Columns.Add ("IsExpression", typeof (bool));
dataTableSchema.Columns.Add ("IsIdentity", typeof (bool));
dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool));
dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool));
dataTableSchema.Columns.Add ("IsHidden", typeof (bool));
dataTableSchema.Columns.Add ("IsLong", typeof (bool));
dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool));
int ord = 1;
for (int i=0; i < fields.Length; i++)
{
MySqlField f = fields[i];
DataRow r = dataTableSchema.NewRow();
r["ColumnName"] = f.ColumnName;
r["ColumnOrdinal"] = ord++;
r["ColumnSize"] = f.ColumnLength;
int prec = f.Precision;
int pscale = f.Scale;
if (prec != -1)
r["NumericPrecision"] = (short)prec;
if (pscale != -1)
r["NumericScale"] = (short)pscale;
r["DataType"] = GetFieldType(i);
r["ProviderType"] = (int)f.Type;
r["IsLong"] = f.IsBlob && f.ColumnLength > 255;
r["AllowDBNull"] = f.AllowsNull;
r["IsReadOnly"] = false;
r["IsRowVersion"] = false;
r["IsUnique"] = f.IsUnique || f.IsPrimaryKey;
r["IsKey"] = f.IsPrimaryKey;
r["IsAutoIncrement"] = f.IsAutoIncrement;
r["BaseSchemaName"] = f.DatabaseName;
r["BaseCatalogName"] = null;
r["BaseTableName"] = f.TableName;
r["BaseColumnName"] = f.ColumnName;
dataTableSchema.Rows.Add( r );
}
return dataTableSchema;
}
///
public String GetString(int index)
{
MySqlValue val = GetFieldValue(index);
MySqlString str = (val as MySqlString);
if (str != null) return str.Value;
if (val is MySqlBinary)
return (currentResult[index] as MySqlBinary).ToString( fields[index].Encoding );
return val.ToString();
}
///
public TimeSpan GetTimeSpan(int index)
{
MySqlTimeSpan ts = (MySqlTimeSpan)GetFieldValue(index);
return ts.Value;
}
///
/// Gets the value of the specified column in its native format.
///
///
///
public object GetValue(int i)
{
if (! isOpen) throw new Exception("No current query in data reader");
if (i >= fields.Length) throw new IndexOutOfRangeException();
MySqlValue val = GetFieldValue(i);
if (val.IsNull) return DBNull.Value;
// if the column is a date/time, then we return a MySqlDateTime
// so .ToString() will print '0000-00-00' correctly
if (val is MySqlDateTime)
{
if (connection.Settings.AllowZeroDateTime)
return val;
else
return (val as MySqlDateTime).GetDateTime();
}
return val.ValueAsObject;
}
///
/// Gets all attribute columns in the collection for the current row.
///
///
///
public int GetValues(object[] values)
{
if (values == null) return 0;
int numCols = Math.Min( values.Length, fields.Length );
for (int i=0; i < numCols; i ++)
values[i] = GetValue(i);
return numCols;
}
///
public UInt16 GetUInt16( int index )
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlUInt16)
return ((MySqlUInt16)v).Value;
return Convert.ToUInt16(v.ValueAsObject);
}
///
public UInt32 GetUInt32( int index )
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlUInt32)
return ((MySqlUInt32)v).Value;
return Convert.ToUInt32(v.ValueAsObject);
}
///
public UInt64 GetUInt64( int index )
{
MySqlValue v = GetFieldValue(index);
if (v is MySqlUInt64)
return ((MySqlUInt64)v).Value;
return Convert.ToUInt64(v.ValueAsObject);
}
#endregion
IDataReader IDataRecord.GetData(int i)
{
throw new NotSupportedException("GetData not supported.");
}
///
/// Gets a value indicating whether the column contains non-existent or missing values.
///
///
///
public bool IsDBNull(int i)
{
return DBNull.Value == GetValue(i);
}
///
/// Advances the data reader to the next result, when reading the results of batch SQL statements.
///
///
public bool NextResult()
{
if (! isOpen)
throw new MySqlException("Invalid attempt to NextResult when reader is closed.");
// clear any rows that have not been read from the last rowset
if (currentResult != null)
currentResult.Consume();
// tell our command to continue execution of the SQL batch until it its
// another resultset
try
{
currentResult = command.GetNextResultSet(this);
readCount = 0;
}
catch (MySqlException ex)
{
if (ex.IsFatal) connection.Close();
throw;
}
// if there was no more resultsets, then signal done
if (currentResult == null)
{
canRead = false;
return false;
}
// When executing query statements, the result byte that is returned
// from MySql is the column count. That is why we reference the LastResult
// property here to dimension our field array
connection.SetState( ConnectionState.Fetching );
// load in our field defs and set our internal variables so we know
// what we can do (canRead, hasRows)
try
{
canRead = hasRows = currentResult.Load();
fields = currentResult.Fields;
return true;
}
catch (MySqlException ex)
{
if (ex.IsFatal)
connection.Close();
else
connection.SetState( ConnectionState.Open );
throw;
}
finally
{
if (connection.State != ConnectionState.Closed && connection.State != ConnectionState.Open)
connection.SetState( ConnectionState.Open );
}
}
///
/// Advances the MySqlDataReader to the next record.
///
///
public bool Read()
{
if (! isOpen)
throw new MySqlException("Invalid attempt to Read when reader is closed.");
if (! canRead) return false;
readCount ++;
connection.SetState( ConnectionState.Fetching );
try
{
try
{
if ( (Behavior & CommandBehavior.SequentialAccess) != 0)
canRead = currentResult.ReadDataRow( false );
else
canRead = currentResult.ReadDataRow( true );
if ( ! canRead) return false;
}
catch (MySqlException ex)
{
if (ex.IsFatal) connection.Close();
throw;
}
// if we are in SingleRow mode, then set canRead to false so we'll
// fail next time.
if (Behavior == CommandBehavior.SingleRow)
canRead = false;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("MySql error: " + ex.Message);
throw;
}
finally
{
connection.SetState( ConnectionState.Open );
}
return true;
}
private MySqlValue GetFieldValue(int index)
{
if (index < 0 || index >= fields.Length)
throw new ArgumentException( "You have specified an invalid column ordinal." );
MySqlValue val = currentResult.ReadColumnValue(index);
if ( readCount == 0 )
throw new MySqlException("Invalid attempt to access a field before calling Read()");
return val;
}
/*
* Implementation specific methods.
*/
private int _cultureAwareCompare(string strA, string strB)
{
// return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
return 0;
}
#region IEnumerator
IEnumerator IEnumerable.GetEnumerator()
{
return new System.Data.Common.DbEnumerator(this);
}
#endregion
}
}