|
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.
|