OPENJPA-487: Simplify generated SQL for SUBSTRING and LOCATE JPQL functions by switching from JDO style to JPA style in expressions. SUBSTRING parameters are still inlined to support grouping by SUBSTRING.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1142426 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Milosz Tylenda 2011-07-03 12:32:03 +00:00
parent bfa48697eb
commit 8a85ffb402
21 changed files with 236 additions and 196 deletions

View File

@ -127,16 +127,16 @@ public class Substring
BinaryOpExpState bstate = (BinaryOpExpState) state;
FilterValue str = new FilterValueImpl(sel, ctx, bstate.state1, _val1);
FilterValue start;
FilterValue end = null;
FilterValue length = null;
if (_val2 instanceof Args) {
FilterValue[] filts = ((Args) _val2).newFilterValues(sel, ctx,
bstate.state2);
start = filts[0];
end = filts[1];
length = filts[1];
} else
start = new FilterValueImpl(sel, ctx, bstate.state2, _val2);
ctx.store.getDBDictionary().substring(sql, str, start, end);
ctx.store.getDBDictionary().substring(sql, str, start, length);
}
public void acceptVisit(ExpressionVisitor visitor) {

View File

@ -73,7 +73,7 @@ public abstract class AbstractDB2Dictionary
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
buf.append("(LOCATE(CAST((");
buf.append("LOCATE(CAST((");
find.appendTo(buf);
buf.append(") AS VARCHAR(").append(Integer.toString(varcharCastLength))
.append(")), CAST((");
@ -83,37 +83,32 @@ public abstract class AbstractDB2Dictionary
if (start != null) {
buf.append(", CAST((");
start.appendTo(buf);
buf.append(") AS INTEGER) + 1");
buf.append(") AS INTEGER)");
}
buf.append(") - 1)");
buf.append(")");
}
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
buf.append("SUBSTR(CAST((");
str.appendTo(buf);
buf.append(") AS VARCHAR(").append(Integer.toString(varcharCastLength))
.append(")), ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
buf.append(Long.toString(toLong(start)));
} else {
buf.append("CAST((");
start.appendTo(buf);
buf.append(") AS INTEGER) + 1");
buf.append(") AS INTEGER)");
}
if (end != null) {
if (length != null) {
buf.append(", ");
if (start.getValue() instanceof Number
&& end.getValue() instanceof Number) {
long startLong = toLong(start);
long endLong = toLong(end);
buf.append(Long.toString(endLong - startLong));
if (length.getValue() instanceof Number) {
buf.append(Long.toString(toLong(length)));
} else {
buf.append("CAST((");
end.appendTo(buf);
buf.append(") AS INTEGER) - CAST((");
start.appendTo(buf);
length.appendTo(buf);
buf.append(") AS INTEGER)");
}
}

View File

@ -120,23 +120,20 @@ public abstract class AbstractSQLServerDictionary
}
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
if (end != null)
super.substring(buf, str, start, end);
FilterValue length) {
if (length != null)
super.substring(buf, str, start, length);
else {
// ### it would be good to change this logic as in DBDictionary to
// ### simplify the generated SQL
buf.append("SUBSTRING(");
str.appendTo(buf);
buf.append(", ");
start.appendTo(buf);
buf.append(" + 1, ");
buf.append("LEN(");
buf.append(", LEN(");
str.appendTo(buf);
buf.append(")");
buf.append(" - (");
start.appendTo(buf);
buf.append("))");
buf.append(" - 1))");
}
}
@ -149,9 +146,9 @@ public abstract class AbstractSQLServerDictionary
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append(") - 1");
buf.append(")");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -829,12 +829,12 @@ public class DB2Dictionary
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
if (find.getValue() != null) { // non constants
buf.append("(LOCATE(CAST((");
buf.append("LOCATE(CAST((");
find.appendTo(buf);
buf.append(") AS VARCHAR(1000)), ");
} else {
// this is a constant
buf.append("(LOCATE(");
buf.append("LOCATE(");
find.appendTo(buf);
buf.append(", ");
}
@ -846,16 +846,16 @@ public class DB2Dictionary
str.appendTo(buf);
}
if (start != null) {
if (start.getValue() == null) {
if (start.getValue() != null) {
buf.append(", CAST((");
start.appendTo(buf);
buf.append(") AS INTEGER) + 1");
buf.append(") AS INTEGER)");
} else {
buf.append(", ");
start.appendTo(buf);
}
}
buf.append(") - 1)");
buf.append(")");
}
/**

View File

@ -2745,37 +2745,30 @@ public class DBDictionary
/**
* Invoke this database's substring function.
* Numeric parameters are inlined if possible. This is to handle grouping by SUBSTRING -
* most databases do not allow parameter binding in this case.
*
* @param buf the SQL buffer to write the substring invocation to
* @param str a query value representing the target string
* @param start a query value representing the start index
* @param end a query value representing the end index, or null for none
* @param length a query value representing the length of substring, or null for none
*/
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(", ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
buf.append(Long.toString(toLong(start)));
} else {
buf.append("(");
start.appendTo(buf);
buf.append(" + 1)");
}
if (end != null) {
if (length != null) {
buf.append(", ");
if (start.getValue() instanceof Number
&& end.getValue() instanceof Number) {
long startLong = toLong(start);
long endLong = toLong(end);
buf.append(Long.toString(endLong - startLong));
if (length.getValue() instanceof Number) {
buf.append(Long.toString(toLong(length)));
} else {
end.appendTo(buf);
buf.append(" - (");
start.appendTo(buf);
buf.append(")");
length.appendTo(buf);
}
}
buf.append(")");
@ -2803,9 +2796,9 @@ public class DBDictionary
str.appendTo(buf);
buf.append("), (");
find.appendTo(buf);
buf.append(")) - 1");
buf.append("))");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -402,28 +402,26 @@ public class FirebirdDictionary
* Parameters are inlined because neither parameter binding nor expressions
* are accepted by Firebird here. As a result, an
* {@link UnsupportedException} is thrown when something else than a
* constant is used in <code>start</code> or <code>end</code>.
* constant is used in <code>start</code> or <code>length</code>.
*/
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(" FROM ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
buf.append(Long.toString(startLong));
} else {
throw new UnsupportedException(_loc.get("function-not-supported",
getClass(), substringFunctionName + " with non-constants"));
}
if (end != null) {
if (length != null) {
buf.append(" FOR ");
if (start.getValue() instanceof Number
&& end.getValue() instanceof Number) {
long startLong = toLong(start);
long endLong = toLong(end);
buf.append(Long.toString(endLong - startLong));
if (length.getValue() instanceof Number) {
long lengthLong = toLong(length);
buf.append(Long.toString(lengthLong));
} else {
throw new UnsupportedException(_loc.get(
"function-not-supported", getClass(), substringFunctionName

View File

@ -232,9 +232,9 @@ public class H2Dictionary extends DBDictionary {
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append(") - 1");
buf.append(")");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -345,16 +345,15 @@ public class HSQLDictionary extends DBDictionary {
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
buf.append("(LOCATE(");
buf.append("LOCATE(");
find.appendTo(buf);
buf.append(", ");
str.appendTo(buf);
if (start != null) {
buf.append(", (");
buf.append(", ");
start.appendTo(buf);
buf.append(" + 1)");
}
buf.append(") - 1)");
buf.append(")");
}
@Override

View File

@ -260,7 +260,6 @@ public class IngresDictionary extends DBDictionary {
* - a query value representing the start index, or null to
* start at the beginning
*/
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
@ -273,10 +272,10 @@ public class IngresDictionary extends DBDictionary {
else
str.appendTo(buf);
buf.append(")) - 1");
buf.append("))");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");
@ -291,30 +290,24 @@ public class IngresDictionary extends DBDictionary {
*/
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(", ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
buf.append(Long.toString(toLong(start)));
} else {
buf.append("(CAST ((");
start.appendTo(buf);
buf.append(" + 1) AS INTEGER))");
buf.append(") AS INTEGER))");
}
if (end != null) {
if (length != null) {
buf.append(", ");
if (start.getValue() instanceof Number
&& end.getValue() instanceof Number) {
long startLong = toLong(start);
long endLong = toLong(end);
buf.append(Long.toString(endLong - startLong));
if (length.getValue() instanceof Number) {
buf.append(Long.toString(toLong(length)));
} else {
buf.append("(CAST (");
end.appendTo(buf);
buf.append(" - (");
start.appendTo(buf);
buf.append("(CAST ((");
length.appendTo(buf);
buf.append(") AS INTEGER))");
}
}

View File

@ -69,21 +69,21 @@ public class JDataStoreDictionary
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
buf.append("SUBSTRING(");
str.appendTo(buf);
buf.append(" FROM (");
start.appendTo(buf);
buf.append(" + 1) FOR (");
if (end == null) {
buf.append(") FOR (");
if (length == null) {
buf.append("CHAR_LENGTH(");
str.appendTo(buf);
buf.append(")");
} else
end.appendTo(buf);
length.appendTo(buf);
buf.append(" - (");
start.appendTo(buf);
buf.append(")))");
buf.append(" - 1)))");
}
@Override
@ -96,9 +96,9 @@ public class JDataStoreDictionary
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append(") - 1");
buf.append(")");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -103,20 +103,16 @@ public class PointbaseDictionary
}
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
FilterValue length) {
// SUBSTRING in Pointbase is of the form:
// SELECT SUBSTRING(SOME_COLUMN FROM 1 FOR 5)
buf.append("SUBSTRING(");
str.appendTo(buf);
buf.append(" FROM ");
start.appendTo(buf);
buf.append(" + 1");
if (end != null) {
if (length != null) {
buf.append(" FOR ");
end.appendTo(buf);
buf.append(" - (");
start.appendTo(buf);
buf.append(")");
length.appendTo(buf);
}
buf.append(")");
}
@ -130,9 +126,9 @@ public class PointbaseDictionary
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append(") - 1");
buf.append(")");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -352,9 +352,9 @@ public class PostgresDictionary
substring(buf, str, start, null);
else
str.appendTo(buf);
buf.append(") - 1");
buf.append(")");
if (start != null) {
buf.append(" + ");
buf.append(" - 1 + ");
start.appendTo(buf);
}
buf.append(")");

View File

@ -317,20 +317,17 @@ public class SolidDBDictionary
@Override
public void substring(SQLBuffer buf, FilterValue str, FilterValue start,
FilterValue end) {
if (end != null) {
super.substring(buf, str, start, end);
FilterValue length) {
if (length != null) {
super.substring(buf, str, start, length);
} else {
buf.append(substringFunctionName).append("(");
str.appendTo(buf);
buf.append(", ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
buf.append(Long.toString(toLong(start)));
} else {
buf.append("(");
start.appendTo(buf);
buf.append(" + 1)");
}
buf.append(", ");
if (start.getValue() instanceof Number) {
@ -350,22 +347,15 @@ public class SolidDBDictionary
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
FilterValue start) {
buf.append("(LOCATE(");
buf.append("LOCATE(");
find.appendTo(buf);
buf.append(", ");
str.appendTo(buf);
if (start != null) {
buf.append(", ");
if (start.getValue() instanceof Number) {
long startLong = toLong(start);
buf.append(Long.toString(startLong + 1));
} else {
buf.append("(");
start.appendTo(buf);
buf.append(" + 1)");
}
start.appendTo(buf);
}
buf.append(") - 1)");
buf.append(")");
}
@Override

View File

@ -18,8 +18,6 @@
*/
package org.apache.openjpa.kernel.exps;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
import org.apache.openjpa.meta.ClassMetaData;
@ -332,8 +330,9 @@ public interface ExpressionFactory {
public Value abs(Value num);
/**
* Return a value representing the {@link String#indexOf} function on
* Return a value representing the indexOf (LOCATE in JPQL) function on
* the given target with the given args.
* The optional second argument is one-based.
*/
public Value indexOf(Value str, Value args);
@ -349,9 +348,9 @@ public interface ExpressionFactory {
public Value sqrt(Value num);
/**
* Return a value representing the {@link String#substring} function on
* the given target with the given args. As with {@link String#substring},
* the start index is zero-based, and the second argument is the end index.
* Return a value representing the substring function on
* the given target with the given args. Unlike as with {@link String#substring},
* the start index is one-based, and the second argument is the length.
*/
public Value substring(Value str, Value args);

View File

@ -22,6 +22,7 @@ import org.apache.openjpa.kernel.StoreContext;
/**
* Find the index of one string within another.
* Index is 1-based.
*
* @author Abe White
*/
@ -55,9 +56,9 @@ class IndexOf
if (arg instanceof Object[]) {
Object[] args = (Object[]) arg;
idx = str.toString().indexOf(args[0].toString(),
((Number) args[1]).intValue());
((Number) args[1]).intValue() - 1) + 1;
} else
idx = str.toString().indexOf(arg.toString());
idx = str.toString().indexOf(arg.toString()) + 1;
return idx;
}

View File

@ -53,12 +53,12 @@ class Substring
Object arg = _args.eval(candidate, orig, ctx, params);
if (arg instanceof Object[]) {
Object[] args = (Object[]) arg;
int start = ((Number) args[0]).intValue();
int end = ((Number) args[1]).intValue();
int start = ((Number) args[0]).intValue() - 1;
int length = ((Number) args[1]).intValue();
String string = str == null ? "" : str.toString();
return string.substring(start, Math.min(end, string.length()));
return string.substring(start, Math.min(start + length, string.length()));
}
return str.toString().substring(((Number) arg).intValue());
return str.toString().substring(((Number) arg).intValue() - 1);
}
public void acceptVisit(ExpressionVisitor visitor) {

View File

@ -1304,18 +1304,22 @@ public class JPQLExpressionBuilder
return factory.concat(val1, val2);
case JJTSUBSTRING:
if (node.children.length == 3) {
val1 = getValue(child(node, 0, 3));
val2 = getValue(child(node, 1, 3));
JPQLNode child3 = child(node, 2, 3);
if (child3.id == JJTINTEGERLITERAL)
// Literals are forced to be Integers because PostgreSQL rejects Longs in SUBSTRING parameters.
// This however does not help if an expression like 1+1 is passed as parameter.
val1 = getValue(firstChild(node));
JPQLNode child2 = secondChild(node);
if (child2.id == JJTINTEGERLITERAL) {
val2 = getIntegerValue(child2);
} else {
val2 = getValue(child2);
}
if (node.getChildCount() == 3) {
JPQLNode child3 = thirdChild(node);
if (child3.id == JJTINTEGERLITERAL) {
val3 = getIntegerValue(child3);
else
} else {
val3 = getValue(child3);
} else if (node.children.length == 2) {
val1 = getValue(child(node, 0, 2));
val2 = getValue(child(node, 1, 2));
}
}
setImplicitType(val1, TYPE_STRING);
setImplicitType(val2, Integer.TYPE);
@ -1325,12 +1329,11 @@ public class JPQLExpressionBuilder
return convertSubstringArguments(factory, val1, val2, val3);
case JJTLOCATE:
// as with SUBSTRING (above), the semantics for LOCATE differ
// from ExpressionFactory.indexOf in that LOCATE uses a
// 0-based index, and indexOf uses a 1-based index
Value locatePath = getValue(firstChild(node));
Value locateSearch = getValue(secondChild(node));
Value locateFromIndex = null;
// Literals are forced to be Integers because PostgreSQL rejects Longs in POSITION parameters.
// This however does not help if an expression like 1+1 is passed as parameter.
if (node.getChildCount() > 2) { // optional start index arg
JPQLNode child3 = thirdChild(node);
if (child3.id == JJTINTEGERLITERAL) {
@ -1342,16 +1345,11 @@ public class JPQLExpressionBuilder
setImplicitType(locateSearch, TYPE_STRING);
if (locateFromIndex != null)
setImplicitType(locateFromIndex, TYPE_STRING);
setImplicitType(locateFromIndex, Integer.TYPE);
return factory.add(factory.indexOf(locateSearch,
return factory.indexOf(locateSearch,
locateFromIndex == null ? locatePath
: factory.newArgumentList(locatePath,
factory.subtract(locateFromIndex,
factory.newLiteral(1,
Literal.TYPE_NUMBER)))),
factory.newLiteral(1,
Literal.TYPE_NUMBER));
: factory.newArgumentList(locatePath, locateFromIndex));
case JJTAGGREGATE:
// simply pass-through while asserting a single child
@ -1461,14 +1459,6 @@ public class JPQLExpressionBuilder
* Converts JPQL substring() function to OpenJPA ExpressionFactory
* substring() arguments.
*
* The semantics of the JPQL substring() function are that arg2 is the
* 1-based start index, and arg3 is the length of the string to be return.
* This is different than the semantics of the ExpressionFactory's
* substring(), which matches the Java language (0-based start index,
* arg2 is the end index): we perform the translation by adding one to the
* first argument, and then adding the first argument to the second
* argument to get the endIndex.
*
* @param val1 the original String
* @param val2 the 1-based start index as per JPQL substring() semantics
* @param val3 the length of the returned string as per JPQL semantics
@ -1476,35 +1466,10 @@ public class JPQLExpressionBuilder
*/
public static Value convertSubstringArguments(ExpressionFactory factory,
Value val1, Value val2, Value val3) {
Value start = null;
Value end = null;
if (val2 instanceof Literal &&
(val3 == null || val3 instanceof Literal)) {
// optimize SQL for the common case of two literals
long jpqlStart = ((Number) ((Literal) val2).getValue())
.longValue();
start = factory.newLiteral(Long.valueOf(jpqlStart - 1),
Literal.TYPE_NUMBER);
if (val3 != null) {
long length = ((Number) ((Literal) val3).getValue())
.longValue();
long endIndex = length + (jpqlStart - 1);
end = factory.newLiteral(Long.valueOf(endIndex),
Literal.TYPE_NUMBER);
}
} else {
start = factory.subtract(val2, factory.newLiteral
(1, Literal.TYPE_NUMBER));
if (val3 != null)
end = factory.add(val3,
(factory.subtract(val2, factory.newLiteral
(1, Literal.TYPE_NUMBER))));
}
if (val3 != null)
return factory.substring(val1, factory.newArgumentList(start, end));
return factory.substring(val1, factory.newArgumentList(val2, val3));
else
return factory.substring(val1, start);
return factory.substring(val1, val2);
}
private void assertQueryExtensions(String clause) {
OpenJPAConfiguration conf = resolver.getConfiguration();

View File

@ -107,7 +107,7 @@ public class TestEJBQLFunction extends AbstractTestCase {
em.refresh(user);
assertNotNull("the user is null", user);
assertEquals("the users name is not AblahumSeet", "Ablahumeeth",
assertEquals("the users name is not Ablahumeeth", "Ablahumeeth",
user.getName());
query = "UPDATE CompUser e SET e.name = " +
@ -121,8 +121,23 @@ public class TestEJBQLFunction extends AbstractTestCase {
em.refresh(user);
assertNotNull("the user is null", user);
assertEquals("the users name is not Ablahumeeth", "XYZeeth",
assertEquals("the users name is not XYZeeth", "XYZeeth",
user.getName());
query = "UPDATE CompUser e SET e.name = " +
"CONCAT('CAD', SUBSTRING(e.name, LOCATE('e', e.name, 5))) " +
"WHERE e.name='XYZeeth'";
result = em.createQuery(query).executeUpdate();
assertEquals("the result is not 1", 1, result);
user = em.find(CompUser.class, userid1);
em.refresh(user);
assertNotNull("the user is null", user);
assertEquals("the users name is not CADeth", "CADeth",
user.getName());
endTx(em);
endEm(em);
}

View File

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.query;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.persistence.test.SingleEMTestCase;
/**
* Test for JPQL LOCATE function.
*/
public class TestLocate extends SingleEMTestCase {
public void setUp() {
super.setUp(SimpleEntity.class, CLEAR_TABLES);
EntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
em1.persist(new SimpleEntity("foo", "bar"));
em1.getTransaction().commit();
em1.close();
}
public void testLocate() {
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where LOCATE('bar', o.value) = 1").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where LOCATE('ar', o.value) = 2").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where LOCATE('zzz', o.value) = 0").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where LOCATE('ar', o.value, 1) = 2").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where LOCATE('ar', o.value, 2) = 2").getSingleResult());
}
public void testLocateInMemory() {
List<SimpleEntity> allEntities = em.createQuery("select o from simple o", SimpleEntity.class).getResultList();
Object inMemoryResult = em.createQuery("select LOCATE('bar', o.value) from simple o")
.setCandidateCollection(allEntities).getSingleResult();
assertEquals(1, inMemoryResult);
inMemoryResult = em.createQuery("select LOCATE('ar', o.value, 2) from simple o")
.setCandidateCollection(allEntities).getSingleResult();
assertEquals(2, inMemoryResult);
}
}

View File

@ -18,8 +18,11 @@
*/
package org.apache.openjpa.persistence.query;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.jdbc.sql.PostgresDictionary;
import org.apache.openjpa.persistence.test.SingleEMTestCase;
public class TestSubstring extends SingleEMTestCase {
@ -28,6 +31,18 @@ public class TestSubstring extends SingleEMTestCase {
super.setUp(SimpleEntity.class, CLEAR_TABLES,
"openjpa.Compatibility", "JPQL=extended");
// Expressions as substring parameters fail on PostgreSQL.
// The same problem exists with LOCATE.
// Possible fix: use CAST to integer as we do with DB2.
if ("testSubstringWithExpressionsInWhere".equals(getName())) {
setUnsupportedDatabases(PostgresDictionary.class);
if (isTestsDisabled()) {
getLog().trace(getName() +
" - Skipping test - Expressions as substring parameters fail on PostgreSQL.");
return;
}
}
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(new SimpleEntity("foo", "bar"));
@ -49,6 +64,12 @@ public class TestSubstring extends SingleEMTestCase {
"where substring(o.value, 1, 2) = 'ba'").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where substring(o.value, 2, 2) = 'ar'").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where substring(o.value, 1) = 'bar'").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where substring(o.value, 2) = 'ar'").getSingleResult());
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where substring(o.value, 3) = 'r'").getSingleResult());
}
public void testSubstringInSelect() {
@ -58,5 +79,22 @@ public class TestSubstring extends SingleEMTestCase {
"from simple o").getSingleResult());
assertEquals("r", em.createQuery("select substring(o.value, 3, 1) " +
"from simple o").getSingleResult());
assertEquals("ar", em.createQuery("select substring(o.value, 2) " +
"from simple o").getSingleResult());
}
public void testSubstringInMemory() {
List<SimpleEntity> allEntities = em.createQuery("select o from simple o", SimpleEntity.class).getResultList();
Object inMemoryResult = em.createQuery("select substring(o.value, 1, 1) from simple o")
.setCandidateCollection(allEntities).getSingleResult();
assertEquals("b", inMemoryResult);
inMemoryResult = em.createQuery("select substring(o.value, 2) from simple o")
.setCandidateCollection(allEntities).getSingleResult();
assertEquals("ar", inMemoryResult);
}
public void testSubstringWithExpressionsInWhere() {
assertEquals((long) 1, em.createQuery("select count(o) from simple o " +
"where substring(o.value, 1+1, 1+1) = 'ar'").getSingleResult());
}
}

View File

@ -570,13 +570,10 @@ class Expressions {
Value locateSearch = path.toValue(factory, q);
Value locateFromIndex = (from == null ? null : Expressions.toValue(from, factory, q));
Value locatePath = Expressions.toValue(pattern, factory, q);
return factory.add(factory.indexOf(locateSearch,
locateFromIndex == null ? locatePath
: factory.newArgumentList(locatePath,
factory.subtract(locateFromIndex,
factory.newLiteral(Integer.valueOf(1), Literal.TYPE_NUMBER)))),
factory.newLiteral(Integer.valueOf(1), Literal.TYPE_NUMBER));
return factory.indexOf(locateSearch,
locateFromIndex == null ? locatePath
: factory.newArgumentList(locatePath, locateFromIndex));
}
public void acceptVisit(CriteriaExpressionVisitor visitor) {