From c74b7eba20caa9cd989fbb4724e54a6f0ef644e1 Mon Sep 17 00:00:00 2001 From: "Alexander I. Zaytsev" Date: Thu, 4 Oct 2012 16:42:13 +0600 Subject: [PATCH 1/3] Fix Take/Skip in linq expression for dialects which does not support variable limits (NH-3281) --- .../Linq/ParameterisedQueries.cs | 42 +++++++++---------- .../NHSpecificTest/NH2664/Fixture.cs | 4 +- src/NHibernate/Linq/NhLinqExpression.cs | 2 +- .../Visitors/ExpressionParameterVisitor.cs | 35 ++++++++++++++-- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/NHibernate.Test/Linq/ParameterisedQueries.cs b/src/NHibernate.Test/Linq/ParameterisedQueries.cs index b9251a49a53..7bab8d9fec6 100644 --- a/src/NHibernate.Test/Linq/ParameterisedQueries.cs +++ b/src/NHibernate.Test/Linq/ParameterisedQueries.cs @@ -24,8 +24,8 @@ public void Identical_Expressions_Return_The_Same_Key() Expression>> london2 = () => from c in db.Customers where c.Address.City == "London" select c; - var nhLondon1 = new NhLinqExpression(london1.Body, s.SessionFactory); - var nhLondon2 = new NhLinqExpression(london2.Body, s.SessionFactory); + var nhLondon1 = new NhLinqExpression(london1.Body, sessions); + var nhLondon2 = new NhLinqExpression(london2.Body, sessions); Assert.AreEqual(nhLondon1.Key, nhLondon2.Key); } @@ -44,8 +44,8 @@ public void Expressions_Differing_Only_By_Constants_Return_The_Same_Key() Expression>> newYork = () => from c in db.Customers where c.Address.City == "New York" select c; - var nhLondon = new NhLinqExpression(london.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(newYork.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(london.Body, sessions); + var nhNewYork = new NhLinqExpression(newYork.Body, sessions); Assert.AreEqual(nhLondon.Key, nhNewYork.Key); Assert.AreEqual(1, nhLondon.ParameterValuesByName.Count); @@ -67,8 +67,8 @@ public void Different_Where_Clauses_Return_Different_Keys() Expression>> company = () => from c in db.Customers where c.CompanyName == "Acme" select c; - var nhLondon = new NhLinqExpression(london.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(company.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(london.Body, sessions); + var nhNewYork = new NhLinqExpression(company.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -86,8 +86,8 @@ public void Different_Select_Properties_Return_Different_Keys() Expression>> title = () => from c in db.Customers select c.ContactTitle; - var nhLondon = new NhLinqExpression(customerId.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(title.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(customerId.Body, sessions); + var nhNewYork = new NhLinqExpression(title.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -105,8 +105,8 @@ public void Different_Select_Types_Return_Different_Keys() Expression> customerId = () => from c in db.Customers select c.CustomerId; - var nhLondon = new NhLinqExpression(newCustomerId.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(customerId.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(newCustomerId.Body, sessions); + var nhNewYork = new NhLinqExpression(customerId.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -124,8 +124,8 @@ public void Different_Select_Member_Initialisation_Returns_Different_Keys() Expression> customerId = () => from c in db.Customers select new { Title = c.ContactTitle, Id = c.CustomerId }; - var nhLondon = new NhLinqExpression(newCustomerId.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(customerId.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(newCustomerId.Body, sessions); + var nhNewYork = new NhLinqExpression(customerId.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -143,8 +143,8 @@ public void Different_Conditionals_Return_Different_Keys() Expression> customerId = () => from c in db.Customers select new { Desc = c.CustomerId != "1" ? "First" : "Not First" }; - var nhLondon = new NhLinqExpression(newCustomerId.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(customerId.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(newCustomerId.Body, sessions); + var nhNewYork = new NhLinqExpression(customerId.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -162,8 +162,8 @@ public void Different_Unary_Operation_Returns_Different_Keys() Expression> customerId = () => from c in db.Customers where !(c.CustomerId == "1") select c; - var nhLondon = new NhLinqExpression(newCustomerId.Body, s.SessionFactory); - var nhNewYork = new NhLinqExpression(customerId.Body, s.SessionFactory); + var nhLondon = new NhLinqExpression(newCustomerId.Body, sessions); + var nhNewYork = new NhLinqExpression(customerId.Body, sessions); Assert.AreNotEqual(nhLondon.Key, nhNewYork.Key); } @@ -177,8 +177,8 @@ public void Different_OfType_Returns_Different_Keys() Expression> ofType1 = () => (from a in session.Query().OfType() where a.Pregnant select a.Id); Expression> ofType2 = () => (from a in session.Query().OfType() where a.Pregnant select a.Id); - var nhOfType1 = new NhLinqExpression(ofType1.Body, session.SessionFactory); - var nhOfType2 = new NhLinqExpression(ofType2.Body, session.SessionFactory); + var nhOfType1 = new NhLinqExpression(ofType1.Body, sessions); + var nhOfType2 = new NhLinqExpression(ofType2.Body, sessions); Assert.AreNotEqual(nhOfType1.Key, nhOfType2.Key); } @@ -196,9 +196,9 @@ public void Different_Null_Returns_Different_Keys() Expression> null2 = () => (from a in session.Query() where a.Description == nullVariable select a); Expression> notNull = () => (from a in session.Query() where a.Description == notNullVariable select a); - var nhNull1 = new NhLinqExpression(null1.Body, session.SessionFactory); - var nhNull2 = new NhLinqExpression(null2.Body, session.SessionFactory); - var nhNotNull = new NhLinqExpression(notNull.Body, session.SessionFactory); + var nhNull1 = new NhLinqExpression(null1.Body, sessions); + var nhNull2 = new NhLinqExpression(null2.Body, sessions); + var nhNotNull = new NhLinqExpression(notNull.Body, sessions); Assert.AreNotEqual(nhNull1.Key, nhNotNull.Key); Assert.AreNotEqual(nhNull2.Key, nhNotNull.Key); diff --git a/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs index 020d6e0fba5..e8114b96d93 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH2664/Fixture.cs @@ -120,8 +120,8 @@ public void Different_Key_In_DynamicComponentDictionary_Returns_Different_Keys() Expression> key1 = () => (from a in session.Query() where a.Properties["Name"] == "val" select a); Expression> key2 = () => (from a in session.Query() where a.Properties["Description"] == "val" select a); - var nhKey1 = new NhLinqExpression(key1.Body, session.SessionFactory); - var nhKey2 = new NhLinqExpression(key2.Body, session.SessionFactory); + var nhKey1 = new NhLinqExpression(key1.Body, sessions); + var nhKey2 = new NhLinqExpression(key2.Body, sessions); Assert.AreNotEqual(nhKey1.Key, nhKey2.Key); } diff --git a/src/NHibernate/Linq/NhLinqExpression.cs b/src/NHibernate/Linq/NhLinqExpression.cs index 41e6eff2142..caf4136d9b7 100644 --- a/src/NHibernate/Linq/NhLinqExpression.cs +++ b/src/NHibernate/Linq/NhLinqExpression.cs @@ -27,7 +27,7 @@ public class NhLinqExpression : IQueryExpression private readonly Expression _expression; private readonly IDictionary _constantToParameterMap; - public NhLinqExpression(Expression expression, ISessionFactory sessionFactory) + public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessionFactory) { _expression = NhPartialEvaluatingExpressionTreeVisitor.EvaluateIndependentSubtrees(expression); _expression = NameUnNamedParameters.Visit(_expression); diff --git a/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs b/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs index 3e13118572f..7a2639698f7 100644 --- a/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs +++ b/src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; +using System.Reflection; +using NHibernate.Engine; using NHibernate.Param; using NHibernate.Type; using Remotion.Linq.Parsing; +using Remotion.Linq.Utilities; namespace NHibernate.Linq.Visitors { @@ -14,14 +18,22 @@ namespace NHibernate.Linq.Visitors public class ExpressionParameterVisitor : ExpressionTreeVisitor { private readonly Dictionary _parameters = new Dictionary(); - private readonly ISessionFactory _sessionFactory; + private readonly ISessionFactoryImplementor _sessionFactory; - public ExpressionParameterVisitor(ISessionFactory sessionFactory) + private readonly ICollection _pagingMethods = new HashSet + { + ReflectionHelper.GetMethodDefinition(() => Queryable.Skip(null, 0)), + ReflectionHelper.GetMethodDefinition(() => Queryable.Take(null, 0)), + ReflectionHelper.GetMethodDefinition(() => Enumerable.Skip(null, 0)), + ReflectionHelper.GetMethodDefinition(() => Enumerable.Take(null, 0)), + }; + + public ExpressionParameterVisitor(ISessionFactoryImplementor sessionFactory) { _sessionFactory = sessionFactory; } - public static IDictionary Visit(Expression expression, ISessionFactory sessionFactory) + public static IDictionary Visit(Expression expression, ISessionFactoryImplementor sessionFactory) { var visitor = new ExpressionParameterVisitor(sessionFactory); @@ -32,10 +44,27 @@ public static IDictionary Visit(Expression e protected override Expression VisitMethodCallExpression(MethodCallExpression expression) { + var method = expression.Method.IsGenericMethod + ? expression.Method.GetGenericMethodDefinition() + : expression.Method; + + if (_pagingMethods.Contains(method) && !_sessionFactory.Dialect.SupportsVariableLimit) + { + //TODO: find a way to make this code cleaner + var query = VisitExpression(expression.Arguments[0]); + var arg = expression.Arguments[1]; + + if (query == expression.Arguments[0]) + return expression; + + return Expression.Call(null, expression.Method, query, arg); + } + if (VisitorUtil.IsDynamicComponentDictionaryGetter(expression, _sessionFactory)) { return expression; } + return base.VisitMethodCallExpression(expression); } From 2eb473f1f4450e70e781f4c9a4c23a1bd6fe08e8 Mon Sep 17 00:00:00 2001 From: "Alexander I. Zaytsev" Date: Thu, 4 Oct 2012 22:22:01 +0600 Subject: [PATCH 2/3] Add test to ensure that Skip/Take does not over-cache queries. --- src/NHibernate.Test/Linq/PagingTests.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/NHibernate.Test/Linq/PagingTests.cs b/src/NHibernate.Test/Linq/PagingTests.cs index 8395bc08f10..4c92ca24558 100644 --- a/src/NHibernate.Test/Linq/PagingTests.cs +++ b/src/NHibernate.Test/Linq/PagingTests.cs @@ -25,6 +25,28 @@ orderby c.CustomerId Assert.AreEqual(10, query.Count); } + [Test] + public void Customers11to20And21to30ShouldNoCacheQuery() + { + var query = (from c in db.Customers + orderby c.CustomerId + select c.CustomerId).Skip(10).Take(10).ToList(); + Assert.AreEqual(query[0], "BSBEV"); + Assert.AreEqual(10, query.Count); + + query = (from c in db.Customers + orderby c.CustomerId + select c.CustomerId).Skip(20).Take(10).ToList(); + Assert.AreNotEqual(query[0], "BSBEV"); + Assert.AreEqual(10, query.Count); + + query = (from c in db.Customers + orderby c.CustomerId + select c.CustomerId).Skip(10).Take(20).ToList(); + Assert.AreEqual(query[0], "BSBEV"); + Assert.AreEqual(20, query.Count); + } + [Test] [Ignore("Multiple Takes (or Skips) not handled correctly")] public void CustomersChainedTake() From 12e700cea31dff89c5703d6ca3253fdfecd5874e Mon Sep 17 00:00:00 2001 From: Nicholas Gamble Date: Tue, 21 Jul 2015 14:39:50 +0100 Subject: [PATCH 3/3] Sequence support for Ingres9 --- src/NHibernate/Dialect/Ingres9Dialect.cs | 151 ++++++++++++++--------- 1 file changed, 93 insertions(+), 58 deletions(-) diff --git a/src/NHibernate/Dialect/Ingres9Dialect.cs b/src/NHibernate/Dialect/Ingres9Dialect.cs index b17ce9eb922..97ef50f6a04 100644 --- a/src/NHibernate/Dialect/Ingres9Dialect.cs +++ b/src/NHibernate/Dialect/Ingres9Dialect.cs @@ -2,67 +2,102 @@ namespace NHibernate.Dialect { - public class Ingres9Dialect : IngresDialect - { - /// - /// Does this Dialect have some kind of LIMIT syntax? - /// - /// - /// False, unless overridden. - /// - public override bool SupportsLimit - { - get { return true; } - } + public class Ingres9Dialect : IngresDialect + { + /// + /// Does this Dialect have some kind of LIMIT syntax? + /// + /// + /// False, unless overridden. + /// + public override bool SupportsLimit + { + get { return true; } + } - /// - /// Can parameters be used for a statement containing a LIMIT? - /// - public override bool SupportsVariableLimit - { - get { return false; } - } + /// + /// Can parameters be used for a statement containing a LIMIT? + /// + public override bool SupportsVariableLimit + { + get { return false; } + } - /// - /// Does this Dialect support an offset? - /// - public override bool SupportsLimitOffset - { - get { return true; } - } - - /// - /// Attempts to add a LIMIT clause to the given SQL SELECT. - /// Expects any database-specific offset and limit adjustments to have already been performed (ex. UseMaxForLimit, OffsetStartsAtOne). - /// - /// The to base the limit query off. - /// Offset of the first row to be returned by the query. This may be represented as a parameter, a string literal, or a null value if no limit is requested. This should have already been adjusted to account for OffsetStartsAtOne. - /// Maximum number of rows to be returned by the query. This may be represented as a parameter, a string literal, or a null value if no offset is requested. This should have already been adjusted to account for UseMaxForLimit. - /// - /// A new that contains the LIMIT clause. Returns null - /// if represents a SQL statement to which a limit clause cannot be added, - /// for example when the query string is custom SQL invoking a stored procedure. - /// - public override SqlString GetLimitString(SqlString queryString, SqlString offset, SqlString limit) - { - SqlStringBuilder pagingBuilder = new SqlStringBuilder(); - pagingBuilder.Add(queryString); + /// + /// Does this Dialect support an offset? + /// + public override bool SupportsLimitOffset + { + get { return true; } + } - if (offset != null) - { - pagingBuilder.Add(" offset "); - pagingBuilder.Add(offset); - } + /// + /// Does this dialect support sequences? + /// + public override bool SupportsSequences + { + get { return true; } + } - if (limit != null) - { - pagingBuilder.Add(" fetch "); - pagingBuilder.Add(offset != null ? "next " : "first "); - pagingBuilder.Add(limit); - pagingBuilder.Add(" rows only"); - } + /// + /// Attempts to add a LIMIT clause to the given SQL SELECT. + /// Expects any database-specific offset and limit adjustments to have already been performed (ex. UseMaxForLimit, OffsetStartsAtOne). + /// + /// The to base the limit query off. + /// Offset of the first row to be returned by the query. This may be represented as a parameter, a string literal, or a null value if no limit is requested. This should have already been adjusted to account for OffsetStartsAtOne. + /// Maximum number of rows to be returned by the query. This may be represented as a parameter, a string literal, or a null value if no offset is requested. This should have already been adjusted to account for UseMaxForLimit. + /// + /// A new that contains the LIMIT clause. Returns null + /// if represents a SQL statement to which a limit clause cannot be added, + /// for example when the query string is custom SQL invoking a stored procedure. + /// + public override SqlString GetLimitString(SqlString queryString, SqlString offset, SqlString limit) + { + SqlStringBuilder pagingBuilder = new SqlStringBuilder(); + pagingBuilder.Add(queryString); - return pagingBuilder.ToSqlString(); - } - } + if (offset != null) + { + pagingBuilder.Add(" offset "); + pagingBuilder.Add(offset); + } + + if (limit != null) + { + pagingBuilder.Add(" fetch "); + pagingBuilder.Add(offset != null ? "next " : "first "); + pagingBuilder.Add(limit); + pagingBuilder.Add(" rows only"); + } + + return pagingBuilder.ToSqlString(); + } + + /// + /// Generate the appropriate select statement to to retreive the next value + /// of a sequence. + /// + /// the name of the sequence + /// String The "nextval" select string. + /// This should be a "stand alone" select statement. + public override string GetSequenceNextValString(string sequenceName) + { + return "select " + GetSelectSequenceNextValString(sequenceName) + " as seq"; + } + + /// + /// Generate the select expression fragment that will retrieve the next + /// value of a sequence as part of another (typically DML) statement. + /// + /// the name of the sequence + /// The "nextval" fragment. + /// + /// This differs from in that this + /// should return an expression usable within another statement. + /// + public override string GetSelectSequenceNextValString(string sequenceName) + { + return "next value for " + sequenceName; + } + } } \ No newline at end of file