Skip to content

NH-4062 - Oracle Unicode support dual model. #667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/NHibernate.Test/Async/Linq/ByMethod/GroupByTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -782,15 +782,13 @@ public async Task GroupByComputedValueInObjectArrayWithJoinOnIdAsync()
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
}

[Test(Description = "NH-3801")]
[Test(Description = "NH-3801, NH-4062")]
public async Task GroupByComputedValueInObjectArrayWithJoinInRightSideOfCaseAsync()
{
if (!TestDialect.SupportsComplexExpressionInGroupBy)
Assert.Ignore(Dialect.GetType().Name + " does not support complex group by expressions");
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
Assert.Ignore("SQL Server seems unable to match complex group by and select list arguments when running over ODBC.");
if (Dialect is Oracle8iDialect)
Assert.Ignore("ORA-12704: character set mismatch. Due to NHibernate creating Unicode string types as NVarchar2 but querying them as Varchar2.");

var orderGroups = await (db.OrderLines.GroupBy(o => new[] { o.Order.Customer.CustomerId == null ? "unknown" : o.Order.Customer.CompanyName }).Select(g => new { Key = g.Key, Count = g.Count() }).ToListAsync());
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
Expand Down
57 changes: 56 additions & 1 deletion src/NHibernate.Test/DialectTest/Oracle8iDialectFixture.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using NHibernate.Dialect;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.SqlCommand;
using NHibernate.Util;
using NUnit.Framework;

namespace NHibernate.Test.DialectTest
Expand Down Expand Up @@ -50,6 +52,7 @@ public void GetLimitStringWithTableNotStartingWithSelectKeywordAndDifferentCasin
Assert.AreEqual(limited, expected);
}
#endregion

#region Offset And List
[Test]
public void GetLimitStringWithOffsetAndLimitAndTableStartingWithSelectKeyword()
Expand Down Expand Up @@ -93,5 +96,57 @@ public void GetLimitStringWithOffsetAndLimitAndTableNotStartingWithSelectKeyword
Assert.AreEqual(limited, expected);
}
#endregion

#region Types

[Test]
public void CheckUnicode()
{
var dialect = new Oracle8iDialect();
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
dialect.Configure(cfg.Properties);
var useNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, cfg.Properties, false);

Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.EqualTo(useNPrefixedTypesForUnicode),
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");

var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
Assert.That(unicodeStringType, (useNPrefixedTypesForUnicode ? Does.StartWith("N") : Does.Not.StartWith("N")).IgnoreCase,
"Unexpected type name for an Unicode string");
}

[Test]
public void CheckUnicodeNoPrefix()
{
var dialect = new Oracle8iDialect();

var cfg = TestConfigurationHelper.GetDefaultConfiguration();
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "false");
dialect.Configure(cfg.Properties);

Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.False,
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");

var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
Assert.That(unicodeStringType, Does.Not.StartWith("N").IgnoreCase, "Unexpected type name for an Unicode string");
}

[Test]
public void CheckUnicodeWithPrefix()
{
var dialect = new Oracle8iDialect();

var cfg = TestConfigurationHelper.GetDefaultConfiguration();
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "true");
dialect.Configure(cfg.Properties);

Assert.That(dialect.UseNPrefixedTypesForUnicode, Is.True,
$"Unexpected value for {nameof(Oracle8iDialect)}.{nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} after configuration");

var unicodeStringType = dialect.GetTypeName(NHibernateUtil.String.SqlType);
Assert.That(unicodeStringType, Does.StartWith("N").IgnoreCase, "Unexpected type name for an Unicode string");
}

#endregion
}
}
131 changes: 124 additions & 7 deletions src/NHibernate.Test/DriverTest/OracleDataClientDriverFixture.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.SqlCommand;
using NHibernate.SqlTypes;
using NHibernate.Util;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;

namespace NHibernate.Test.DriverTest
{
Expand All @@ -17,21 +21,134 @@ public class OracleDataClientDriverFixture
/// </summary>
[Test]
[Category("ODP.NET")]
[Explicit]
public void NoBooleanParameters()
[Theory]
public void NoBooleanParameters(bool managed)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted for it to no more fail in case the driver could not be loaded.

{
OracleDataClientDriver driver = new OracleDataClientDriver();
var driver = GetDriver(managed, TestConfigurationHelper.GetDefaultConfiguration().Properties);
var param = GetParameterForType(driver, SqlTypeFactory.Boolean);

SqlStringBuilder builder = new SqlStringBuilder();
Assert.That(param.DbType, Is.Not.EqualTo(DbType.Boolean), "should not still be a DbType.Boolean");
}

[Test]
[Category("ODP.NET")]
[Theory]
public void UnicodeParameters(bool managed)
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
var driver = GetDriver(managed, cfg.Properties);
var useNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, cfg.Properties, false);

