diff --git a/src/NHibernate.Test/Async/Hql/HQLFunctions.cs b/src/NHibernate.Test/Async/Hql/HQLFunctions.cs index c7212067625..e1056cf633e 100644 --- a/src/NHibernate.Test/Async/Hql/HQLFunctions.cs +++ b/src/NHibernate.Test/Async/Hql/HQLFunctions.cs @@ -10,12 +10,16 @@ using System; using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Dialect; using NUnit.Framework; namespace NHibernate.Test.Hql { - using System.Threading.Tasks; + using System.Linq; /// /// This test run each HQL function separately so is easy to know which function need /// an override in the specific dialect implementation. @@ -1231,5 +1235,183 @@ public async Task ParameterLikeArgumentAsync() Assert.AreEqual(1, l.Count); } } + + [Test] + public async Task BitwiseAndAsync() + { + AssumeFunctionSupported("band"); + await (CreateMaterialResourcesAsync()); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State & 1) > 0"); + var result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "& 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State & 2) > 0"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "& 2"); + + query = s.CreateQuery("from MaterialResource m where (m.State & 3) > 0"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(2), "& 3"); + + await (tx.CommitAsync()); + } + } + + [Test] + public async Task BitwiseOrAsync() + { + AssumeFunctionSupported("bor"); + await (CreateMaterialResourcesAsync()); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 0"); + var result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(3), "| 1) > 0"); + + query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 1"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "| 1) > 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State | 0) > 0"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(2), "| 0) > 0"); + + await (tx.CommitAsync()); + } + } + + [Test] + public async Task BitwiseXorAsync() + { + AssumeFunctionSupported("bxor"); + await (CreateMaterialResourcesAsync()); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State ^ 1) > 0"); + var result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(2), "^ 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State ^ 2) > 0"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(2), "^ 2"); + + query = s.CreateQuery("from MaterialResource m where (m.State ^ 3) > 0"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(3), "^ 3"); + + await (tx.CommitAsync()); + } + } + + [Test] + public async Task BitwiseNotAsync() + { + AssumeFunctionSupported("bnot"); + AssumeFunctionSupported("band"); + await (CreateMaterialResourcesAsync()); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + // ! takes not precedence over & at least with some dialects (maybe all). + var query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 3"); + var result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 3"); + + query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 2"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 2"); + + query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 1"); + result = await (query.ListAsync()); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 1"); + + await (tx.CommitAsync()); + } + } + + // #1670 + [Test] + public async Task BitwiseIsThreadsafeAsync() + { + AssumeFunctionSupported("band"); + AssumeFunctionSupported("bor"); + AssumeFunctionSupported("bxor"); + AssumeFunctionSupported("bnot"); + var queries = new List> + { + new Tuple ("select count(*) from MaterialResource m where (m.State & 1) > 0", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State & 2) > 0", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State & 3) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State | 1) > 0", 3), + new Tuple ("select count(*) from MaterialResource m where (m.State | 1) > 1", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State | 0) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 1) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 2) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 3) > 0", 3), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 3", 1), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 2", 1), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 1", 1) + }; + // Do not use a ManualResetEventSlim, it does not support async and exhausts the task thread pool in the + // async counterparts of this test. SemaphoreSlim has the async support and release the thread when waiting. + var semaphore = new SemaphoreSlim(0); + var failures = new ConcurrentBag(); + + await (CreateMaterialResourcesAsync()); + + await (Task.WhenAll( + Enumerable.Range(0, queries.Count + 1 - 0).Select(async i => + { + if (i >= queries.Count) + { + // Give some time to threads for reaching the wait, having all of them ready to do the + // critical part of their job concurrently. + await (Task.Delay(100)); + semaphore.Release(queries.Count); + return; + } + + try + { + var query = queries[i]; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + await (semaphore.WaitAsync()); + var q = s.CreateQuery(query.Item1); + var result = await (q.UniqueResultAsync()); + Assert.That(result, Is.EqualTo(query.Item2), query.Item1); + await (tx.CommitAsync()); + } + } + catch (Exception e) + { + failures.Add(e); + } + }))); + + Assert.That(failures, Is.Empty, $"{failures.Count} task(s) failed."); + } + + private async Task CreateMaterialResourcesAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + await (s.SaveAsync(new MaterialResource("m1", "18", MaterialResource.MaterialState.Available) { Cost = 51.76m }, cancellationToken)); + await (s.SaveAsync(new MaterialResource("m2", "19", MaterialResource.MaterialState.Reserved) { Cost = 15.24m }, cancellationToken)); + await (s.SaveAsync(new MaterialResource("m3", "20", MaterialResource.MaterialState.Discarded) { Cost = 21.54m }, cancellationToken)); + await (tx.CommitAsync(cancellationToken)); + } + } } } diff --git a/src/NHibernate.Test/Hql/HQLFunctions.cs b/src/NHibernate.Test/Hql/HQLFunctions.cs index 2d54fc6ebaf..17a6abd9d09 100644 --- a/src/NHibernate.Test/Hql/HQLFunctions.cs +++ b/src/NHibernate.Test/Hql/HQLFunctions.cs @@ -1,5 +1,9 @@ using System; using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Dialect; using NUnit.Framework; @@ -1220,7 +1224,187 @@ public void ParameterLikeArgument() Assert.AreEqual(1, l.Count); } } + + [Test] + public void BitwiseAnd() + { + AssumeFunctionSupported("band"); + CreateMaterialResources(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State & 1) > 0"); + var result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "& 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State & 2) > 0"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "& 2"); + + query = s.CreateQuery("from MaterialResource m where (m.State & 3) > 0"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(2), "& 3"); + + tx.Commit(); + } + } + + [Test] + public void BitwiseOr() + { + AssumeFunctionSupported("bor"); + CreateMaterialResources(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 0"); + var result = query.List(); + Assert.That(result, Has.Count.EqualTo(3), "| 1) > 0"); + + query = s.CreateQuery("from MaterialResource m where (m.State | 1) > 1"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "| 1) > 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State | 0) > 0"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(2), "| 0) > 0"); + + tx.Commit(); + } + } + + [Test] + public void BitwiseXor() + { + AssumeFunctionSupported("bxor"); + CreateMaterialResources(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + var query = s.CreateQuery("from MaterialResource m where (m.State ^ 1) > 0"); + var result = query.List(); + Assert.That(result, Has.Count.EqualTo(2), "^ 1"); + + query = s.CreateQuery("from MaterialResource m where (m.State ^ 2) > 0"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(2), "^ 2"); + + query = s.CreateQuery("from MaterialResource m where (m.State ^ 3) > 0"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(3), "^ 3"); + + tx.Commit(); + } + } + + [Test] + public void BitwiseNot() + { + AssumeFunctionSupported("bnot"); + AssumeFunctionSupported("band"); + CreateMaterialResources(); + + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + // ! takes not precedence over & at least with some dialects (maybe all). + var query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 3"); + var result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 3"); + + query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 2"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 2"); + + query = s.CreateQuery("from MaterialResource m where ((!m.State) & 3) = 1"); + result = query.List(); + Assert.That(result, Has.Count.EqualTo(1), "((!m.State) & 3) = 1"); + + tx.Commit(); + } + } + + // #1670 + [Test] + public void BitwiseIsThreadsafe() + { + AssumeFunctionSupported("band"); + AssumeFunctionSupported("bor"); + AssumeFunctionSupported("bxor"); + AssumeFunctionSupported("bnot"); + var queries = new List> + { + new Tuple ("select count(*) from MaterialResource m where (m.State & 1) > 0", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State & 2) > 0", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State & 3) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State | 1) > 0", 3), + new Tuple ("select count(*) from MaterialResource m where (m.State | 1) > 1", 1), + new Tuple ("select count(*) from MaterialResource m where (m.State | 0) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 1) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 2) > 0", 2), + new Tuple ("select count(*) from MaterialResource m where (m.State ^ 3) > 0", 3), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 3", 1), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 2", 1), + new Tuple ("select count(*) from MaterialResource m where ((!m.State) & 3) = 1", 1) + }; + // Do not use a ManualResetEventSlim, it does not support async and exhausts the task thread pool in the + // async counterparts of this test. SemaphoreSlim has the async support and release the thread when waiting. + var semaphore = new SemaphoreSlim(0); + var failures = new ConcurrentBag(); + + CreateMaterialResources(); + + Parallel.For( + 0, queries.Count + 1, + i => + { + if (i >= queries.Count) + { + // Give some time to threads for reaching the wait, having all of them ready to do the + // critical part of their job concurrently. + Thread.Sleep(100); + semaphore.Release(queries.Count); + return; + } + + try + { + var query = queries[i]; + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + semaphore.Wait(); + var q = s.CreateQuery(query.Item1); + var result = q.UniqueResult(); + Assert.That(result, Is.EqualTo(query.Item2), query.Item1); + tx.Commit(); + } + } + catch (Exception e) + { + failures.Add(e); + } + }); + + Assert.That(failures, Is.Empty, $"{failures.Count} task(s) failed."); + } + + private void CreateMaterialResources() + { + using (var s = OpenSession()) + using (var tx = s.BeginTransaction()) + { + s.Save(new MaterialResource("m1", "18", MaterialResource.MaterialState.Available) { Cost = 51.76m }); + s.Save(new MaterialResource("m2", "19", MaterialResource.MaterialState.Reserved) { Cost = 15.24m }); + s.Save(new MaterialResource("m3", "20", MaterialResource.MaterialState.Discarded) { Cost = 21.54m }); + tx.Commit(); + } + } } + public class ForNh1725 { public string Description { get; set; } diff --git a/src/NHibernate.Test/TestCase.cs b/src/NHibernate.Test/TestCase.cs index 13f82daf61e..3a6bab6e1d0 100644 --- a/src/NHibernate.Test/TestCase.cs +++ b/src/NHibernate.Test/TestCase.cs @@ -447,6 +447,19 @@ protected DateTime RoundForDialect(DateTime value) {"locate", new HashSet {typeof (SQLiteDialect)}}, {"bit_length", new HashSet {typeof (SQLiteDialect)}}, {"extract", new HashSet {typeof (SQLiteDialect)}}, + { + "bxor", + new HashSet + { + // Could be supported like Oracle, with a template + typeof (SQLiteDialect), + // Could be supported by overriding registration with # instead of ^ + typeof (PostgreSQLDialect), + typeof (PostgreSQL81Dialect), + typeof (PostgreSQL82Dialect), + typeof (PostgreSQL83Dialect) + } + }, { "nullif", new HashSet diff --git a/src/NHibernate/Dialect/BitwiseFunctionOperation.cs b/src/NHibernate/Dialect/BitwiseFunctionOperation.cs index 43faa74fa63..db86b2af3af 100644 --- a/src/NHibernate/Dialect/BitwiseFunctionOperation.cs +++ b/src/NHibernate/Dialect/BitwiseFunctionOperation.cs @@ -14,99 +14,63 @@ namespace NHibernate.Dialect public class BitwiseFunctionOperation : ISQLFunction { private readonly string _functionName; - private SqlStringBuilder _sqlBuffer; - private Queue _args; /// - /// Creates an instance of this class using the provided function name + /// Creates an instance of this class using the provided function name. /// /// - /// The bitwise function name as defined by the SQL-Dialect + /// The bitwise function name as defined by the SQL-Dialect. /// public BitwiseFunctionOperation(string functionName) { - _functionName = functionName; + _functionName = functionName; } #region ISQLFunction Members + /// public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int64; } - public bool HasArguments - { - get { return true; } - } + /// + public bool HasArguments => true; - public bool HasParenthesesIfNoArguments - { - get { return true; } - } + /// + public bool HasParenthesesIfNoArguments => true; + /// public SqlString Render(IList args, ISessionFactoryImplementor factory) { - Prepare(args); - - AddFunctionName(); - OpenParens(); - AddArguments(); - CloseParens(); - - return SqlResult(); - } - - #endregion + var sqlBuffer = new SqlStringBuilder(); - private void Prepare(IList args) - { - _sqlBuffer = new SqlStringBuilder(); - _args = new Queue(); + sqlBuffer.Add(_functionName); + sqlBuffer.Add("("); foreach (var arg in args) { - if (!IsParens(arg.ToString())) - _args.Enqueue(arg); - } - } - - private static bool IsParens(string candidate) - { - return candidate == "(" || candidate == ")"; - } - - private void AddFunctionName() - { - _sqlBuffer.Add(_functionName); - } - - private void OpenParens() - { - _sqlBuffer.Add("("); - } - - private void AddArguments() - { - while (_args.Count > 0) - { - var arg = _args.Dequeue(); + // The actual second argument may be surrounded by parentesis as additional arguments. + // They have to be ignored, otherwise it would emit "functionName(firstArg, (, secondArg, ))" + if (IsParens(arg.ToString())) + continue; if (arg is Parameter || arg is SqlString) - _sqlBuffer.AddObject(arg); + sqlBuffer.AddObject(arg); else - _sqlBuffer.Add(arg.ToString()); - if (_args.Count > 0) - _sqlBuffer.Add(", "); + sqlBuffer.Add(arg.ToString()); + sqlBuffer.Add(", "); } - } - private void CloseParens() - { - _sqlBuffer.Add(")"); + sqlBuffer.RemoveAt(sqlBuffer.Count - 1); + sqlBuffer.Add(")"); + + return sqlBuffer.ToSqlString(); } - private SqlString SqlResult() + #endregion + + private static bool IsParens(string candidate) { - return _sqlBuffer.ToSqlString(); + return candidate == "(" || candidate == ")"; } } } diff --git a/src/NHibernate/Dialect/BitwiseNativeOperation.cs b/src/NHibernate/Dialect/BitwiseNativeOperation.cs index 7d31ae59c64..89c11572de5 100644 --- a/src/NHibernate/Dialect/BitwiseNativeOperation.cs +++ b/src/NHibernate/Dialect/BitwiseNativeOperation.cs @@ -15,28 +15,26 @@ public class BitwiseNativeOperation : ISQLFunction { private readonly string _sqlOpToken; private readonly bool _isNot; - private Queue _args; - private SqlStringBuilder _sqlBuffer; /// - /// creates an instance using the giving token + /// Creates an instance using the giving token. /// /// - /// The operation token + /// The operation token. /// /// - /// Use this constructor only if the token DOES NOT represent a NOT-Operation + /// Use this constructor only if the token DOES NOT represent an unary operator. /// public BitwiseNativeOperation(string sqlOpToken) : this(sqlOpToken, false) { } - /// - /// creates an instance using the giving token and the flag indicating a NOT-Operation - /// - /// - /// + /// + /// Creates an instance using the giving token and the flag indicating if it is an unary operator. + /// + /// The operation token. + /// Whether the operation is unary or not. public BitwiseNativeOperation(string sqlOpToken, bool isNot) { _sqlOpToken = sqlOpToken; @@ -45,64 +43,46 @@ public BitwiseNativeOperation(string sqlOpToken, bool isNot) #region ISQLFunction Members + /// public IType ReturnType(IType columnType, IMapping mapping) { return NHibernateUtil.Int64; } - public bool HasArguments - { - get { return true; } - } + /// + public bool HasArguments => true; - public bool HasParenthesesIfNoArguments - { - get { return false; } - } + /// + public bool HasParenthesesIfNoArguments => false; + /// public SqlString Render(IList args, ISessionFactoryImplementor factory) { - Prepare(args); - if (_isNot == false) - AddFirstArgument(); - AddToken(); - AddRestOfArguments(); + if (args.Count == 0) + throw new ArgumentException("Function argument list cannot be empty", nameof(args)); - return _sqlBuffer.ToSqlString(); - } - - #endregion + var sqlBuffer = new SqlStringBuilder(); - private void Prepare(IList args) - { - _sqlBuffer = new SqlStringBuilder(); - _args = new Queue(args); - } + if (!_isNot) + AddToBuffer(args[0], sqlBuffer); - private void AddFirstArgument() - { - AddToBuffer(_args.Dequeue()); - } - - private void AddToken() - { - AddToBuffer(string.Format(" {0} ", _sqlOpToken)); - } - - private void AddRestOfArguments() - { - while (_args.Count > 0) + sqlBuffer.Add(" ").Add(_sqlOpToken).Add(" "); + for (var i = _isNot ? 0 : 1; i < args.Count; i++) { - AddToBuffer(_args.Dequeue()); + AddToBuffer(args[i], sqlBuffer); } + + return sqlBuffer.ToSqlString(); } - private void AddToBuffer(object arg) + #endregion + + private static void AddToBuffer(object arg, SqlStringBuilder buffer) { if (arg is Parameter || arg is SqlString) - _sqlBuffer.AddObject(arg); + buffer.AddObject(arg); else - _sqlBuffer.Add(arg.ToString()); + buffer.Add(arg.ToString()); } } }