diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH3565/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH3565/FixtureByCode.cs new file mode 100644 index 00000000000..07047559dcc --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH3565/FixtureByCode.cs @@ -0,0 +1,121 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Data; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlTypes; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.NH3565 +{ + using System.Threading.Tasks; + [TestFixture] + public class ByCodeFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => + { + m.Type(NHibernateUtil.AnsiString); + m.Length(10); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return base.AppliesTo(dialect) + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + && Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task ParameterTypeForLikeIsProperlyDetectedAsync() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob") + select e; + + Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + + [KnownBug("Not fixed yet")] + [Test] + public async Task ParameterTypeForContainsIsProperlyDetectedAsync() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where e.Name.Contains("Bob") + select e; + + Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + + [KnownBug("Not fixed yet")] + [Test] + public async Task ParameterTypeForStartsWithIsProperlyDetectedAsync() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where e.Name.StartsWith("Bob") + select e; + + Assert.That(await (result.ToListAsync()), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3565/Entity.cs b/src/NHibernate.Test/NHSpecificTest/NH3565/Entity.cs new file mode 100644 index 00000000000..4cc0a2c4513 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3565/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.NH3565 +{ + class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3565/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/NH3565/FixtureByCode.cs new file mode 100644 index 00000000000..f683293d591 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3565/FixtureByCode.cs @@ -0,0 +1,109 @@ +using System.Data; +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NHibernate.SqlTypes; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH3565 +{ + [TestFixture] + public class ByCodeFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => + { + m.Type(NHibernateUtil.AnsiString); + m.Length(10); + }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return base.AppliesTo(dialect) + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + && Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void ParameterTypeForLikeIsProperlyDetected() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where NHibernate.Linq.SqlMethods.Like(e.Name, "Bob") + select e; + + Assert.That(result.ToList(), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + + [KnownBug("Not fixed yet")] + [Test] + public void ParameterTypeForContainsIsProperlyDetected() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where e.Name.Contains("Bob") + select e; + + Assert.That(result.ToList(), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + + [KnownBug("Not fixed yet")] + [Test] + public void ParameterTypeForStartsWithIsProperlyDetected() + { + using (var logSpy = new SqlLogSpy()) + using (var session = OpenSession()) + { + var result = from e in session.Query() + where e.Name.StartsWith("Bob") + select e; + + Assert.That(result.ToList(), Has.Count.EqualTo(1)); + Assert.That(logSpy.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } + } +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs index c48fd3aa248..93a62499634 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/CaseNode.cs @@ -29,17 +29,38 @@ public override IType DataType if (ExpectedType != null) return ExpectedType; - foreach (var node in GetResultNodes()) + if (base.DataType != null) + return base.DataType; + + var dataType = GetTypeFromResultNodes(); + + foreach (var node in GetResultNodes().OfType()) { - if (node is ISelectExpression select && !(node is ParameterNode)) - return select.DataType; + if (node.DataType == null && node is IExpectedTypeAwareNode typeAwareNode) + { + typeAwareNode.ExpectedType = dataType; + } } - throw new HibernateException("Unable to determine data type of CASE statement."); + base.DataType = dataType; + return dataType; } set { base.DataType = value; } } + private IType GetTypeFromResultNodes() + { + foreach (var node in GetResultNodes()) + { + if (node is ISelectExpression select && select.DataType != null) + { + return select.DataType; + } + } + + throw new HibernateException("Unable to determine data type of CASE statement."); + } + public IEnumerable GetResultNodes() { for (int i = 0; i < ChildCount; i++) diff --git a/src/NHibernate/Linq/DefaultQueryProvider.cs b/src/NHibernate/Linq/DefaultQueryProvider.cs index 912b640a951..681d34105cc 100644 --- a/src/NHibernate/Linq/DefaultQueryProvider.cs +++ b/src/NHibernate/Linq/DefaultQueryProvider.cs @@ -265,7 +265,11 @@ private static void SetParameters(IQuery query, IDictionary constantExpressions, ConstantTypeLocatorVisitor visitor, NamedParameter namedParameter, - out bool tryProcessInHql) + out bool isGuessedType) { - tryProcessInHql = false; + isGuessedType = false; // All constant expressions have the same type/value var constantExpression = constantExpressions.First(); var constantType = constantExpression.Type.UnwrapIfNullable(); @@ -159,10 +159,7 @@ private static IType GetParameterType( return candidateType; } - if (visitor.NotGuessableConstants.Contains(constantExpression) && constantExpression.Value != null) - { - tryProcessInHql = true; - } + isGuessedType = true; // No related MemberExpressions was found, guess the type by value or its type when null. // When a numeric parameter is compared to different columns with different types (e.g. Where(o => o.Single >= singleParam || o.Double <= singleParam)) @@ -174,13 +171,10 @@ private static IType GetParameterType( private class ConstantTypeLocatorVisitor : RelinqExpressionVisitor { - private bool _hqlGenerator; private readonly bool _removeMappedAsCalls; private readonly System.Type _targetType; private readonly IDictionary _parameters; private readonly ISessionFactoryImplementor _sessionFactory; - private readonly ILinqToHqlGeneratorsRegistry _functionRegistry; - public readonly HashSet NotGuessableConstants = new HashSet(); public readonly Dictionary ConstantExpressions = new Dictionary(); public readonly Dictionary> ParameterConstants = @@ -198,7 +192,6 @@ public ConstantTypeLocatorVisitor( _targetType = targetType; _sessionFactory = sessionFactory; _parameters = parameters; - _functionRegistry = sessionFactory.Settings.LinqToHqlGeneratorsRegistry; } protected override Expression VisitBinary(BinaryExpression node) @@ -269,16 +262,6 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return node; } - // For hql method generators we do not want to guess the parameter type here, let hql logic figure it out. - if (_functionRegistry.TryGetGenerator(node.Method, out _)) - { - var origHqlGenerator = _hqlGenerator; - _hqlGenerator = true; - var expression = base.VisitMethodCall(node); - _hqlGenerator = origHqlGenerator; - return expression; - } - return base.VisitMethodCall(node); } @@ -289,11 +272,6 @@ protected override Expression VisitConstant(ConstantExpression node) return node; } - if (_hqlGenerator) - { - NotGuessableConstants.Add(node); - } - RelatedExpressions.Add(node, new HashSet()); ConstantExpressions.Add(node, null); if (!ParameterConstants.TryGetValue(param, out var set)) diff --git a/src/NHibernate/Param/NamedParameter.cs b/src/NHibernate/Param/NamedParameter.cs index 93af89daa0d..a499c1b2514 100644 --- a/src/NHibernate/Param/NamedParameter.cs +++ b/src/NHibernate/Param/NamedParameter.cs @@ -20,7 +20,7 @@ internal NamedParameter(string name, object value, IType type, bool isCollection public string Name { get; private set; } public object Value { get; internal set; } public IType Type { get; internal set; } - internal bool IsGuessedType { get; set; } + internal bool IsGuessedType { get; set; } = true; public virtual bool IsCollection { get; }