Assert.That(driver.UseNPrefixedTypesForUnicode, Is.EqualTo(useNPrefixedTypesForUnicode),
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");

var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
var oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo(useNPrefixedTypesForUnicode ? "NVarchar2" : "Varchar2").IgnoreCase,
"Unexpected Unicode string parameter type");

param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo(useNPrefixedTypesForUnicode ? "NChar" : "Char").IgnoreCase,
"Unexpected Unicode string fixed length parameter type");
}

[Test]
[Category("ODP.NET")]
[Theory]
public void UnicodeParametersNoPrefix(bool managed)
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "false");
var driver = GetDriver(managed, cfg.Properties);

Assert.That(driver.UseNPrefixedTypesForUnicode, Is.False,
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");

var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
var oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo("Varchar2").IgnoreCase, "Unexpected Unicode string parameter type");

param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo("Char").IgnoreCase, "Unexpected Unicode string fixed length parameter type");
}

[Test]
[Category("ODP.NET")]
[Theory]
public void UnicodeParametersWithPrefix(bool managed)
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
cfg.SetProperty(Environment.OracleUseNPrefixedTypesForUnicode, "true");
var driver = GetDriver(managed, cfg.Properties);

Assert.That(driver.UseNPrefixedTypesForUnicode, Is.True,
$"Unexpected value for {nameof(OracleDataClientDriverBase)}.{nameof(OracleDataClientDriverBase.UseNPrefixedTypesForUnicode)}");

var param = GetParameterForType(driver, SqlTypeFactory.GetString(200));
var oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo("NVarchar2").IgnoreCase, "Unexpected Unicode string parameter type");

param = GetParameterForType(driver, new StringFixedLengthSqlType(10));
oracleParamType = GetOracleParameterType(param);
Assert.That(oracleParamType.ToString(), Is.EqualTo("NChar").IgnoreCase, "Unexpected Unicode string fixed length parameter type");
}

[Test]
[Category("ODP.NET")]
[Theory]
public void HasSameUnicodeDefaultThanDialect(bool managed)
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
cfg.Properties.Remove(Environment.OracleUseNPrefixedTypesForUnicode);
var driver = GetDriver(managed, cfg.Properties);
var dialect = new Oracle8iDialect();
dialect.Configure(cfg.Properties);

Assert.That(driver.UseNPrefixedTypesForUnicode, Is.EqualTo(dialect.UseNPrefixedTypesForUnicode),
$"Default {nameof(Oracle8iDialect.UseNPrefixedTypesForUnicode)} values mismatch between driver and dialect");
}


private static OracleDataClientDriverBase GetDriver(bool managed, IDictionary<string, string> settings)
{
OracleDataClientDriverBase driver = null;
try
{
driver = managed
? (OracleDataClientDriverBase)new OracleManagedDataClientDriver()
: new OracleDataClientDriver();
}
catch (Exception ex)
{
Assert.Ignore("Unable to load the driver: {0}", ex);
}

driver.Configure(settings);

return driver;
}

private static DbParameter GetParameterForType(IDriver driver, SqlType paramType)
{
var builder = new SqlStringBuilder();
builder.Add("select * from table1 where col1=");
builder.Add(Parameter.Placeholder);

var cmd = driver.GenerateCommand(CommandType.Text, builder.ToSqlString(), new SqlType[] {SqlTypeFactory.Boolean});
var cmd = driver.GenerateCommand(CommandType.Text, builder.ToSqlString(), new[] { paramType });

Assert.That(cmd.Parameters, Has.Count.EqualTo(1), "Unexpected parameters count");
var param = cmd.Parameters[0];
return param;
}

Assert.AreEqual("col1", param.ParameterName, "kept same param name");
Assert.IsFalse(param.DbType == DbType.Boolean, "should not still be a DbType.Boolean");
private static object GetOracleParameterType(DbParameter dbParameter)
{
var parameterType = dbParameter.GetType();
var typeProperty = parameterType.GetProperty("OracleDbType") ??
throw new InvalidOperationException("Unable to find OracleDbType property");
return typeProperty.GetValue(dbParameter);
}
}
}
4 changes: 1 addition & 3 deletions src/NHibernate.Test/Linq/ByMethod/GroupByTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -771,15 +771,13 @@ public void GroupByComputedValueInObjectArrayWithJoinOnId()
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
}

