Skip to content

Commit 461a5d5

Browse files
committed
NH-3901, NH-3846 - Correct IndexOfGenerator 0-based to 1-based index translation
1 parent 08ed53b commit 461a5d5

File tree

6 files changed

+116
-49
lines changed

6 files changed

+116
-49
lines changed

src/NHibernate.Test/Hql/SimpleFunctionsTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public void PositionSubstring()
131131
args.Add("'a'");
132132
args.Add("va2");
133133
args.Add("2");
134-
Assert.AreEqual("(position('a' in substring(va2, 2))+2-1)", psf.Render(args, factoryImpl).ToString());
134+
Assert.AreEqual("(case position('a' in substring(va2, 2)) when 0 then 0 else (position('a' in substring(va2, 2))+2-1) end)", psf.Render(args, factoryImpl).ToString());
135135
}
136136

137137
[Test]

src/NHibernate.Test/Linq/FunctionTests.cs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,35 @@ public void CharIndexFunction()
131131
if (!TestDialect.SupportsLocate)
132132
Assert.Ignore("Locate function not supported.");
133133

134+
var raw = (from e in db.Employees select e.FirstName).ToList();
135+
var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a') == 0).ToList();
136+
134137
var query = from e in db.Employees
135-
where e.FirstName.IndexOf('A') == 1
136-
select e.FirstName;
138+
let lowerName = e.FirstName.ToLower()
139+
where lowerName.IndexOf('a') == 0
140+
select lowerName;
141+
var result = query.ToList();
137142

143+
Assert.That(result, Is.EqualTo(expected), "Expected {0} but was {1}", string.Join("|", expected), string.Join("|", result));
144+
ObjectDumper.Write(query);
145+
}
146+
147+
[Test]
148+
public void CharIndexOffsetNegativeFunction()
149+
{
150+
if (!TestDialect.SupportsLocate)
151+
Assert.Ignore("Locate function not supported.");
152+
153+
var raw = (from e in db.Employees select e.FirstName).ToList();
154+
var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf('a', 2) == -1).ToList();
155+
156+
var query = from e in db.Employees
157+
let lowerName = e.FirstName.ToLower()
158+
where lowerName.IndexOf('a', 2) == -1
159+
select lowerName;
160+
var result = query.ToList();
161+
162+
Assert.That(result, Is.EqualTo(expected), "Expected {0} but was {1}", string.Join("|", expected), string.Join("|", result));
138163
ObjectDumper.Write(query);
139164
}
140165

@@ -144,10 +169,16 @@ public void IndexOfFunctionExpression()
144169
if (!TestDialect.SupportsLocate)
145170
Assert.Ignore("Locate function not supported.");
146171

172+
var raw = (from e in db.Employees select e.FirstName).ToList();
173+
var expected = raw.Select(x => x.ToLower()).Where(x => x.IndexOf("an") == 0).ToList();
174+
147175
var query = from e in db.Employees
148-
where e.FirstName.IndexOf("An") == 1
149-
select e.FirstName;
176+
let lowerName = e.FirstName.ToLower()
177+
where lowerName.IndexOf("an") == 0
178+
select lowerName;
179+
var result = query.ToList();
150180

181+
Assert.That(result, Is.EqualTo(expected), "Expected {0} but was {1}", string.Join("|", expected), string.Join("|", result));
151182
ObjectDumper.Write(query);
152183
}
153184

@@ -156,11 +187,17 @@ public void IndexOfFunctionProjection()
156187
{
157188
if (!TestDialect.SupportsLocate)
158189
Assert.Ignore("Locate function not supported.");
159-
190+
191+
var raw = (from e in db.Employees select e.FirstName).ToList();
192+
var expected = raw.Select(x => x.ToLower()).Where(x => x.Contains("a")).Select(x => x.IndexOf("a", 1)).ToList();
193+
160194
var query = from e in db.Employees
161-
where e.FirstName.Contains("a")
162-
select e.FirstName.IndexOf('A', 3);
195+
let lowerName = e.FirstName.ToLower()
196+
where lowerName.Contains("a")
197+
select lowerName.IndexOf("a", 1);
198+
var result = query.ToList();
163199

200+
Assert.That(result, Is.EqualTo(expected), "Expected {0} but was {1}", string.Join("|", expected), string.Join("|", result));
164201
ObjectDumper.Write(query);
165202
}
166203

src/NHibernate/Dialect/Function/CharIndexFunction.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,38 @@ public SqlString Render(IList args, ISessionFactoryImplementor factory)
4343
object start = threeArgs ? args[2] : null;
4444

4545
SqlStringBuilder buf = new SqlStringBuilder();
46-
buf.Add("charindex(")
47-
.AddObject(pattern)
48-
.Add(", ");
4946
if (threeArgs)
5047
{
51-
buf.Add("right(");
48+
buf.Add("(case ");
49+
RenderPositionInSubstring(buf, pattern, orgString, start);
50+
buf.Add(" when 0 then 0 else (");
51+
RenderPositionInSubstring(buf, pattern, orgString, start);
52+
buf.Add("+(")
53+
.AddObject(start)
54+
.Add("-1)) end)");
5255
}
53-
buf.AddObject(orgString);
54-
if (threeArgs)
56+
else
5557
{
56-
buf.Add(", char_length(")
57-
.AddObject(orgString)
58-
.Add(")-(")
59-
.AddObject(start)
60-
.Add("-1))");
58+
buf.Add("charindex(")
59+
.AddObject(pattern)
60+
.Add(", ")
61+
.AddObject(orgString)
62+
.Add(")");
6163
}
62-
buf.Add(")");
6364
return buf.ToSqlString();
6465
}
6566

