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);
         }


Reply via email to