[Test(Description = "NH-3801")]
[Test(Description = "NH-3801, NH-4062")]
Copy link
Member Author

@fredericDelaporte fredericDelaporte Aug 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If #666 is merged first, will need to re-base then re-enabled Oracle in this test, since #666 disable it.

Edit: done.

public void GroupByComputedValueInObjectArrayWithJoinInRightSideOfCase()
{
if (!TestDialect.SupportsComplexExpressionInGroupBy)
Assert.Ignore(Dialect.GetType().Name + " does not support complex group by expressions");
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
Assert.Ignore("SQL Server seems unable to match complex group by and select list arguments when running over ODBC.");
if (Dialect is Oracle8iDialect)
Assert.Ignore("ORA-12704: character set mismatch. Due to NHibernate creating Unicode string types as NVarchar2 but querying them as Varchar2.");

var orderGroups = db.OrderLines.GroupBy(o => new[] { o.Order.Customer.CustomerId == null ? "unknown" : o.Order.Customer.CompanyName }).Select(g => new { Key = g.Key, Count = g.Count() }).ToList();
Assert.AreEqual(2155, orderGroups.Sum(g => g.Count));
Expand Down
14 changes: 14 additions & 0 deletions src/NHibernate/Cfg/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ public static string Version
/// </summary>
public const string OdbcDateTimeScale = "odbc.explicit_datetime_scale";

/// <summary>
/// <para>Oracle has a dual Unicode support model.</para>
/// <para>Either the whole database use an Unicode encoding, and then all string types
/// will be Unicode. In such case, Unicode strings should be mapped to non <c>N</c> prefixed
/// types, such as <c>Varchar2</c>. This is the default.</para>
/// <para>Or <c>N</c> prefixed types such as <c>NVarchar2</c> are to be used for Unicode strings.</para>
/// </summary>
/// <remarks>
/// See https://docs.oracle.com/cd/B19306_01/server.102/b14225/ch6unicode.htm#CACHCAHF
/// https://docs.oracle.com/database/121/ODPNT/featOraCommand.htm#i1007557
/// This setting applies only to Oracle dialects and ODP.Net managed or unmanaged driver.
/// </remarks>
public const string OracleUseNPrefixedTypesForUnicode = "oracle.use_n_prefixed_types_for_unicode";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New setting just for Oracle.


private static readonly Dictionary<string, string> GlobalProperties;

private static IBytecodeProvider BytecodeProviderInstance;
Expand Down
20 changes: 15 additions & 5 deletions src/NHibernate/Dialect/Dialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public static Dialect GetDialect()
{
throw new HibernateException("The dialect was not set. Set the property 'dialect'.", e);
}
return InstantiateDialect(dialectName);
return InstantiateDialect(dialectName, Environment.Properties);
}

/// <summary>
Expand All @@ -167,7 +167,7 @@ public static Dialect GetDialect()
public static Dialect GetDialect(IDictionary<string, string> props)
{
if (props == null)
throw new ArgumentNullException("props");
throw new ArgumentNullException(nameof(props));
string dialectName;
if (props.TryGetValue(Environment.Dialect, out dialectName) == false)
throw new InvalidOperationException("Could not find the dialect in the configuration");
Expand All @@ -176,21 +176,31 @@ public static Dialect GetDialect(IDictionary<string, string> props)
return GetDialect();
}

return InstantiateDialect(dialectName);
return InstantiateDialect(dialectName, props);
}

private static Dialect InstantiateDialect(string dialectName)
private static Dialect InstantiateDialect(string dialectName, IDictionary<string, string> props)
{
try
{
return (Dialect)Environment.BytecodeProvider.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(dialectName));
var dialect = (Dialect)Environment.BytecodeProvider.ObjectsFactory.CreateInstance(ReflectHelper.ClassForName(dialectName));
dialect.Configure(props);
return dialect;
}
catch (Exception e)
{
throw new HibernateException("Could not instantiate dialect class " + dialectName, e);
}
}

/// <summary>
/// Configure the dialect.
/// </summary>
/// <param name="settings">The configuration settings.</param>
public virtual void Configure(IDictionary<string, string> settings)
{
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a way to configure the dialect.


#endregion

#region Database type mapping support
Expand Down
Loading