67+
private static void RenderPositionInSubstring(SqlStringBuilder buf, object pattern, object orgString, object start)
68+
{
69+
buf.Add("charindex(")
70+
.AddObject(pattern)
71+
.Add(", right(")
72+
.AddObject(orgString)
73+
.Add(", char_length(")
74+
.AddObject(start)
75+
.Add(")))");
76+
}
77+
6678
#endregion
6779
}
6880
}

src/NHibernate/Dialect/Function/PositionSubstringFunction.cs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Text;
4+
using Antlr.Runtime;
45
using NHibernate.Engine;
56
using NHibernate.SqlCommand;
67
using NHibernate.Type;
@@ -49,32 +50,36 @@ public SqlString Render(IList args, ISessionFactoryImplementor factory)
4950
SqlStringBuilder buf = new SqlStringBuilder();
5051
if (threeArgs)
5152
{
52-
buf.Add("(");
53-
}
54-
buf.Add("position(")
55-
.AddObject(pattern)
56-
.Add(" in ");
57-
if (threeArgs)
58-
{
59-
buf.Add("substring(");
60-
}
61-
buf.AddObject(orgString);
62-
if (threeArgs)
63-
{
64-
buf.Add(", ")
65-
.AddObject(start)
66-
.Add(")");
53+
buf.Add("(case ");
54+
RenderPositionInSubstring(buf, pattern, orgString, start);
55+
buf.Add(" when 0 then 0 else (");
56+
RenderPositionInSubstring(buf, pattern, orgString, start);
57+
buf.Add("+")
58+
.AddObject(start)
59+
.Add("-1) end)");
6760
}
68-
buf.Add(")");
69-
if (threeArgs)
61+
else
7062
{
71-
buf.Add("+")
72-
.AddObject(start)
73-
.Add("-1)");
63+
buf.Add("position(")
64+
.AddObject(pattern)
65+
.Add(" in ")
66+
.AddObject(orgString)
67+
.Add(")");
7468
}
7569
return buf.ToSqlString();
7670
}
7771

72+
private static void RenderPositionInSubstring(SqlStringBuilder buf, object pattern, object orgString, object start)
73+
{
74+
buf.Add("position(")
75+
.AddObject(pattern)
76+
.Add(" in substring(")
77+
.AddObject(orgString)
78+
.Add(", ")
79+
.AddObject(start)
80+
.Add("))");
81+
}
82+
7883
#endregion
7984
}
8085
}

src/NHibernate/Linq/Functions/StringGenerator.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,17 +196,23 @@ public IndexOfGenerator()
196196
}
197197
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
198198
{
199+
HqlMethodCall locate;
199200
if (arguments.Count == 1)
200201
{
201-
return treeBuilder.MethodCall("locate",
202-
visitor.Visit(arguments[0]).AsExpression(),
203-
visitor.Visit(targetObject).AsExpression());//,
204-
//treeBuilder.Constant(0));
205-
}
206-
return treeBuilder.MethodCall("locate",
202+
locate = treeBuilder.MethodCall("locate",
207203
visitor.Visit(arguments[0]).AsExpression(),
208-
visitor.Visit(targetObject).AsExpression(),
209-
visitor.Visit(arguments[1]).AsExpression());
204+
visitor.Visit(targetObject).AsExpression()); //,
205+
//treeBuilder.Constant(0));
206+
}
207+
else
208+
{
209+
var start = treeBuilder.Add(visitor.Visit(arguments[1]).AsExpression(), treeBuilder.Constant(1));
210+
locate = treeBuilder.MethodCall("locate",
211+
visitor.Visit(arguments[0]).AsExpression(),
212+
visitor.Visit(targetObject).AsExpression(),
213+
start);
214+
}
215+
return treeBuilder.Subtract(locate,treeBuilder.Constant(1));
210216
}
211217
}
212218

src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ protected override Expression VisitConstantExpression(ConstantExpression express
8686
if (!_parameters.ContainsKey(expression) && !typeof(IQueryable).IsAssignableFrom(expression.Type) && !IsNullObject(expression))
8787
{
8888
// We use null for the type to indicate that the caller should let HQL figure it out.
89+
object value = expression.Value;
8990
IType type = null;
9091

9192
// We have a bit more information about the null parameter value.
@@ -103,12 +104,18 @@ protected override Expression VisitConstantExpression(ConstantExpression express
103104
}
104105
}
105106

107+
// Constant characters should be sent as strings
108+
if (expression.Type == typeof(char))
109+
{
110+
value = value.ToString();
111+
}
112+
106113
// There is more information available in the Linq expression than to HQL directly.
107114
// In some cases it might be advantageous to use the extra info. Assuming this
108115
// comes up, it would be nice to combine the HQL parameter type determination code
109116
// and the Expression information.
110117

111-
_parameters.Add(expression, new NamedParameter("p" + (_parameters.Count + 1), expression.Value, type));
118+
_parameters.Add(expression, new NamedParameter("p" + (_parameters.Count + 1), value, type));
112119
}
113120

114121
return base.VisitConstantExpression(expression);

0 commit comments

Comments
 (0)