using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AsyncRewriter;
using Npgsql.BackendMessages;
using Npgsql.TypeHandlers;
using Npgsql.TypeHandlers.NumericHandlers;
using Npgsql.Logging;
using NpgsqlTypes;
namespace Npgsql
{
///
/// Reads a forward-only stream of rows from a data source.
///
public partial class NpgsqlDataReader : DbDataReader
{
internal NpgsqlCommand Command { get; private set; }
readonly NpgsqlConnector _connector;
readonly NpgsqlConnection _connection;
readonly CommandBehavior _behavior;
ReaderState State { get; set; }
///
/// Holds the list of RowDescription messages for each of the resultsets this reader is to process.
///
List _queries;
///
/// The index of the current query resultset we're processing (within a multiquery)
///
int _queryIndex;
///
/// The RowDescription message for the current resultset being processed
///
RowDescriptionMessage _rowDescription;
DataRowMessage _row;
uint? _recordsAffected;
///
/// Indicates that at least one row has been read across all result sets
///
bool _readOneRow;
///
/// Whether the current result set has rows
///
bool? _hasRows;
///
/// If HasRows was called before any rows were read, it was forced to read messages. A pending
/// message may be stored here for processing in the next Read() or NextResult().
///
IBackendMessage _pendingMessage;
#if !DNXCORE50
///
/// If has been called, its results are cached here.
///
DataTable _cachedSchemaTable;
#endif
///
/// Is raised whenever Close() is called.
///
public event EventHandler ReaderClosed;
///
/// In non-sequential mode, contains the cached values already read from the current row
///
readonly RowCache _rowCache;
static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();
internal bool IsSequential { get { return (_behavior & CommandBehavior.SequentialAccess) != 0; } }
internal bool IsCaching { get { return !IsSequential; } }
internal bool IsSchemaOnly { get { return (_behavior & CommandBehavior.SchemaOnly) != 0; } }
internal NpgsqlDataReader(NpgsqlCommand command, CommandBehavior behavior, List queries)
{
Command = command;
_connection = command.Connection;
_connector = _connection.Connector;
_behavior = behavior;
_recordsAffected = null;
State = IsSchemaOnly ? ReaderState.BetweenResults : ReaderState.InResult;
if (IsCaching) {
_rowCache = new RowCache();
}
_queries = queries;
}
[RewriteAsync]
internal void Init()
{
_rowDescription = _queries[0].Description;
if (_rowDescription == null)
{
// The first query has not result set, seek forward to the first query that does (if any)
if (!NextResult())
{
// No resultsets at all
return;
}
}
if (Command.Parameters.Any(p => p.IsOutputDirection))
{
PopulateOutputParameters();
}
}
#region Read
///
/// Advances the reader to the next record in a result set.
///
/// true if there are more rows; otherwise false.
///
/// The default position of a data reader is before the first record. Therefore, you must call Read to begin accessing data.
///
public override bool Read()
{
return ReadInternal();
}
///
/// This is the asynchronous version of The cancellation token is currently ignored.
///
/// Ignored for now.
/// A task representing the asynchronous operation.
public override async Task ReadAsync(CancellationToken cancellationToken)
{
return await ReadInternalAsync().ConfigureAwait(false);
}
[RewriteAsync]
bool ReadInternal()
{
if (_row != null) {
_row.Consume();
_row = null;
}
switch (State)
{
case ReaderState.InResult:
break;
case ReaderState.BetweenResults:
case ReaderState.Consumed:
case ReaderState.Closed:
return false;
default:
throw new ArgumentOutOfRangeException();
}
try
{
if ((_behavior & CommandBehavior.SingleRow) != 0 && _readOneRow)
{
// TODO: See optimization proposal in #410
Consume();
return false;
}
while (true)
{
var msg = ReadMessage();
switch (ProcessMessage(msg))
{
case ReadResult.RowRead:
return true;
case ReadResult.RowNotRead:
return false;
case ReadResult.ReadAgain:
continue;
default:
throw new ArgumentOutOfRangeException();
}
}
}
catch (NpgsqlException)
{
State = ReaderState.Consumed;
throw;
}
}
ReadResult ProcessMessage(IBackendMessage msg)
{
Contract.Requires(msg != null);
switch (msg.Code)
{
case BackendMessageCode.DataRow:
Contract.Assert(_rowDescription != null);
_connector.State = ConnectorState.Fetching;
_row = (DataRowMessage)msg;
Contract.Assume(_rowDescription.NumFields == _row.NumColumns);
if (IsCaching) { _rowCache.Clear(); }
_readOneRow = true;
_hasRows = true;
return ReadResult.RowRead;
case BackendMessageCode.CompletedResponse:
var completed = (CommandCompleteMessage) msg;
if (completed.RowsAffected.HasValue)
{
_recordsAffected = !_recordsAffected.HasValue
? completed.RowsAffected
: _recordsAffected.Value + completed.RowsAffected.Value;
}
if (completed.LastInsertedOID.HasValue) {
LastInsertedOID = completed.LastInsertedOID.Value;
}
goto case BackendMessageCode.EmptyQueryResponse;
case BackendMessageCode.EmptyQueryResponse:
State = ReaderState.BetweenResults;
return ReadResult.RowNotRead;
case BackendMessageCode.ReadyForQuery:
State = ReaderState.Consumed;
return ReadResult.RowNotRead;
case BackendMessageCode.BindComplete:
case BackendMessageCode.CloseComplete:
return ReadResult.ReadAgain;
default:
throw new Exception("Received unexpected backend message of type " + msg.Code);
}
}
#endregion
#region NextResult
///
/// Advances the reader to the next result when reading the results of a batch of statements.
///
///
public override sealed bool NextResult()
{
return IsSchemaOnly ? NextResultSchemaOnly() : NextResultInternal();
}
///
/// This is the asynchronous version of NextResult.
/// The parameter is currently ignored.
///
/// Currently ignored.
/// A task representing the asynchronous operation.
public override async sealed Task NextResultAsync(CancellationToken cancellationToken)
{
return IsSchemaOnly ? NextResultSchemaOnly() : await NextResultInternalAsync().ConfigureAwait(false);
}
[RewriteAsync]
bool NextResultInternal()
{
Contract.Requires(!IsSchemaOnly);
Contract.Ensures(Command.CommandType != CommandType.StoredProcedure || Contract.Result() == false);
try
{
// If we're in the middle of a resultset, consume it
switch (State)
{
case ReaderState.InResult:
if (_row != null) {
_row.Consume();
_row = null;
}
// TODO: Duplication with SingleResult handling above
var completedMsg = SkipUntil(BackendMessageCode.CompletedResponse, BackendMessageCode.EmptyQueryResponse);
ProcessMessage(completedMsg);
break;
case ReaderState.BetweenResults:
break;
case ReaderState.Consumed:
case ReaderState.Closed:
return false;
default:
throw new ArgumentOutOfRangeException();
}
Contract.Assert(State == ReaderState.BetweenResults);
_hasRows = null;
#if !DNXCORE50
_cachedSchemaTable = null;
#endif
if ((_behavior & CommandBehavior.SingleResult) != 0)
{
if (State == ReaderState.BetweenResults) {
Consume();
}
return false;
}
// We are now at the end of the previous result set. Read up to the next result set, if any.
for (_queryIndex++; _queryIndex < _queries.Count; _queryIndex++)
{
_rowDescription = _queries[_queryIndex].Description;
if (_rowDescription != null)
{
State = ReaderState.InResult;
// Found a resultset
return true;
}
// Next query has no resultset, read and process its completion message and move on to the next
var completedMsg = SkipUntil(BackendMessageCode.CompletedResponse, BackendMessageCode.EmptyQueryResponse);
ProcessMessage(completedMsg);
}
// There are no more queries, we're done. Read to the RFQ.
ProcessMessage(SkipUntil(BackendMessageCode.ReadyForQuery));
_rowDescription = null;
return false;
}
catch (NpgsqlException)
{
State = ReaderState.Consumed;
throw;
}
}
///
/// Note that in SchemaOnly mode there are no resultsets, and we read nothing from the backend (all
/// RowDescriptions have already been processed and are available)
///
bool NextResultSchemaOnly()
{
Contract.Requires(IsSchemaOnly);
for (_queryIndex++; _queryIndex < _queries.Count; _queryIndex++)
{
_rowDescription = _queries[_queryIndex].Description;
if (_rowDescription != null)
{
// Found a resultset
return true;
}
}
return false;
}
#endregion
[RewriteAsync]
IBackendMessage ReadMessage()
{
if (_pendingMessage != null) {
var msg = _pendingMessage;
_pendingMessage = null;
return msg;
}
return _connector.ReadSingleMessage(IsSequential ? DataRowLoadingMode.Sequential : DataRowLoadingMode.NonSequential);
}
[RewriteAsync]
IBackendMessage SkipUntil(BackendMessageCode stopAt)
{
if (_pendingMessage != null)
{
if (_pendingMessage.Code == stopAt)
{
var msg = _pendingMessage;
_pendingMessage = null;
return msg;
}
_pendingMessage = null;
}
return _connector.SkipUntil(stopAt);
}
[RewriteAsync]
IBackendMessage SkipUntil(BackendMessageCode stopAt1, BackendMessageCode stopAt2)
{
if (_pendingMessage != null) {
if (_pendingMessage.Code == stopAt1 || _pendingMessage.Code == stopAt2) {
var msg = _pendingMessage;
_pendingMessage = null;
return msg;
}
_pendingMessage = null;
}
return _connector.SkipUntil(stopAt1, stopAt2);
}
///
/// Gets a value indicating the depth of nesting for the current row. Always returns zero.
///
public override Int32 Depth
{
get { return 0; }
}
///
/// Gets a value indicating whether the data reader is closed.
///
public override bool IsClosed
{
get { return State == ReaderState.Closed; }
}
///
/// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
///
public override int RecordsAffected
{
get { return _recordsAffected.HasValue ? (int)_recordsAffected.Value : -1; }
}
///
/// Returns the OID of the last inserted row.
/// If table is created without OIDs, this will always be 0.
///
public uint LastInsertedOID { get; private set; }
///
/// Gets a value that indicates whether this DbDataReader contains one or more rows.
///
public override bool HasRows
{
get
{
if (_hasRows.HasValue) {
return _hasRows.Value;
}
if (_queryIndex >= _queries.Count) {
return false;
}
while (true)
{
var msg = _connector.ReadSingleMessage(IsSequential ? DataRowLoadingMode.Sequential : DataRowLoadingMode.NonSequential);
switch (msg.Code)
{
case BackendMessageCode.RowDescription:
ProcessMessage(msg);
continue;
case BackendMessageCode.DataRow:
_pendingMessage = msg;
return true;
case BackendMessageCode.CompletedResponse:
case BackendMessageCode.EmptyQueryResponse:
_pendingMessage = msg;
return false;
case BackendMessageCode.CloseComplete:
return false;
default:
throw new ArgumentOutOfRangeException("Got unexpected message type: " + msg.Code);
}
}
}
}
///
/// Indicates whether the reader is currently positioned on a row, i.e. whether reading a
/// column is possible.
/// This property is different from in that will
/// return true even if attempting to read a column will fail, e.g. before
/// has been called
///
public bool IsOnRow { get { return _row != null; } }
///
/// Gets the name of the column, given the zero-based column ordinal.
///
/// The zero-based column ordinal.
/// The name of the specified column.
public override string GetName(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
return _rowDescription[ordinal].Name;
}
///
/// Gets the number of columns in the current row.
///
public override int FieldCount
{
get
{
// Note MSDN docs that seem to say we should case -1 in this case:
// http://msdn.microsoft.com/en-us/library/system.data.idatarecord.fieldcount(v=vs.110).aspx
// But SqlClient returns 0
return _rowDescription == null ? 0 : _rowDescription.NumFields;
}
}
#region Cleanup / Dispose
///
/// Consumes all result sets for this reader, leaving the connector ready for sending and processing further
/// queries
///
[RewriteAsync]
void Consume()
{
if (IsSchemaOnly)
{
State = ReaderState.Consumed;
return;
}
if (_row != null)
{
_row.Consume();
_row = null;
}
// Skip over the other result sets, processing only CommandCompleted for RecordsAffected
while (true)
{
var msg = SkipUntil(BackendMessageCode.CompletedResponse, BackendMessageCode.ReadyForQuery);
switch (msg.Code)
{
case BackendMessageCode.CompletedResponse:
ProcessMessage(msg);
continue;
case BackendMessageCode.ReadyForQuery:
ProcessMessage(msg);
return;
default:
throw new Exception("Unexpected message of type " + msg.Code);
}
}
}
///
/// Closes the object.
///
public override void Close()
{
if (State == ReaderState.Closed) { return; }
switch (_connector.State)
{
case ConnectorState.Broken:
case ConnectorState.Closed:
// This may have happen because an I/O error while reading a value, or some non-safe
// exception thrown from a type handler. Or if the connection was closed while the reader
// was still open
State = ReaderState.Closed;
if (ReaderClosed != null) {
ReaderClosed(this, EventArgs.Empty);
}
return;
}
if (State != ReaderState.Consumed) {
Consume();
}
if ((_behavior & CommandBehavior.CloseConnection) != 0) {
_connection.Close();
}
State = ReaderState.Closed;
Command.State = CommandState.Idle;
_connector.CurrentReader = null;
_connector.EndUserAction();
if (ReaderClosed != null) {
ReaderClosed(this, EventArgs.Empty);
ReaderClosed = null;
}
}
///
/// Special version of close used when a connection is closed with an open reader.
/// We don't want to simply close because that would block the user until the open reader
/// is consumed, potentially a long process.
///
internal async Task CloseImmediate()
{
State = ReaderState.Closed;
Command.State = CommandState.Idle;
_connector.CurrentReader = null;
if (ReaderClosed != null) {
ReaderClosed(this, EventArgs.Empty);
ReaderClosed = null;
}
if (IsSchemaOnly) {
return;
}
// TODO: Consume asynchronously?
if (State != ReaderState.Consumed)
{
if (_row != null)
{
await _row.ConsumeAsync();
_row = null;
}
await SkipUntilAsync(BackendMessageCode.ReadyForQuery);
}
_connector.EndUserAction();
}
#endregion
///
/// Returns the current row, or throws an exception if a row isn't available
///
private DataRowMessage Row
{
get
{
if (_row == null) {
throw new InvalidOperationException("Invalid attempt to read when no data is present.");
}
return _row;
}
}
#region Simple value getters
///
/// Gets the value of the specified column as a Boolean.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override bool GetBoolean(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumnWithoutCache(ordinal);
}
///
/// Gets the value of the specified column as a byte.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override byte GetByte(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumnWithoutCache(ordinal);
}
///
/// Gets the value of the specified column as a single character.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override char GetChar(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumnWithoutCache(ordinal);
}
///
/// Gets the value of the specified column as a 16-bit signed integer.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override short GetInt16(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a 32-bit signed integer.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override int GetInt32(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a 64-bit signed integer.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override long GetInt64(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a object.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override DateTime GetDateTime(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as an instance of .
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override string GetString(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a object.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override decimal GetDecimal(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a double-precision floating point number.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override double GetDouble(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a single-precision floating point number.
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override float GetFloat(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a globally-unique identifier (GUID).
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override Guid GetGuid(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Populates an array of objects with the column values of the current row.
///
/// An array of Object into which to copy the attribute columns.
/// The number of instances of in the array.
public override int GetValues(object[] values)
{
#region Contracts
if (values == null)
throw new ArgumentNullException("values");
CheckRow();
Contract.Ensures(Contract.Result() >= 0 && Contract.Result() <= values.Length);
#endregion
var count = Math.Min(FieldCount, values.Length);
for (var i = 0; i < count; i++) {
values[i] = GetValue(i);
}
return count;
}
///
/// Gets the value of the specified column as an instance of .
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override object this[int ordinal]
{
get
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return GetValue(ordinal);
}
}
#endregion
#region Provider-specific type getters
///
/// Gets the value of the specified column as an ,
/// Npgsql's provider-specific type for dates.
///
///
/// PostgreSQL's date type represents dates from 4713 BC to 5874897 AD, while .NET's DateTime
/// only supports years from 1 to 1999. If you require years outside this range use this accessor.
/// The standard method will also return this type, but has
/// the disadvantage of boxing the value.
/// See http://www.postgresql.org/docs/current/static/datatype-datetime.html
///
/// The zero-based column ordinal.
/// The value of the specified column.
public NpgsqlDate GetDate(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as a TimeSpan,
///
///
/// PostgreSQL's interval type has has a resolution of 1 microsecond and ranges from
/// -178000000 to 178000000 years, while .NET's TimeSpan has a resolution of 100 nanoseconds
/// and ranges from roughly -29247 to 29247 years.
/// See http://www.postgresql.org/docs/current/static/datatype-datetime.html
///
/// The zero-based column ordinal.
/// The value of the specified column.
public TimeSpan GetTimeSpan(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as an ,
/// Npgsql's provider-specific type for time spans.
///
///
/// PostgreSQL's interval type has has a resolution of 1 microsecond and ranges from
/// -178000000 to 178000000 years, while .NET's TimeSpan has a resolution of 100 nanoseconds
/// and ranges from roughly -29247 to 29247 years. If you require values from outside TimeSpan's
/// range use this accessor.
/// The standard ADO.NET method will also return this
/// type, but has the disadvantage of boxing the value.
/// See http://www.postgresql.org/docs/current/static/datatype-datetime.html
///
/// The zero-based column ordinal.
/// The value of the specified column.
public NpgsqlTimeSpan GetInterval(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
///
/// Gets the value of the specified column as an ,
/// Npgsql's provider-specific type for date/time timestamps. Note that this type covers
/// both PostgreSQL's "timestamp with time zone" and "timestamp without time zone" types,
/// which differ only in how they are converted upon input/output.
///
///
/// PostgreSQL's timestamp type represents dates from 4713 BC to 5874897 AD, while .NET's DateTime
/// only supports years from 1 to 1999. If you require years outside this range use this accessor.
/// The standard method will also return this type, but has
/// the disadvantage of boxing the value.
/// See http://www.postgresql.org/docs/current/static/datatype-datetime.html
///
/// The zero-based column ordinal.
/// The value of the specified column.
public NpgsqlDateTime GetTimeStamp(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
return ReadColumn(ordinal);
}
#endregion
#region Special binary getters
///
/// Reads a stream of bytes from the specified column, starting at location indicated by dataOffset, into the buffer, starting at the location indicated by bufferOffset.
///
/// The zero-based column ordinal.
/// The index within the row from which to begin the read operation.
/// The buffer into which to copy the data.
/// The index with the buffer to which the data will be copied.
/// The maximum number of characters to read.
/// The actual number of bytes read.
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
{
#region Contracts
CheckRowAndOrdinal(ordinal);
if (dataOffset < 0 || dataOffset > int.MaxValue)
throw new ArgumentOutOfRangeException("dataOffset", dataOffset, String.Format("dataOffset must be between {0} and {1}", 0, int.MaxValue));
if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length))
throw new IndexOutOfRangeException(String.Format("bufferOffset must be between {0} and {1}", 0, (buffer.Length - 1)));
if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset))
throw new IndexOutOfRangeException(String.Format("length must be between {0} and {1}", 0, buffer.Length - bufferOffset));
Contract.Ensures(Contract.Result() >= 0);
#endregion
var fieldDescription = _rowDescription[ordinal];
var handler = fieldDescription.Handler as ByteaHandler;
if (handler == null) {
throw new InvalidCastException("GetBytes() not supported for type " + fieldDescription.Name);
}
var row = Row;
row.SeekToColumn(ordinal);
row.CheckNotNull();
return handler.GetBytes(row, (int)dataOffset, buffer, bufferOffset, length, fieldDescription);
}
///
/// Retrieves data as a .
///
/// The zero-based column ordinal.
/// The returned object.
#if NET40
public Stream GetStream(int ordinal)
#else
public override Stream GetStream(int ordinal)
#endif
{
CheckRowAndOrdinal(ordinal);
Contract.Ensures(Contract.Result() != null);
var fieldDescription = _rowDescription[ordinal];
var handler = fieldDescription.Handler as ByteaHandler;
if (handler == null) {
throw new InvalidCastException("GetStream() not supported for type " + fieldDescription.Name);
}
var row = Row;
row.SeekToColumnStart(ordinal);
row.CheckNotNull();
return row.GetStream();
}
#endregion
#region Special text getters
///
/// Reads a stream of characters from the specified column, starting at location indicated by dataOffset, into the buffer, starting at the location indicated by bufferOffset.
///
/// The zero-based column ordinal.
/// The index within the row from which to begin the read operation.
/// The buffer into which to copy the data.
/// The index with the buffer to which the data will be copied.
/// The maximum number of characters to read.
/// The actual number of characters read.
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
{
#region Contracts
CheckRowAndOrdinal(ordinal);
if (dataOffset < 0 || dataOffset > int.MaxValue)
throw new ArgumentOutOfRangeException("dataOffset", dataOffset, String.Format("dataOffset must be between {0} and {1}", 0, int.MaxValue));
if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length))
throw new IndexOutOfRangeException(String.Format("bufferOffset must be between {0} and {1}", 0, (buffer.Length - 1)));
if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset))
throw new IndexOutOfRangeException(String.Format("length must be between {0} and {1}", 0, buffer.Length - bufferOffset));
Contract.Ensures(Contract.Result() >= 0);
#endregion
var fieldDescription = _rowDescription[ordinal];
var handler = fieldDescription.Handler as TextHandler;
if (handler == null) {
throw new InvalidCastException("GetChars() not supported for type " + fieldDescription.Name);
}
var row = Row;
row.SeekToColumn(ordinal);
row.CheckNotNull();
return handler.GetChars(row, (int)dataOffset, buffer, bufferOffset, length, fieldDescription);
}
///
/// Retrieves data as a .
///
/// The zero-based column ordinal.
/// The returned object.
#if NET40
public TextReader GetTextReader(int ordinal)
#else
public override TextReader GetTextReader(int ordinal)
#endif
{
CheckRowAndOrdinal(ordinal);
Contract.Ensures(Contract.Result() != null);
var fieldDescription = _rowDescription[ordinal];
var handler = fieldDescription.Handler as TextHandler;
if (handler == null)
{
throw new InvalidCastException("GetTextReader() not supported for type " + fieldDescription.Name);
}
var row = Row;
row.SeekToColumnStart(ordinal);
row.CheckNotNull();
return new StreamReader(row.GetStream());
}
#endregion
#region IsDBNull
///
/// Gets a value that indicates whether the column contains nonexistent or missing values.
///
/// The zero-based column ordinal.
/// true if the specified column is equivalent to ; otherwise false.
public override bool IsDBNull(int ordinal)
{
return IsDBNullInternal(ordinal);
}
///
/// An asynchronous version of , which gets a value that indicates whether the column contains non-existent or missing values.
/// The parameter is currently ignored.
///
/// The zero-based column to be retrieved.
/// Currently ignored.
/// true if the specified column value is equivalent to otherwise false.
public override async Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken)
{
return await IsDBNullInternalAsync(ordinal).ConfigureAwait(false);
}
[RewriteAsync]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once InconsistentNaming
bool IsDBNullInternal(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.EndContractBlock();
Row.SeekToColumn(ordinal);
return _row.IsColumnNull;
}
#endregion
///
/// Gets the value of the specified column as an instance of .
///
/// The name of the column.
/// The value of the specified column.
public override object this[string name]
{
get { return GetValue(GetOrdinal(name)); }
}
///
/// Gets the column ordinal given the name of the column.
///
/// The name of the column.
/// The zero-based column ordinal.
public override int GetOrdinal(string name)
{
#region Contracts
CheckRow();
if (String.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be empty", "name");
Contract.EndContractBlock();
#endregion
return _rowDescription.GetFieldIndex(name);
}
///
/// Gets the data type information for the specified field.
/// This will be the Postgresql type name (e.g. int4), not the .NET type ()
///
///
///
public override string GetDataTypeName(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
return _rowDescription[ordinal].Handler.PgName;
}
///
/// Gets the data type of the specified column.
///
/// The zero-based column ordinal.
/// The data type of the specified column.
public override Type GetFieldType(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
var fieldDescription = _rowDescription[ordinal];
return fieldDescription.Handler.GetFieldType(fieldDescription);
}
///
/// Returns the provider-specific field type of the specified column.
///
/// The zero-based column ordinal.
/// The Type object that describes the data type of the specified column.
public override Type GetProviderSpecificFieldType(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
var fieldDescription = _rowDescription[ordinal];
return fieldDescription.Handler.GetProviderSpecificFieldType(fieldDescription);
}
///
/// Gets the value of the specified column as an instance of .
///
/// The zero-based column ordinal.
/// The value of the specified column.
public override object GetValue(int ordinal)
{
CheckRowAndOrdinal(ordinal);
Contract.Ensures(Contract.Result