#region License
// The PostgreSQL License
//
// Copyright (C) 2015 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion
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.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using AsyncRewriter;
using JetBrains.Annotations;
using Npgsql.BackendMessages;
using Npgsql.TypeHandlers;
using Npgsql.TypeHandlers.NumericHandlers;
using NpgsqlTypes;
namespace Npgsql
{
///
/// Reads a forward-only stream of rows from a data source.
///
public partial class NpgsqlDataReader : DbDataReader
{
internal NpgsqlCommand Command { get; }
readonly NpgsqlConnector _connector;
readonly NpgsqlConnection _connection;
readonly CommandBehavior _behavior;
ReaderState State { get; set; }
///
/// Holds the list of statements being executed by this reader.
///
readonly List _statements;
///
/// The index of the current query resultset we're processing (within a multiquery)
///
int _statementIndex;
///
/// 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 NET45 || NET452 || DNX452
///
/// 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();
bool IsSequential => (_behavior & CommandBehavior.SequentialAccess) != 0;
bool IsCaching => !IsSequential;
bool IsSchemaOnly => (_behavior & CommandBehavior.SchemaOnly) != 0;
internal NpgsqlDataReader(NpgsqlCommand command, CommandBehavior behavior, List statements)
{
Command = command;
_connection = command.Connection;
_connector = _connection.Connector;
_behavior = behavior;
State = IsSchemaOnly ? ReaderState.BetweenResults : ReaderState.InResult;
if (IsCaching) {
_rowCache = new RowCache();
}
_statements = statements;
}
[RewriteAsync]
internal void Init()
{
_rowDescription = _statements[0].Description;
if (_rowDescription == null)
{
// This happens if the first (or only) statement does not return a resultset (e.g. INSERT).
// At the end of Init the reader should be positioned at the beginning of the first resultset,
// so we seek it here.
if (!NextResult())
{
// No resultsets at all
return;
}
}
// If we have any output parameters, we need to read the first data row (in non-sequential mode)
// and extract them
if (Command.Parameters.Any(p => p.IsOutputDirection))
{
PopulateOutputParameters();
}
else
{
// If the query generated an error, we want an exception thrown here (i.e. from ExecuteQuery).
// So read the first message
// Note this isn't necessary if we're in SchemaOnly mode (we don't execute the query so no error
// is possible). Also, if we have output parameters as we already read the first message above.
if (!IsSchemaOnly)
{
_pendingMessage = ReadMessage();
}
}
}
///
/// The first row in a stored procedure command that has output parameters needs to be traversed twice -
/// once for populating the output parameters and once for the actual result set traversal. So in this
/// case we can't be sequential.
///
void PopulateOutputParameters()
{
Contract.Requires(_rowDescription != null);
Contract.Requires(Command.Parameters.Any(p => p.IsOutputDirection));
while (_row == null)
{
var msg = _connector.ReadSingleMessage(DataRowLoadingMode.NonSequential);
switch (msg.Code)
{
case BackendMessageCode.DataRow:
_pendingMessage = msg;
_row = (DataRowNonSequentialMessage)msg;
break;
case BackendMessageCode.CompletedResponse:
case BackendMessageCode.EmptyQueryResponse:
_pendingMessage = msg;
return;
default:
throw new ArgumentOutOfRangeException("Unexpected message type while populating output parameter: " + msg.Code);
}
}
Contract.Assume(_rowDescription.NumFields == _row.NumColumns);
if (IsCaching) { _rowCache.Clear(); }
var pending = new Queue();
var taken = new List();
foreach (var p in Command.Parameters.Where(p => p.IsOutputDirection))
{
int idx;
if (_rowDescription.TryGetFieldIndex(p.CleanName, out idx))
{
// TODO: Provider-specific check?
p.Value = GetValue(idx);
taken.Add(idx);
}
else
{
pending.Enqueue(p);
}
}
for (var i = 0; pending.Count != 0 && i != _row.NumColumns; ++i)
{
if (!taken.Contains(i))
{
// TODO: Need to get the provider-specific value based on the out param's type
pending.Dequeue().Value = GetValue(i);
}
}
}
#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(cancellationToken).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;
switch (completed.StatementType)
{
case StatementType.Update:
case StatementType.Insert:
case StatementType.Delete:
case StatementType.Copy:
if (!_recordsAffected.HasValue) {
_recordsAffected = 0;
}
_recordsAffected += completed.Rows;
break;
}
_statements[_statementIndex].StatementType = completed.StatementType;
_statements[_statementIndex].Rows = completed.Rows;
_statements[_statementIndex].OID = completed.OID;
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(cancellationToken).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 NET45 || NET452 || DNX452
_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 (_statementIndex++; _statementIndex < _statements.Count; _statementIndex++)
{
_rowDescription = _statements[_statementIndex].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 (_statementIndex++; _statementIndex < _statements.Count; _statementIndex++)
{
_rowDescription = _statements[_statementIndex].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 => 0;
///
/// Gets a value indicating whether the data reader is closed.
///
public override bool IsClosed => State == ReaderState.Closed;
///
/// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
///
public override int RecordsAffected => _recordsAffected.HasValue ? (int)_recordsAffected.Value : -1;
///
/// Returns details about each statement that this reader will or has executed.
///
///
/// Note that some fields (i.e. rows and oid) are only populated as the reader
/// traverses the result.
///
/// For commands with multiple queries, this exposes the number of rows affected on
/// a statement-by-statement basis, unlike
/// which exposes an aggregation across all statements.
///
public IReadOnlyList Statements => _statements.AsReadOnly();
///
/// 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 (_statementIndex >= _statements.Count) {
return false;
}
while (true)
{
var msg = ReadMessage();
switch (msg.Code)
{
case BackendMessageCode.BindComplete:
case BackendMessageCode.RowDescription:
ProcessMessage(msg);
continue;
case BackendMessageCode.DataRow:
_pendingMessage = msg;
_hasRows = true;
return true;
case BackendMessageCode.CompletedResponse:
case BackendMessageCode.EmptyQueryResponse:
_pendingMessage = msg;
_hasRows = false;
return false;
case BackendMessageCode.CloseComplete:
_hasRows = false;
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
///
[PublicAPI]
public bool IsOnRow => _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 => _rowDescription?.NumFields ?? 0;
#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);
}
}
}
///
/// Releases the resources used by the NpgsqlDataReader.
///
protected override void Dispose(bool disposing)
{
Close();
}
///
/// Closes the object.
///
#if NET45 || NET452 || DNX452
public override void Close()
#else
public void Close()
#endif
{
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;
Command.State = CommandState.Idle;
ReaderClosed?.Invoke(this, EventArgs.Empty);
return;
}
if (State != ReaderState.Consumed) {
Consume();
}
Cleanup();
}
internal void Cleanup()
{
State = ReaderState.Closed;
Command.State = CommandState.Idle;
_connector.CurrentReader = null;
_connector.EndUserAction();
if ((_behavior & CommandBehavior.CloseConnection) != 0)
{
_connection.Close();
}
if (ReaderClosed != null)
{
ReaderClosed(this, EventArgs.Empty);
ReaderClosed = null;
}
}
#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(nameof(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(nameof(dataOffset), dataOffset,
$"dataOffset must be between {0} and {int.MaxValue}");
if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length))
throw new IndexOutOfRangeException($"bufferOffset must be between {0} and {(buffer.Length - 1)}");
if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset))
throw new IndexOutOfRangeException($"length must be between {0} and {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(nameof(dataOffset), dataOffset,
$"dataOffset must be between {0} and {int.MaxValue}");
if (buffer != null && (bufferOffset < 0 || bufferOffset >= buffer.Length))
throw new IndexOutOfRangeException($"bufferOffset must be between {0} and {(buffer.Length - 1)}");
if (buffer != null && (length < 0 || length > buffer.Length - bufferOffset))
throw new IndexOutOfRangeException($"length must be between {0} and {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(cancellationToken, 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] => 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
CheckResultSet();
if (String.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be empty", nameof(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) as in the pg_type table,
/// not the .NET type (see for that).
///
/// The zero-based column index.
///
public override string GetDataTypeName(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
// In AllResultTypesAreUnknown mode, the handler is UnrecognizedTypeHandler but we can still get
// the data type name from the handler that would have been used in binary for the type OID
var field = _rowDescription[ordinal];
TypeHandler binaryHandler;
return
field.Handler is UnrecognizedTypeHandler &&
field.OID != 0 &&
_connector.TypeHandlerRegistry.OIDIndex.TryGetValue(field.OID, out binaryHandler)
? binaryHandler.PgName
: field.Handler.PgName;
}
///
/// Gets the OID for the PostgreSQL type for the specified field, as it appears in the pg_type table.
///
///
/// This is a PostgreSQL-internal value that should not be relied upon and should only be used for
/// debugging purposes.
///
/// The zero-based column index.
///
public uint GetDataTypeOID(int ordinal)
{
CheckResultSet();
CheckOrdinal(ordinal);
Contract.EndContractBlock();
return _rowDescription[ordinal].OID;
}
///
/// 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 type = Command.ObjectResultTypes?[ordinal];
if (type != null)
{
return type;
}
var field = _rowDescription[ordinal];
return field.Handler.GetFieldType(field);
}
///
/// 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);
CachedValue