This is an automated email from the ASF dual-hosted git repository.
pcristof pushed a commit to branch OPENJPA-2940
in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/OPENJPA-2940 by this push:
new 4ec8d4819 [WIP][OPENJPA-2940] Implements string LEFT and RIGHT string
functions
4ec8d4819 is described below
commit 4ec8d4819222cd161900ff954350a5e6bb9ae669
Author: Paulo Cristovão de Araújo Silva Filho <[email protected]>
AuthorDate: Thu Sep 25 20:33:03 2025 -0300
[WIP][OPENJPA-2940] Implements string LEFT and RIGHT string functions
---
.../jdbc/kernel/exps/JDBCExpressionFactory.java | 16 ++
.../org/apache/openjpa/jdbc/kernel/exps/Left.java | 163 +++++++++++++++++++++
.../org/apache/openjpa/jdbc/kernel/exps/Right.java | 163 +++++++++++++++++++++
.../org/apache/openjpa/jdbc/sql/DBDictionary.java | 26 ++++
.../apache/openjpa/jdbc/sql/OracleDictionary.java | 28 ++++
.../openjpa/kernel/exps/ExpressionFactory.java | 17 +++
.../kernel/exps/InMemoryExpressionFactory.java | 15 ++
.../java/org/apache/openjpa/kernel/exps/Left.java | 68 +++++++++
.../java/org/apache/openjpa/kernel/exps/Right.java | 71 +++++++++
.../openjpa/kernel/jpql/JPQLExpressionBuilder.java | 26 ++++
.../jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt | 19 ++-
.../apache/openjpa/kernel/jpql/TestJPQLParser.java | 26 ++++
.../criteria/TestTypecastAsCriteria.java | 13 --
.../persistence/criteria/TestTypesafeCriteria.java | 32 ++++
.../jpql/functions/TestEJBQLFunction.java | 30 ++++
.../persistence/criteria/CriteriaBuilderImpl.java | 18 +--
.../openjpa/persistence/criteria/Expressions.java | 75 ++++++++--
17 files changed, 769 insertions(+), 37 deletions(-)
diff --git
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
index bb601a755..92fad8d14 100644
---
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
+++
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/JDBCExpressionFactory.java
@@ -548,6 +548,22 @@ public class JDBCExpressionFactory
public Value substring(Value v1, Value v2) {
return new Substring((Val) v1, (Val) v2);
}
+
+ @Override
+ public Value replace(Value orig, Value pattern, Value replacement) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Value left(Value str, Value length) {
+ return new Left((Val) str, (Val) length);
+ }
+
+ @Override
+ public Value right(Value str, Value length) {
+ return new Right((Val) str, (Val) length);
+ }
@Override
public Value toUpperCase(Value val) {
diff --git
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Left.java
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Left.java
new file mode 100644
index 000000000..42fce5c9a
--- /dev/null
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Left.java
@@ -0,0 +1,163 @@
+/*
+ * 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.jdbc.kernel.exps;
+
+import java.sql.SQLException;
+
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.Filters;
+import org.apache.openjpa.kernel.exps.ExpressionVisitor;
+import org.apache.openjpa.meta.ClassMetaData;
+
+/**
+ * Take the left part of a string.
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+public class Left
+ extends AbstractVal {
+
+
+ private static final long serialVersionUID = 1L;
+ private final Val _val1;
+ private final Val _val2;
+ private ClassMetaData _meta = null;
+
+ /**
+ * Constructor. Provide the string and length to operate on
+ */
+ public Left(Val val1, Val val2) {
+ _val1 = val1;
+ _val2 = val2;
+ }
+
+ public Val getVal1() {
+ return _val1;
+ }
+
+ public Val getVal2() {
+ return _val2;
+ }
+
+ @Override
+ public ClassMetaData getMetaData() {
+ return _meta;
+ }
+
+ @Override
+ public void setMetaData(ClassMetaData meta) {
+ _meta = meta;
+ }
+
+ @Override
+ public Class getType() {
+ return String.class;
+ }
+
+ @Override
+ public void setImplicitType(Class type) {
+ }
+
+ @Override
+ public ExpState initialize(Select sel, ExpContext ctx, int flags) {
+ ExpState s1 = _val1.initialize(sel, ctx, 0);
+ ExpState s2 = _val2.initialize(sel, ctx, 0);
+ return new BinaryOpExpState(sel.and(s1.joins, s2.joins), s1, s2);
+ }
+
+ @Override
+ public void select(Select sel, ExpContext ctx, ExpState state,
+ boolean pks) {
+ sel.select(newSQLBuffer(sel, ctx, state), this);
+ }
+
+ @Override
+ public void selectColumns(Select sel, ExpContext ctx, ExpState state,
+ boolean pks) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ _val1.selectColumns(sel, ctx, bstate.state1, true);
+ _val2.selectColumns(sel, ctx, bstate.state2, true);
+ }
+
+ @Override
+ public void groupBy(Select sel, ExpContext ctx, ExpState state) {
+ sel.groupBy(newSQLBuffer(sel, ctx, state));
+ }
+
+ @Override
+ public void orderBy(Select sel, ExpContext ctx, ExpState state,
+ boolean asc) {
+ sel.orderBy(newSQLBuffer(sel, ctx, state), asc, false, getSelectAs());
+ }
+
+ private SQLBuffer newSQLBuffer(Select sel, ExpContext ctx, ExpState state)
{
+ calculateValue(sel, ctx, state, null, null);
+ SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
+ appendTo(sel, ctx, state, buf, 0);
+ return buf;
+ }
+
+ @Override
+ public Object load(ExpContext ctx, ExpState state, Result res)
+ throws SQLException {
+ return Filters.convert(res.getObject(this,
+ JavaSQLTypes.JDBC_DEFAULT, null), getType());
+ }
+
+ @Override
+ public void calculateValue(Select sel, ExpContext ctx, ExpState state,
+ Val other, ExpState otherState) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ _val1.calculateValue(sel, ctx, bstate.state1, null, null);
+ _val2.calculateValue(sel, ctx, bstate.state2, null, null);
+ }
+
+ @Override
+ public int length(Select sel, ExpContext ctx, ExpState state) {
+ return 1;
+ }
+
+ @Override
+ public void appendTo(Select sel, ExpContext ctx, ExpState state,
+ SQLBuffer sql, int index) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ FilterValue str = new FilterValueImpl(sel, ctx, bstate.state1, _val1);
+ FilterValue len = new FilterValueImpl(sel, ctx, bstate.state2, _val2);
+
+ ctx.store.getDBDictionary().left(sql, str, len);
+ }
+
+ @Override
+ public void acceptVisit(ExpressionVisitor visitor) {
+ visitor.enter(this);
+ _val1.acceptVisit(visitor);
+ _val2.acceptVisit(visitor);
+ visitor.exit(this);
+ }
+
+ @Override
+ public int getId() {
+ return Val.SUBSTRING_VAL;
+ }
+}
+
diff --git
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Right.java
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Right.java
new file mode 100644
index 000000000..1b7ec436a
--- /dev/null
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Right.java
@@ -0,0 +1,163 @@
+/*
+ * 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.jdbc.kernel.exps;
+
+import java.sql.SQLException;
+
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.Filters;
+import org.apache.openjpa.kernel.exps.ExpressionVisitor;
+import org.apache.openjpa.meta.ClassMetaData;
+
+/**
+ * Take the right part of a string.
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+public class Right
+ extends AbstractVal {
+
+
+ private static final long serialVersionUID = 1L;
+ private final Val _val1;
+ private final Val _val2;
+ private ClassMetaData _meta = null;
+
+ /**
+ * Constructor. Provide the string and length to operate on
+ */
+ public Right(Val val1, Val val2) {
+ _val1 = val1;
+ _val2 = val2;
+ }
+
+ public Val getVal1() {
+ return _val1;
+ }
+
+ public Val getVal2() {
+ return _val2;
+ }
+
+ @Override
+ public ClassMetaData getMetaData() {
+ return _meta;
+ }
+
+ @Override
+ public void setMetaData(ClassMetaData meta) {
+ _meta = meta;
+ }
+
+ @Override
+ public Class getType() {
+ return String.class;
+ }
+
+ @Override
+ public void setImplicitType(Class type) {
+ }
+
+ @Override
+ public ExpState initialize(Select sel, ExpContext ctx, int flags) {
+ ExpState s1 = _val1.initialize(sel, ctx, 0);
+ ExpState s2 = _val2.initialize(sel, ctx, 0);
+ return new BinaryOpExpState(sel.and(s1.joins, s2.joins), s1, s2);
+ }
+
+ @Override
+ public void select(Select sel, ExpContext ctx, ExpState state,
+ boolean pks) {
+ sel.select(newSQLBuffer(sel, ctx, state), this);
+ }
+
+ @Override
+ public void selectColumns(Select sel, ExpContext ctx, ExpState state,
+ boolean pks) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ _val1.selectColumns(sel, ctx, bstate.state1, true);
+ _val2.selectColumns(sel, ctx, bstate.state2, true);
+ }
+
+ @Override
+ public void groupBy(Select sel, ExpContext ctx, ExpState state) {
+ sel.groupBy(newSQLBuffer(sel, ctx, state));
+ }
+
+ @Override
+ public void orderBy(Select sel, ExpContext ctx, ExpState state,
+ boolean asc) {
+ sel.orderBy(newSQLBuffer(sel, ctx, state), asc, false, getSelectAs());
+ }
+
+ private SQLBuffer newSQLBuffer(Select sel, ExpContext ctx, ExpState state)
{
+ calculateValue(sel, ctx, state, null, null);
+ SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
+ appendTo(sel, ctx, state, buf, 0);
+ return buf;
+ }
+
+ @Override
+ public Object load(ExpContext ctx, ExpState state, Result res)
+ throws SQLException {
+ return Filters.convert(res.getObject(this,
+ JavaSQLTypes.JDBC_DEFAULT, null), getType());
+ }
+
+ @Override
+ public void calculateValue(Select sel, ExpContext ctx, ExpState state,
+ Val other, ExpState otherState) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ _val1.calculateValue(sel, ctx, bstate.state1, null, null);
+ _val2.calculateValue(sel, ctx, bstate.state2, null, null);
+ }
+
+ @Override
+ public int length(Select sel, ExpContext ctx, ExpState state) {
+ return 1;
+ }
+
+ @Override
+ public void appendTo(Select sel, ExpContext ctx, ExpState state,
+ SQLBuffer sql, int index) {
+ BinaryOpExpState bstate = (BinaryOpExpState) state;
+ FilterValue str = new FilterValueImpl(sel, ctx, bstate.state1, _val1);
+ FilterValue len = new FilterValueImpl(sel, ctx, bstate.state2, _val2);
+
+ ctx.store.getDBDictionary().right(sql, str, len);
+ }
+
+ @Override
+ public void acceptVisit(ExpressionVisitor visitor) {
+ visitor.enter(this);
+ _val1.acceptVisit(visitor);
+ _val2.acceptVisit(visitor);
+ visitor.exit(this);
+ }
+
+ @Override
+ public int getId() {
+ return Val.SUBSTRING_VAL;
+ }
+}
+
diff --git
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
index d67fc3d56..d3d667e59 100644
--- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
+++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
@@ -295,6 +295,8 @@ public class DBDictionary
public String concatenateFunction = "({0}||{1})";
public String concatenateDelimiter = "'OPENJPATOKEN'";
public String substringFunctionName = "SUBSTRING";
+ public String leftFunctionName = "LEFT";
+ public String rightFunctionName = "RIGHT";
public String currentDateFunction = "CURRENT_DATE";
public String currentTimeFunction = "CURRENT_TIME";
public String currentTimestampFunction = "CURRENT_TIMESTAMP";
@@ -3148,6 +3150,30 @@ public class DBDictionary
}
buf.append(")");
}
+
+ public void left(SQLBuffer buf, FilterValue str, FilterValue length) {
+ buf.append(leftFunctionName).append("(");
+ str.appendTo(buf);
+ buf.append(", ");
+ if (length.getValue() instanceof Number) {
+ buf.append(Long.toString(toLong(length)));
+ } else {
+ length.appendTo(buf);
+ }
+ buf.append(")");
+ }
+
+ public void right(SQLBuffer buf, FilterValue str, FilterValue length) {
+ buf.append(rightFunctionName).append("(");
+ str.appendTo(buf);
+ buf.append(", ");
+ if (length.getValue() instanceof Number) {
+ buf.append(Long.toString(toLong(length)));
+ } else {
+ length.appendTo(buf);
+ }
+ buf.append(")");
+ }
long toLong(FilterValue litValue) {
return ((Number) litValue.getValue()).longValue();
diff --git
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
index 7be6eef5c..6f3585555 100644
---
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
+++
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
@@ -250,6 +250,7 @@ public class OracleDictionary
}));
substringFunctionName = "SUBSTR";
+ leftFunctionName = substringFunctionName;
super.setBatchLimit(defaultBatchLimit);
selectWordSet.add("WITH");
reportsSuccessNoInfoOnBatchUpdates = true;
@@ -1595,6 +1596,33 @@ public class OracleDictionary
}
return super.getIsNotNullSQL(colAlias, colType);
}
+
+ @Override
+ public void left(SQLBuffer buf, FilterValue str, FilterValue length) {
+ buf.append(leftFunctionName).append("(");
+ str.appendTo(buf);
+ buf.append(", ").append(Long.toString(0l)).append(", ");
+ if (length.getValue() instanceof Number) {
+ buf.append(Long.toString(toLong(length)));
+ } else {
+ length.appendTo(buf);
+ }
+ buf.append(")");
+ }
+
+ @Override
+ public void right(SQLBuffer buf, FilterValue str, FilterValue length) {
+ buf.append(rightFunctionName).append("(");
+ str.appendTo(buf);
+ buf.append(", ");
+ if (length.getValue() instanceof Number) {
+ buf.append(Long.toString(toLong(length)));
+ } else {
+ buf.append("-");
+ length.appendTo(buf);
+ }
+ buf.append(")");
+ }
@Override
public void indexOf(SQLBuffer buf, FilterValue str, FilterValue find,
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
index 170c3ec74..80ca1ddbc 100644
---
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
+++
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExpressionFactory.java
@@ -416,6 +416,23 @@ public interface ExpressionFactory {
* the start index is one-based, and the second argument is the length.
*/
Value substring(Value str, Value args);
+
+ /**
+ * Returns a value representing the left function on the given target
+ * with the given arguments.
+ */
+ Value left(Value str, Value length);
+
+ /**
+ * Returns a value representing the right function on the given target
+ * with the given arguments.
+ */
+ Value right(Value str, Value length);
+
+ /**
+ * Returns the original string with the pattern replaced by the third
argument
+ */
+ Value replace(Value orig, Value pattern, Value replacement);
/**
* Return the upper case of the given value.
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
index c9e4a90a1..2f1f8f616 100644
---
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
+++
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/InMemoryExpressionFactory.java
@@ -715,6 +715,21 @@ public class InMemoryExpressionFactory
public Value substring(Value val1, Value val2) {
return new Substring((Val) val1, (Val) val2);
}
+
+ @Override
+ public Value left(Value str, Value length) {
+ return new Left((Val) str, (Val) length);
+ }
+
+ @Override
+ public Value right(Value str, Value length) {
+ return new Right((Val) str, (Val) length);
+ }
+
+ @Override
+ public Value replace(Value orig, Value pattern, Value replacement) {
+ throw new UnsupportedException("not implemented yet");
+ }
@Override
public Value toUpperCase(Value val) {
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Left.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Left.java
new file mode 100644
index 000000000..e397c83eb
--- /dev/null
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Left.java
@@ -0,0 +1,68 @@
+/*
+ * 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.kernel.exps;
+
+import org.apache.openjpa.kernel.StoreContext;
+
+/**
+ * Take the left part of a string.
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+class Left
+ extends Val {
+
+
+ private static final long serialVersionUID = 1L;
+ private final Val _val;
+ private final Val _len;
+
+ /**
+ * Constructor. Provide substring and length of the left part that must be
kept.
+ */
+ public Left(Val val, Val len) {
+ _val = val;
+ _len = len;
+ }
+
+ @Override
+ public Class getType() {
+ return String.class;
+ }
+
+ @Override
+ public void setImplicitType(Class type) {
+ }
+
+ @Override
+ protected Object eval(Object candidate, Object orig, StoreContext ctx,
Object[] params) {
+ Object str = _val.eval(candidate, orig, ctx, params);
+ Object arg = _len.eval(candidate, orig, ctx, params);
+ return str.toString().substring(0, ((Number) arg).intValue());
+ }
+
+ @Override
+ public void acceptVisit(ExpressionVisitor visitor) {
+ visitor.enter(this);
+ _val.acceptVisit(visitor);
+ _len.acceptVisit(visitor);
+ visitor.exit(this);
+ }
+}
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Right.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Right.java
new file mode 100644
index 000000000..3ef6297fc
--- /dev/null
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Right.java
@@ -0,0 +1,71 @@
+/*
+ * 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.kernel.exps;
+
+import org.apache.openjpa.kernel.StoreContext;
+
+/**
+ * Take the right part of a string.
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+class Right
+ extends Val {
+
+
+ private static final long serialVersionUID = 1L;
+ private final Val _val;
+ private final Val _len;
+
+ /**
+ * Constructor. Provide substring and length of the right part that must
be kept.
+ */
+ public Right(Val val, Val len) {
+ _val = val;
+ _len = len;
+ }
+
+ @Override
+ public Class getType() {
+ return String.class;
+ }
+
+ @Override
+ public void setImplicitType(Class type) {
+ }
+
+ @Override
+ protected Object eval(Object candidate, Object orig, StoreContext ctx,
Object[] params) {
+ Object str = _val.eval(candidate, orig, ctx, params);
+ Object arg = _len.eval(candidate, orig, ctx, params);
+ String s = str.toString();
+ int len = ((Number) arg).intValue();
+ int slen = s.length();
+ return len > slen ? s : s.substring(slen - len, slen);
+ }
+
+ @Override
+ public void acceptVisit(ExpressionVisitor visitor) {
+ visitor.enter(this);
+ _val.acceptVisit(visitor);
+ _len.acceptVisit(visitor);
+ visitor.exit(this);
+ }
+}
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
index f87eff846..4be7a8ff3 100644
---
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
+++
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java
@@ -1379,6 +1379,31 @@ public class JPQLExpressionBuilder
setImplicitType(val3, Integer.TYPE);
return convertSubstringArguments(factory, val1, val2, val3);
+
+ case JJTLEFT:
+ val1 = getValue(firstChild(node));
+ child2 = secondChild(node);
+ val2 = child2.id == JJTINTEGERLITERAL ? getIntegerValue(child2)
: getValue(child2);
+ setImplicitType(val1, TYPE_STRING);
+ setImplicitType(val2, Integer.TYPE);
+ return factory.left(val1, val2);
+
+ case JJTRIGHT:
+ val1 = getValue(firstChild(node));
+ child2 = secondChild(node);
+ val2 = child2.id == JJTINTEGERLITERAL ? getIntegerValue(child2)
: getValue(child2);
+ setImplicitType(val1, TYPE_STRING);
+ setImplicitType(val2, Integer.TYPE);
+ return factory.right(val1, val2);
+
+ case JJTREPLACE:
+ val1 = getValue(firstChild(node));
+ val2 = getValue(secondChild(node));
+ val3 = getValue(thirdChild(node));
+ setImplicitType(val1, TYPE_STRING);
+ setImplicitType(val2, TYPE_STRING);
+ setImplicitType(val3, TYPE_STRING);
+ return factory.replace(val1, val2, val3);
case JJTLOCATE:
Value locatePath = getValue(firstChild(node));
@@ -1587,6 +1612,7 @@ public class JPQLExpressionBuilder
else
return factory.substring(val1, val2);
}
+
private void assertQueryExtensions(String clause) {
OpenJPAConfiguration conf = resolver.getConfiguration();
switch(conf.getCompatibilityInstance().getJPQL()) {
diff --git
a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt
b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt
index 289f83acf..386702449 100644
--- a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt
+++ b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt
@@ -201,6 +201,8 @@ TOKEN [ IGNORE_CASE ]: /* functions returning strings */
| < TRIM: "TRIM" >
| < LOWER: "LOWER" >
| < UPPER: "UPPER" >
+ | < RIGHT: "RIGHT" >
+ | < REPLACE: "REPLACE" >
}
TOKEN [ IGNORE_CASE ]: /* trim specification */
@@ -1232,7 +1234,7 @@ void entity_bean_expression() : { }
void functions_returning_strings() : { }
{
- concat() | substring() | trim() | lower() | upper()
+ concat() | substring() | trim() | lower() | upper() | replace() |
left() | right()
}
void string_cast_function() #STRINGCAST : { }
@@ -1251,6 +1253,20 @@ void substring() #SUBSTRING : { }
<SUBSTRING> "(" string_expression() <COMMA> arithmetic_expression() [
<COMMA> arithmetic_expression() ] ")"
}
+void replace() #REPLACE : { }
+{
+ <REPLACE> "(" string_expression() <COMMA> string_expression() <COMMA>
string_expression() ")"
+}
+
+void left() #LEFT : { }
+{
+ <LEFT> "(" string_expression() <COMMA> arithmetic_expression() ")"
+}
+
+void right() #RIGHT : { }
+{
+ <RIGHT> "(" string_expression() <COMMA> arithmetic_expression() ")"
+}
void trim() #TRIM : { }
{
@@ -1520,6 +1536,7 @@ void path_component() #IDENTIFICATIONVARIABLE :
| t = <SECOND>
| t = <SELECT>
| t = <DISTINCT>
+ | t = <RIGHT>
| t = <FROM>
| t = <UPDATE>
| t = <DELETE>
diff --git
a/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/jpql/TestJPQLParser.java
b/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/jpql/TestJPQLParser.java
index ec44f791a..ede12ed8e 100644
---
a/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/jpql/TestJPQLParser.java
+++
b/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/jpql/TestJPQLParser.java
@@ -190,5 +190,31 @@ public class TestJPQLParser {
fail();
}
}
+
+ @Test
+ public void testStringLeftFunction() {
+ try {
+ String query = "SELECT LEFT(u.name, 3) FROM User AS u WHERE
LEFT(u.lastName, 1) = 'D'";
+ JPQLNode node = (JPQLNode) new JPQL(query).parseQuery();
+ assertNotNull(node);
+ } catch (ParseException ex) {
+ ex.printStackTrace();
+ fail();
+ }
+
+ }
+
+ @Test
+ public void testStringRightFunction() {
+ try {
+ String query = "SELECT RIGHT(u.name, 3) FROM User AS u WHERE
right(u.lastName, 1) = 'D'";
+ JPQLNode node = (JPQLNode) new JPQL(query).parseQuery();
+ assertNotNull(node);
+ } catch (ParseException ex) {
+ ex.printStackTrace();
+ fail();
+ }
+
+ }
}
diff --git
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypecastAsCriteria.java
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypecastAsCriteria.java
index fb5c0e855..92a2f2dd6 100644
---
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypecastAsCriteria.java
+++
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypecastAsCriteria.java
@@ -18,26 +18,13 @@
*/
package org.apache.openjpa.persistence.criteria;
-import java.math.BigDecimal;
import java.time.LocalDateTime;
-import java.util.Collection;
-import jakarta.persistence.Query;
-import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaQuery;
-import jakarta.persistence.criteria.Expression;
-import jakarta.persistence.criteria.Join;
-import jakarta.persistence.criteria.JoinType;
-import jakarta.persistence.criteria.ListJoin;
-import jakarta.persistence.criteria.MapJoin;
-import jakarta.persistence.criteria.ParameterExpression;
-import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.SetJoin;
-import jakarta.persistence.criteria.Subquery;
import org.apache.openjpa.jdbc.sql.DerbyDictionary;
-import org.apache.openjpa.persistence.test.AllowFailure;
public class TestTypecastAsCriteria extends CriteriaTest {
diff --git
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java
index adaa60ebf..f85d6423c 100644
---
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java
+++
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestTypesafeCriteria.java
@@ -1784,5 +1784,37 @@ public class TestTypesafeCriteria extends CriteriaTest {
assertEquivalence(cq, jpql);
}
+
+ public void testLeft() {
+ String jpql = "select p from Person p where left(p.name, 4) = 'John'";
+ em.getTransaction().begin();
+ Person p = new Person();
+ p.setName("John Fitzgerald Doe");
+ em.persist(p);
+ em.getTransaction().commit();
+
+ CriteriaQuery<Person> cq = cb.createQuery(Person.class);
+ Root<Person> c = cq.from(Person.class);
+ cq.where(cb.equal(cb.left(c.get("name"), 4), "John"));
+ em.createQuery(cq).getResultList();
+
+ assertEquivalence(cq, jpql);
+ }
+
+ public void testRight() {
+ String jpql = "select p from Person p where RIGHT(p.name, 3) = 'Doe'";
+ em.getTransaction().begin();
+ Person p = new Person();
+ p.setName("John Fitzgerald Doe");
+ em.persist(p);
+ em.getTransaction().commit();
+
+ CriteriaQuery<Person> cq = cb.createQuery(Person.class);
+ Root<Person> c = cq.from(Person.class);
+ cq.where(cb.equal(cb.left(c.get("name"), 3), "Doe"));
+ em.createQuery(cq).getResultList();
+
+ assertEquivalence(cq, jpql);
+ }
}
diff --git
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/functions/TestEJBQLFunction.java
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/functions/TestEJBQLFunction.java
index 1a194f6a1..7053d7ee7 100644
---
a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/functions/TestEJBQLFunction.java
+++
b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/functions/TestEJBQLFunction.java
@@ -339,6 +339,36 @@ public class TestEJBQLFunction extends AbstractTestCase {
endEm(em);
}
+
+ public void testLeft() {
+ if (getDbDictionary(getEmf()) instanceof DerbyDictionary) {
+ // Derby does not support LEFT
+ return;
+ }
+ EntityManager em = currentEntityManager();
+ String query = "SELECT LEFT(u.name, 3) FROM CompUser AS u WHERE
LEFT(u.address.streetAd, 2) = '43'";
+ List result = em.createQuery(query).getResultList();
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals("See", (String) result.get(0));
+ endEm(em);
+ }
+
+ public void testRight() {
+ if (getDbDictionary(getEmf()) instanceof DerbyDictionary) {
+ // Derby does not support LEFT
+ return;
+ }
+ EntityManager em = currentEntityManager();
+ String query = "SELECT RIGHT(u.name, 3) FROM CompUser AS u WHERE
right(u.address.streetAd, 4) = 'some'";
+ List result = em.createQuery(query).getResultList();
+
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals("tha", (String) result.get(0));
+ endEm(em);
+ }
public void testArithmFunc() {
EntityManager em = currentEntityManager();
diff --git
a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilderImpl.java
b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilderImpl.java
index 680a884b9..f0883b02f 100644
---
a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilderImpl.java
+++
b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilderImpl.java
@@ -1092,26 +1092,22 @@ public class CriteriaBuilderImpl implements
OpenJPACriteriaBuilder, ExpressionPa
@Override
public Expression<String> left(Expression<String> x, int len) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Not yet implemented (JPA
3.2)");
+ return new Expressions.Left(x, len);
}
@Override
- public Expression<String> right(Expression<String> x, int len) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Not yet implemented (JPA
3.2)");
+ public Expression<String> left(Expression<String> x,
Expression<Integer> len) {
+ return new Expressions.Left(x, len);
}
-
+
@Override
- public Expression<String> left(Expression<String> x,
Expression<Integer> len) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Not yet implemented (JPA
3.2)");
+ public Expression<String> right(Expression<String> x, int len) {
+ return new Expressions.Right(x, len);
}
@Override
public Expression<String> right(Expression<String> x,
Expression<Integer> len) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Not yet implemented (JPA
3.2)");
+ return new Expressions.Right(x, len);
}
@Override
diff --git
a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java
b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java
index 1e82c64f1..7c5464d02 100644
---
a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java
+++
b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/Expressions.java
@@ -47,12 +47,13 @@ import
org.apache.openjpa.persistence.criteria.CriteriaExpressionVisitor.Travers
import org.apache.openjpa.persistence.meta.Types;
/**
- * Expressions according to JPA 2.0.
+ * Expressions according to JPA 3.2.
*
* A facade to OpenJPA kernel expressions to enforce stronger typing.
*
* @author Pinaki Poddar
* @author Fay Wang
+ * @author Paulo Cristovão Filho
*
* @since 2.0.0
*
@@ -216,14 +217,14 @@ class Expressions {
*
* @param <X> the type of the resultant expression
*/
- public abstract static class BinarayFunctionalExpression<X> extends
ExpressionImpl<X>{
+ public abstract static class BinaryFunctionalExpression<X> extends
ExpressionImpl<X>{
protected final ExpressionImpl<?> e1;
protected final ExpressionImpl<?> e2;
/**
* Supply the resultant type and pair of input operand expressions.
*/
- public BinarayFunctionalExpression(Class<X> t, Expression<?> x,
Expression<?> y) {
+ public BinaryFunctionalExpression(Class<X> t, Expression<?> x,
Expression<?> y) {
super(t);
e1 = (ExpressionImpl<?>)x;
e2 = (ExpressionImpl<?>)y;
@@ -391,7 +392,7 @@ class Expressions {
}
}
- public static class Power<X, Y extends Number> extends
BinarayFunctionalExpression<Double> {
+ public static class Power<X, Y extends Number> extends
BinaryFunctionalExpression<Double> {
public Power(Expression<X> x, Expression<Y> y) {
super(double.class, x, y);
}
@@ -414,7 +415,7 @@ class Expressions {
}
}
- public static class Round<X> extends BinarayFunctionalExpression<X> {
+ public static class Round<X> extends BinaryFunctionalExpression<X> {
public Round(Expression<?> x, Expression<?> y) {
super(((Class<X>) x.getJavaType()), x, y);
}
@@ -558,6 +559,56 @@ class Expressions {
return Expressions.asValue(q, "SIZE", OPEN_BRACE, e, CLOSE_BRACE);
}
}
+
+ public static class Left extends BinaryFunctionalExpression<String> {
+
+ public Left(Expression<String> x, Integer length) {
+ this(x, new Constant<Integer>(length));
+ }
+
+ public Left(Expression<String> x, Expression<Integer> y) {
+ super(String.class, x, y);
+ }
+
+ @Override
+ public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q)
{
+ Value value = factory.left(
+ Expressions.toValue(e1, factory, q),
+ Expressions.toValue(e2, factory, q));
+ value.setImplicitType(String.class);
+ return value;
+ }
+
+ @Override
+ public StringBuilder asValue(AliasContext q) {
+ return Expressions.asValue(q, "LEFT", OPEN_BRACE, e1, COMMA,
e2, CLOSE_BRACE);
+ }
+ }
+
+ public static class Right extends BinaryFunctionalExpression<String> {
+
+ public Right(Expression<String> x, int length) {
+ this(x, new Expressions.Constant<Integer>(length));
+ }
+
+ public Right(Expression<String> x, Expression<Integer> y) {
+ super(String.class, x, y);
+ }
+
+ @Override
+ public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q)
{
+ Value value = factory.right(
+ Expressions.toValue(e1, factory, q),
+ Expressions.toValue(e2, factory, q));
+ value.setImplicitType(String.class);
+ return value;
+ }
+
+ @Override
+ public StringBuilder asValue(AliasContext q) {
+ return Expressions.asValue(q, "RIGHT", OPEN_BRACE, e1, COMMA,
e2, CLOSE_BRACE);
+ }
+ }
public static class DatabaseFunction<T> extends FunctionalExpression<T> {
private final String functionName;
@@ -650,7 +701,7 @@ class Expressions {
}
}
- public static class Concat extends BinarayFunctionalExpression<String> {
+ public static class Concat extends BinaryFunctionalExpression<String> {
public Concat(Expression<String> x, Expression<String> y) {
super(String.class, x, y);
}
@@ -772,7 +823,7 @@ class Expressions {
}
}
- public static class Trim extends BinarayFunctionalExpression<String> {
+ public static class Trim extends BinaryFunctionalExpression<String> {
static Expression<Character> defaultTrim = new
Constant<>(Character.class, ' ');
static Trimspec defaultSpec = Trimspec.BOTH;
private Trimspec ts;
@@ -825,7 +876,7 @@ class Expressions {
}
}
- public static class Sum<N extends Number> extends
BinarayFunctionalExpression<N> {
+ public static class Sum<N extends Number> extends
BinaryFunctionalExpression<N> {
public Sum(Expression<? extends Number> x, Expression<? extends
Number> y) {
super((Class<N>)x.getJavaType(), x, y);
}
@@ -861,7 +912,7 @@ class Expressions {
}
}
- public static class Product<N extends Number> extends
BinarayFunctionalExpression<N> {
+ public static class Product<N extends Number> extends
BinaryFunctionalExpression<N> {
public Product(Expression<? extends Number> x, Expression<? extends
Number> y) {
super((Class<N>)x.getJavaType(), x, y);
}
@@ -887,7 +938,7 @@ class Expressions {
}
}
- public static class Diff<N extends Number> extends
BinarayFunctionalExpression<N> {
+ public static class Diff<N extends Number> extends
BinaryFunctionalExpression<N> {
public Diff(Expression<? extends Number> x, Expression<? extends
Number> y) {
super((Class<N>)x.getJavaType(), x, y);
}
@@ -916,7 +967,7 @@ class Expressions {
}
- public static class Quotient<N extends Number> extends
BinarayFunctionalExpression<N> {
+ public static class Quotient<N extends Number> extends
BinaryFunctionalExpression<N> {
public Quotient(Expression<? extends Number> x, Expression<? extends
Number> y) {
super((Class<N>)x.getJavaType(), x, y);
}
@@ -944,7 +995,7 @@ class Expressions {
}
}
- public static class Mod extends BinarayFunctionalExpression<Integer> {
+ public static class Mod extends BinaryFunctionalExpression<Integer> {
public Mod(Expression<Integer> x, Expression<Integer> y) {
super(Integer.class, x,y);
}