Skip to content

Commit

Permalink
nhibernateGH-3530: The Oracle driver does not support DbDataReader.Ge…
Browse files Browse the repository at this point in the history
…tChar and does not convert strings to datetime values. Add a custom OracleDbDataReader to address these issues.
  • Loading branch information
David Ellingsworth authored and David Ellingsworth committed Jun 11, 2024
1 parent 4da33b7 commit b6b55f4
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 38 deletions.
46 changes: 46 additions & 0 deletions src/NHibernate/AdoNet/OracleDbDataReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Data.Common;

namespace NHibernate.AdoNet
{
public class OracleDbDataReader : DbDataReaderWrapper
{
private readonly string _timestampFormat;

public OracleDbDataReader(DbDataReader reader, string timestampFormat)
: base(reader)
{
_timestampFormat = timestampFormat;
}

// Oracle driver does not implement GetChar
public override char GetChar(int ordinal)
{
var value = DataReader[ordinal];

return value switch
{
string { Length: > 0 } s => s[0],
_ => (char) value
};
}

public override DateTime GetDateTime(int ordinal)
{
var value = DataReader[ordinal];

if (value is string && _timestampFormat != null)
{
return ParseDate((string)value);
}

return (DateTime) value;
}

private DateTime ParseDate(string value)
{
// Need to implment rules according to https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Format-Models.html#GUID-49B32A81-0904-433E-B7FE-51606672183A
throw new NotImplementedException($"Should parse '{value}' using '{_timestampFormat}'");
}
}
}
33 changes: 24 additions & 9 deletions src/NHibernate/Async/Driver/OracleDataClientDriverBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,42 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using NHibernate.AdoNet;
using NHibernate.Engine.Query;
using NHibernate.SqlTypes;
using NHibernate.Util;

namespace NHibernate.Driver
{
using System.Threading.Tasks;
using System.Threading;
public abstract partial class OracleDataClientDriverBase : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider
{
private partial class OracleDbCommandWrapper : DbCommandWrapper
{

protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
public override Task<DbDataReader> ExecuteReaderAsync(DbCommand command, CancellationToken cancellationToken)
{
if (!SuppressDecimalInvalidCastException && _suppressDecimalInvalidCastExceptionSetter == null)
{
cancellationToken.ThrowIfCancellationRequested();
var reader = await (Command.ExecuteReaderAsync(behavior, cancellationToken)).ConfigureAwait(false);
_suppressDecimalInvalidCastExceptionSetter(reader, true);
throw new NotSupportedException("OracleDataReader.SuppressGetDecimalInvalidCastException property is supported only in ODP.NET version 19.10 or newer");
}
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<DbDataReader>(cancellationToken);
}
return InternalExecuteReaderAsync();
async Task<DbDataReader> InternalExecuteReaderAsync()
{

var reader = await (command.ExecuteReaderAsync(cancellationToken)).ConfigureAwait(false);

if (SuppressDecimalInvalidCastException)
{
_suppressDecimalInvalidCastExceptionSetter(reader, true);
}

string timestampFormat = GetDateFormat(command.Connection);

return reader;
return new OracleDbDataReader(reader, timestampFormat);
}
}
}
Expand Down
54 changes: 25 additions & 29 deletions src/NHibernate/Driver/OracleDataClientDriverBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using NHibernate.AdoNet;
using NHibernate.Engine.Query;
using NHibernate.SqlTypes;
Expand All @@ -21,24 +19,6 @@ namespace NHibernate.Driver
/// </remarks>
public abstract partial class OracleDataClientDriverBase : ReflectionBasedDriver, IEmbeddedBatcherFactoryProvider
{
private partial class OracleDbCommandWrapper : DbCommandWrapper
{
private readonly Action<object, bool> _suppressDecimalInvalidCastExceptionSetter;

public OracleDbCommandWrapper(DbCommand command, Action<object, bool> suppressDecimalInvalidCastExceptionSetter) : base(command)
{
_suppressDecimalInvalidCastExceptionSetter = suppressDecimalInvalidCastExceptionSetter;
}

protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
var reader = Command.ExecuteReader(behavior);
_suppressDecimalInvalidCastExceptionSetter(reader, true);

return reader;
}
}

private const string _commandClassName = "OracleCommand";

private static readonly SqlType _guidSqlType = new SqlType(DbType.Binary, 16);
Expand All @@ -54,6 +34,8 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
private readonly object _oracleDbTypeBinaryDouble;
private readonly object _oracleDbTypeBinaryFloat;

private readonly Func<object, object> _oracleGetSessionInfo;
private readonly Func<object, string> _oracleGetTimeStampFormat;
/// <summary>
/// Default constructor.
/// </summary>
Expand Down Expand Up @@ -89,6 +71,10 @@ private OracleDataClientDriverBase(string driverAssemblyName, string clientNames
{
_suppressDecimalInvalidCastExceptionSetter = DelegateHelper.BuildPropertySetter<bool>(oracleDataReader, "SuppressGetDecimalInvalidCastException");
}

var oracleGlobalization = ReflectHelper.TypeFromAssembly(clientNamespace + ".OracleGlobalization", driverAssemblyName, true);
_oracleGetTimeStampFormat = DelegateHelper.BuildPropertyGetter<string>(oracleGlobalization, "TimeStampFormat");
_oracleGetSessionInfo = DelegateHelper.BuildFunc<object>(TypeOfConnection, "GetSessionInfo");
}

/// <inheritdoc/>
Expand Down Expand Up @@ -221,25 +207,35 @@ protected override void OnBeforePrepare(DbCommand command)
command.Parameters.Insert(0, outCursor);
}

public override DbCommand CreateCommand()
public override DbDataReader ExecuteReader(DbCommand command)
{
var command = base.CreateCommand();
if (!SuppressDecimalInvalidCastException)
if (!SuppressDecimalInvalidCastException && _suppressDecimalInvalidCastExceptionSetter == null)
{
return command;
throw new NotSupportedException("OracleDataReader.SuppressGetDecimalInvalidCastException property is supported only in ODP.NET version 19.10 or newer");
}

if (_suppressDecimalInvalidCastExceptionSetter == null)
var reader = command.ExecuteReader();

if (SuppressDecimalInvalidCastException)
{
throw new NotSupportedException("OracleDataReader.SuppressGetDecimalInvalidCastException property is supported only in ODP.NET version 19.10 or newer");
_suppressDecimalInvalidCastExceptionSetter(reader, true);
}

return new OracleDbCommandWrapper(command, _suppressDecimalInvalidCastExceptionSetter);
string timestampFormat = GetDateFormat(command.Connection);

return new OracleDbDataReader(reader, timestampFormat);
}

public override DbCommand UnwrapDbCommand(DbCommand command)
private string GetDateFormat(DbConnection connection)
{
return command is OracleDbCommandWrapper wrapper ? wrapper.Command : command;
if (_oracleGetSessionInfo == null && _oracleGetTimeStampFormat == null)
{
return null;
}

var sessionInfo = _oracleGetSessionInfo(connection);

return _oracleGetTimeStampFormat(sessionInfo);
}

System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(OracleDataClientBatchingBatcherFactory);
Expand Down

0 comments on commit b6b55f4

Please sign in to comment.