From: <ric...@us...> - 2010-09-22 21:01:41
|
Revision: 5203 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5203&view=rev Author: ricbrown Date: 2010-09-22 21:01:34 +0000 (Wed, 22 Sep 2010) Log Message: ----------- Fix NH-2024 (Max results parameter could not provided to sub-query) Pushed QuotedAndParenthesisStringTokenizer up to Dialect base class to share with Oracle. On Oracle, fixes: NHibernate.Test.Criteria.CriteriaQueryTest.SubqueryPagination NHibernate.Test.Criteria.CriteriaQueryTest.SubqueryPaginationOnlyWithFirst NHibernate.Test.NHSpecificTest.NH2251.Fixture.MultiplePagingParametersInSingleQuery Modified Paths: -------------- trunk/nhibernate/src/NHibernate/Criterion/SubqueryExpression.cs trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs trunk/nhibernate/src/NHibernate/Dialect/MsSql2005Dialect.cs trunk/nhibernate/src/NHibernate/Dialect/Oracle8iDialect.cs trunk/nhibernate/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs trunk/nhibernate/src/NHibernate.Test/DialectTest/MsSql2005DialectFixture.cs Modified: trunk/nhibernate/src/NHibernate/Criterion/SubqueryExpression.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Criterion/SubqueryExpression.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate/Criterion/SubqueryExpression.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -68,10 +68,11 @@ if (criteriaImpl.FirstResult != 0 || criteriaImpl.MaxResults != RowSelection.NoValue) { - int maxResults = (criteriaImpl.MaxResults != RowSelection.NoValue) ? criteriaImpl.MaxResults : int.MaxValue; - int? offsetParameterIndex = criteriaQuery.CreatePagingParameter(criteriaImpl.FirstResult); + int firstResults = Loader.Loader.GetFirstRow(parameters.RowSelection); + int maxResults = Loader.Loader.GetMaxOrLimit(factory.Dialect, parameters.RowSelection); + int? offsetParameterIndex = criteriaQuery.CreatePagingParameter(firstResults); int? limitParameterIndex = criteriaQuery.CreatePagingParameter(maxResults); - sql = factory.Dialect.GetLimitString(sql, criteriaImpl.FirstResult, criteriaImpl.MaxResults, offsetParameterIndex, limitParameterIndex); + sql = factory.Dialect.GetLimitString(sql, firstResults, maxResults, offsetParameterIndex, limitParameterIndex); } if (op != null) Modified: trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate/Dialect/Dialect.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -1,10 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Text; using Iesi.Collections.Generic; - using NHibernate.Dialect.Function; using NHibernate.Dialect.Lock; using NHibernate.Dialect.Schema; @@ -1337,6 +1337,256 @@ return value ? "1" : "0"; } + protected static void ExtractColumnOrAliasNames(SqlString select, out List<SqlString> columnsOrAliases, + out Dictionary<SqlString, SqlString> aliasToColumn) + { + columnsOrAliases = new List<SqlString>(); + aliasToColumn = new Dictionary<SqlString, SqlString>(); + + IList<SqlString> tokens = new QuotedAndParenthesisStringTokenizer(select).GetTokens(); + int index = 0; + while (index < tokens.Count) + { + SqlString token = tokens[index]; + + int nextTokenIndex = index += 1; + + if (token.StartsWithCaseInsensitive("select")) + continue; + + if (token.StartsWithCaseInsensitive("distinct")) + continue; + + if (token.StartsWithCaseInsensitive(",")) + continue; + + if (token.StartsWithCaseInsensitive("from")) + break; + + // handle composite expressions like "2 * 4 as foo" + while ((nextTokenIndex < tokens.Count) + && (tokens[nextTokenIndex].StartsWithCaseInsensitive("as") == false + && tokens[nextTokenIndex].StartsWithCaseInsensitive("from") == false + && tokens[nextTokenIndex].StartsWithCaseInsensitive(",") == false)) + { + SqlString nextToken = tokens[nextTokenIndex]; + token = token.Append(nextToken); + nextTokenIndex = index += 1; + } + + // if there is no alias, the token and the alias will be the same + SqlString alias = token; + + bool isFunctionCallOrQuotedString = token.IndexOfCaseInsensitive("'") >= 0 || token.IndexOfCaseInsensitive("(") >= 0; + + // this is heuristic guess, if the expression contains ' or (, it is probably + // not appropriate to just slice parts off of it + if (isFunctionCallOrQuotedString == false) + { + // its a simple column reference, so lets set the alias to the + // column name minus the table qualifier if it exists + int dot = token.IndexOfCaseInsensitive("."); + if (dot != -1) + alias = token.Substring(dot + 1); + } + + // notice! we are checking here the existence of "as" "alias", two + // tokens from the current one + if (nextTokenIndex + 1 < tokens.Count) + { + SqlString nextToken = tokens[nextTokenIndex]; + if (nextToken.IndexOfCaseInsensitive("as") >= 0) + { + SqlString tokenAfterNext = tokens[nextTokenIndex + 1]; + alias = tokenAfterNext; + index += 2; //skip the "as" and the alias + } + } + + columnsOrAliases.Add(alias); + aliasToColumn[alias] = token; + } + } + + /// <summary> + /// This specialized string tokenizier will break a string to tokens, taking + /// into account single quotes, parenthesis and commas and [ ] + /// Notice that we aren't differenciating between [ ) and ( ] on purpose, it would complicate + /// the code and it is not legal at any rate. + /// </summary> + public class QuotedAndParenthesisStringTokenizer : IEnumerable<SqlString> + { + private readonly SqlString original; + + public QuotedAndParenthesisStringTokenizer(SqlString original) + { + this.original = original; + } + + IEnumerator<SqlString> IEnumerable<SqlString>.GetEnumerator() + { + TokenizerState state = TokenizerState.WhiteSpace; + int parenthesisCount = 0; + bool escapeQuote = false; + char quoteType = '\''; + int tokenStart = 0; + int tokenLength = 0; + string originalString = original.ToString(); + + for (int i = 0; i < originalString.Length; i++) + { + char ch = originalString[i]; + switch (state) + { + case TokenizerState.WhiteSpace: + if (ch == '\'') + { + state = TokenizerState.Quoted; + quoteType = '\''; + tokenLength += 1; + } + else if (ch == '"') + { + state = TokenizerState.Quoted; + quoteType = '"'; + tokenLength += 1; + } + else if (ch == ',') + { + yield return new SqlString(","); + tokenStart += 1; + } + else if (ch == '(' || ch == '[') + { + state = TokenizerState.InParenthesis; + tokenLength += 1; + parenthesisCount = 1; + } + else if (char.IsWhiteSpace(ch) == false) + { + state = TokenizerState.Token; + tokenLength += 1; + } + else + { + tokenStart += 1; + } + break; + case TokenizerState.Quoted: + if (escapeQuote) + { + escapeQuote = false; + tokenLength += 1; + } + // handle escaping of ' by using '' or \' + else if (ch == '\\' || (ch == quoteType && i + 1 < originalString.Length && originalString[i + 1] == quoteType)) + { + escapeQuote = true; + tokenLength += 1; + } + else if (ch == quoteType) + { + yield return original.Substring(tokenStart, tokenLength + 1); + tokenStart += tokenLength + 1; + tokenLength = 0; + state = TokenizerState.WhiteSpace; + } + else + { + tokenLength += 1; + } + break; + case TokenizerState.InParenthesis: + if (ch == ')' || ch == ']') + { + tokenLength += 1; + parenthesisCount -= 1; + if (parenthesisCount == 0) + { + yield return original.Substring(tokenStart, tokenLength); + tokenStart += tokenLength; + tokenLength = 0; + state = TokenizerState.WhiteSpace; + } + } + else if (ch == '(' || ch == '[') + { + tokenLength += 1; + parenthesisCount += 1; + } + else + { + tokenLength += 1; + } + break; + case TokenizerState.Token: + if (char.IsWhiteSpace(ch)) + { + yield return original.Substring(tokenStart, tokenLength); + tokenStart += tokenLength + 1; + tokenLength = 0; + state = TokenizerState.WhiteSpace; + } + else if (ch == ',') // stop current token, and send the , as well + { + yield return original.Substring(tokenStart, tokenLength); + yield return new SqlString(","); + tokenStart += tokenLength + 1; + tokenLength = 0; + state = TokenizerState.WhiteSpace; + } + else if (ch == '(' || ch == '[') + { + state = TokenizerState.InParenthesis; + parenthesisCount = 1; + tokenLength += 1; + } + else if (ch == '\'') + { + state = TokenizerState.Quoted; + quoteType = '\''; + tokenLength += 1; + } + else if (ch == '"') + { + state = TokenizerState.Quoted; + quoteType = '"'; + tokenLength += 1; + } + else + { + tokenLength += 1; + } + break; + default: + throw new InvalidExpressionException("Could not understand the string " + original); + } + } + if (tokenLength > 0) + { + yield return original.Substring(tokenStart, tokenLength); + } + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable<SqlString>)this).GetEnumerator(); + } + + public enum TokenizerState + { + WhiteSpace, + Quoted, + InParenthesis, + Token + } + + public IList<SqlString> GetTokens() + { + return new List<SqlString>(this); + } + } + #endregion #region limit/offset support Modified: trunk/nhibernate/src/NHibernate/Dialect/MsSql2005Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/MsSql2005Dialect.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate/Dialect/MsSql2005Dialect.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Data; using NHibernate.Mapping; @@ -153,74 +152,6 @@ throw new NotSupportedException("The query should start with 'SELECT' or 'SELECT DISTINCT'"); } - private static void ExtractColumnOrAliasNames(SqlString select, out List<SqlString> columnsOrAliases, - out Dictionary<SqlString, SqlString> aliasToColumn) - { - columnsOrAliases = new List<SqlString>(); - aliasToColumn = new Dictionary<SqlString, SqlString>(); - - IList<SqlString> tokens = new QuotedAndParenthesisStringTokenizer(select).GetTokens(); - int index = 0; - while (index < tokens.Count) - { - SqlString token = tokens[index]; - - int nextTokenIndex = index += 1; - - if (token.StartsWithCaseInsensitive("select")) - continue; - - if (token.StartsWithCaseInsensitive("distinct")) - continue; - - if (token.StartsWithCaseInsensitive(",")) - continue; - - if (token.StartsWithCaseInsensitive("from")) - break; - - // handle composite expressions like "2 * 4 as foo" - while ((nextTokenIndex < tokens.Count) && (tokens[nextTokenIndex].StartsWithCaseInsensitive("as") == false && tokens[nextTokenIndex].StartsWithCaseInsensitive(",") == false)) - { - SqlString nextToken = tokens[nextTokenIndex]; - token = token.Append(nextToken); - nextTokenIndex = index += 1; - } - - // if there is no alias, the token and the alias will be the same - SqlString alias = token; - - bool isFunctionCallOrQuotedString = token.IndexOfCaseInsensitive("'") >= 0 || token.IndexOfCaseInsensitive("(") >= 0; - - // this is heuristic guess, if the expression contains ' or (, it is probably - // not appropriate to just slice parts off of it - if (isFunctionCallOrQuotedString == false) - { - // its a simple column reference, so lets set the alias to the - // column name minus the table qualifier if it exists - int dot = token.IndexOfCaseInsensitive("."); - if (dot != -1) - alias = token.Substring(dot + 1); - } - - // notice! we are checking here the existence of "as" "alias", two - // tokens from the current one - if (nextTokenIndex + 1 < tokens.Count) - { - SqlString nextToken = tokens[nextTokenIndex]; - if (nextToken.IndexOfCaseInsensitive("as") >= 0) - { - SqlString tokenAfterNext = tokens[nextTokenIndex + 1]; - alias = tokenAfterNext; - index += 2; //skip the "as" and the alias - } - } - - columnsOrAliases.Add(alias); - aliasToColumn[alias] = token; - } - } - /// <summary> /// Indicates whether the string fragment contains matching parenthesis /// </summary> @@ -309,164 +240,5 @@ get { return false; } } - /// <summary> - /// This specialized string tokenizier will break a string to tokens, taking - /// into account single quotes, parenthesis and commas and [ ] - /// Notice that we aren't differenciating between [ ) and ( ] on purpose, it would complicate - /// the code and it is not legal at any rate. - /// </summary> - public class QuotedAndParenthesisStringTokenizer : IEnumerable<SqlString> - { - private readonly SqlString original; - - public QuotedAndParenthesisStringTokenizer(SqlString original) - { - this.original = original; - } - - IEnumerator<SqlString> IEnumerable<SqlString>.GetEnumerator() - { - TokenizerState state = TokenizerState.WhiteSpace; - int parenthesisCount = 0; - bool escapeQuote = false; - int tokenStart = 0; - int tokenLength = 0; - string originalString = original.ToString(); - - for (int i = 0; i < originalString.Length; i++) - { - char ch = originalString[i]; - switch (state) - { - case TokenizerState.WhiteSpace: - if (ch == '\'') - { - state = TokenizerState.Quoted; - tokenLength += 1; - } - else if (ch == ',') - { - yield return new SqlString(","); - //tokenLength += 1? - } - else if (ch == '(' || ch == '[') - { - state = TokenizerState.InParenthesis; - tokenLength += 1; - parenthesisCount = 1; - } - else if (char.IsWhiteSpace(ch) == false) - { - state = TokenizerState.Token; - tokenLength += 1; - } - break; - case TokenizerState.Quoted: - if (escapeQuote) - { - escapeQuote = false; - tokenLength += 1; - } - // handle escaping of ' by using '' or \' - else if (ch == '\\' || (ch == '\'' && i + 1 < originalString.Length && originalString[i + 1] == '\'')) - { - escapeQuote = true; - tokenLength += 1; - } - else if (ch == '\'') - { - yield return original.Substring(tokenStart, tokenLength); - tokenStart += tokenLength + 1; - tokenLength = 0; - state = TokenizerState.WhiteSpace; - } - else - { - tokenLength += 1; - } - break; - case TokenizerState.InParenthesis: - if (ch == ')' || ch == ']') - { - tokenLength += 1; - parenthesisCount -= 1; - if (parenthesisCount == 0) - { - yield return original.Substring(tokenStart, tokenLength); - tokenStart += tokenLength + 1; - tokenLength = 0; - state = TokenizerState.WhiteSpace; - } - } - else if (ch == '(' || ch == '[') - { - tokenLength += 1; - parenthesisCount += 1; - } - else - { - tokenLength += 1; - } - break; - case TokenizerState.Token: - if (char.IsWhiteSpace(ch)) - { - yield return original.Substring(tokenStart, tokenLength); - tokenStart += tokenLength + 1; - tokenLength = 0; - state = TokenizerState.WhiteSpace; - } - else if (ch == ',') // stop current token, and send the , as well - { - yield return original.Substring(tokenStart, tokenLength); - yield return new SqlString(","); - tokenStart += tokenLength + 2; - tokenLength = 0; - state = TokenizerState.WhiteSpace; - } - else if (ch == '(' || ch == '[') - { - state = TokenizerState.InParenthesis; - parenthesisCount = 1; - tokenLength += 1; - } - else if (ch == '\'') - { - state = TokenizerState.Quoted; - tokenLength += 1; - } - else - { - tokenLength += 1; - } - break; - default: - throw new InvalidExpressionException("Could not understand the string " + original); - } - } - if (tokenLength > 0) - { - yield return original.Substring(tokenStart, tokenLength); - } - } - - public IEnumerator GetEnumerator() - { - return ((IEnumerable<SqlString>)this).GetEnumerator(); - } - - public enum TokenizerState - { - WhiteSpace, - Quoted, - InParenthesis, - Token - } - - public IList<SqlString> GetTokens() - { - return new List<SqlString>(this); - } - } } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Dialect/Oracle8iDialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/Oracle8iDialect.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate/Dialect/Oracle8iDialect.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -1,14 +1,16 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Data; +using System.Data.Common; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; using NHibernate.Engine; using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Type; -using Environment=NHibernate.Cfg.Environment; -using System.Data.Common; +using NHibernate.Util; +using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Dialect { @@ -259,14 +261,16 @@ isForUpdate = true; } + string selectColumns = ExtractColumnOrAliasNames(sql); + var pagingSelect = new SqlStringBuilder(sql.Parts.Count + 10); if (hasOffset) { - pagingSelect.Add("select * from ( select row_.*, rownum rownum_ from ( "); + pagingSelect.Add("select " + selectColumns + " from ( select row_.*, rownum rownum_ from ( "); } else { - pagingSelect.Add("select * from ( "); + pagingSelect.Add("select " + selectColumns + " from ( "); } pagingSelect.Add(sql); if (hasOffset) @@ -286,6 +290,15 @@ return pagingSelect.ToSqlString(); } + private string ExtractColumnOrAliasNames(SqlString select) + { + List<SqlString> columnsOrAliases; + Dictionary<SqlString, SqlString> aliasToColumn; + ExtractColumnOrAliasNames(select, out columnsOrAliases, out aliasToColumn); + + return StringHelper.Join(",", columnsOrAliases); + } + /// <summary> /// Allows access to the basic <see cref="Dialect.GetSelectClauseNullString"/> /// implementation... Modified: trunk/nhibernate/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -378,6 +378,7 @@ var result = session.CreateCriteria(typeof(Student)) .Add(Subqueries.PropertyIn("Name", dc)) + .AddOrder(Order.Asc("StudentNumber")) .List<Student>(); Assert.That(result.Count, Is.EqualTo(2)); Modified: trunk/nhibernate/src/NHibernate.Test/DialectTest/MsSql2005DialectFixture.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/DialectTest/MsSql2005DialectFixture.cs 2010-09-22 20:25:09 UTC (rev 5202) +++ trunk/nhibernate/src/NHibernate.Test/DialectTest/MsSql2005DialectFixture.cs 2010-09-22 21:01:34 UTC (rev 5203) @@ -159,6 +159,35 @@ Assert.AreEqual(current, expected.Length); } + [Test] + public void QuotedStringTokenizerTests() + { + MsSql2005Dialect.QuotedAndParenthesisStringTokenizer tokenizier = + new MsSql2005Dialect.QuotedAndParenthesisStringTokenizer( + new SqlString("SELECT fish.\"id column\", fish.'fish name' as 'bar\\' column', f FROM fish")); + string[] expected = new string[] + { + "SELECT", + "fish.\"id column\"", + ",", + "fish.'fish name'", + "as", + "'bar\\' column'", + ",", + "f", + "FROM", + "fish" + }; + int current = 0; + IList<SqlString> tokens = tokenizier.GetTokens(); + foreach (SqlString token in tokens) + { + Assert.AreEqual(expected[current], token.ToString()); + current += 1; + } + Assert.AreEqual(current, expected.Length); + } + [Test] public void GetIfExistsDropConstraintTest_without_schema() { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |