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 4fc32601d [WIP][OPENJPA-2940] Adding REPLACE JPQL function and Criteria
4fc32601d is described below

commit 4fc32601dc500e105a7ea13a2e2f45df74c72c41
Author: Paulo Cristovão de Araújo Silva Filho <[email protected]>
AuthorDate: Mon Oct 6 20:17:15 2025 -0300

    [WIP][OPENJPA-2940] Adding REPLACE JPQL function and Criteria
---
 .../jdbc/kernel/exps/JDBCExpressionFactory.java    |   3 +-
 .../apache/openjpa/jdbc/kernel/exps/Replace.java   | 177 +++++++++++++++++++++
 .../org/apache/openjpa/jdbc/sql/DBDictionary.java  |  11 ++
 .../kernel/exps/InMemoryExpressionFactory.java     |   2 +-
 .../org/apache/openjpa/kernel/exps/Replace.java    |  76 +++++++++
 .../apache/openjpa/kernel/jpql/TestJPQLParser.java |  22 ++-
 .../persistence/criteria/TestTypesafeCriteria.java |  22 ++-
 .../jpql/functions/TestEJBQLFunction.java          |  16 ++
 .../persistence/criteria/CriteriaBuilderImpl.java  |  20 +--
 .../openjpa/persistence/criteria/Expressions.java  |  30 ++++
 10 files changed, 360 insertions(+), 19 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 92fad8d14..a77cb9f9d 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
@@ -551,8 +551,7 @@ public class JDBCExpressionFactory
     
     @Override
     public Value replace(Value orig, Value pattern, Value replacement) {
-       // TODO Auto-generated method stub
-       return null;
+       return new Replace((Val) orig, (Val) pattern, (Val) replacement);
     }
     
     @Override
diff --git 
a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Replace.java 
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Replace.java
new file mode 100644
index 000000000..a5d640ae9
--- /dev/null
+++ 
b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Replace.java
@@ -0,0 +1,177 @@
+/*
+ * 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.Joins;
+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 a string and replaces part of it with a replacement string.
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+public class Replace
+    extends AbstractVal {
+
+    
+    private static final long serialVersionUID = 1L;
+    private final Val _orig;
+    private final Val _subs;
+    private final Val _repl;
+    private ClassMetaData _meta = null;
+
+    /**
+     * Constructor. Provide the strings to operate on.
+     */
+    public Replace(Val orig, Val pattern, Val replacement) {
+        _orig = orig;
+        _subs = pattern;
+        _repl = replacement;
+    }
+
+    @Override
+    public ClassMetaData getMetaData() {
+        return _meta;
+    }
+
+    @Override
+    public void setMetaData(ClassMetaData meta) {
+        _meta = meta;
+    }
+
+    @SuppressWarnings("rawtypes")
+       @Override
+    public Class getType() {
+        return String.class;
+    }
+
+    @SuppressWarnings("rawtypes")
+       @Override
+    public void setImplicitType(Class type) {
+    }
+
+    @Override
+    public ExpState initialize(Select sel, ExpContext ctx, int flags) {
+        ExpState s1 = _orig.initialize(sel, ctx, 0);
+        ExpState s2 = _subs.initialize(sel, ctx, 0);
+        ExpState s3 = _repl.initialize(sel, ctx, 0);
+        return new ReplaceExpState(sel.and(s1.joins, sel.and(s2.joins, 
s3.joins)), s1, s2, s3);
+    }
+
+    @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) {
+        ReplaceExpState rstate = (ReplaceExpState) state;
+        _orig.selectColumns(sel, ctx, rstate.orig, true);
+        _subs.selectColumns(sel, ctx, rstate.subs, true);
+        _repl.selectColumns(sel, ctx, rstate.repl, 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) {
+        ReplaceExpState rstate = (ReplaceExpState) state;
+        _orig.calculateValue(sel, ctx, rstate.orig, null, null);
+        _subs.calculateValue(sel, ctx, rstate.subs, null, null);
+        _repl.calculateValue(sel, ctx, rstate.repl, 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) {
+       ReplaceExpState rstate = (ReplaceExpState) state;
+        FilterValue str = new FilterValueImpl(sel, ctx, rstate.orig, _orig);
+        FilterValue pat = new FilterValueImpl(sel, ctx, rstate.subs, _subs);
+        FilterValue repl = new FilterValueImpl(sel, ctx, rstate.repl, _repl);
+        ctx.store.getDBDictionary().replace(sql, str, pat, repl);
+    }
+
+    @Override
+    public void acceptVisit(ExpressionVisitor visitor) {
+        visitor.enter(this);
+        _orig.acceptVisit(visitor);
+        _subs.acceptVisit(visitor);
+        _repl.acceptVisit(visitor);
+        visitor.exit(this);
+    }
+
+    @Override
+    public int getId() {
+        return Val.SUBSTRING_VAL;
+    }
+    
+    private static class ReplaceExpState extends ExpState {
+       private final ExpState orig;
+       private final ExpState subs;
+       private final ExpState repl;
+       
+       public ReplaceExpState(Joins joins, ExpState orig, ExpState subs, 
ExpState repl) {
+               super(joins);
+               this.orig = orig;
+               this.subs = subs;
+               this.repl = repl;
+       }
+    }
+}
+
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 d3d667e59..6686b297c 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,7 @@ public class DBDictionary
     public String concatenateFunction = "({0}||{1})";
     public String concatenateDelimiter = "'OPENJPATOKEN'";
     public String substringFunctionName = "SUBSTRING";
+    public String replaceFunctionName = "REPLACE";
     public String leftFunctionName = "LEFT";
     public String rightFunctionName = "RIGHT";
     public String currentDateFunction = "CURRENT_DATE";
@@ -3151,6 +3152,16 @@ public class DBDictionary
         buf.append(")");
     }
     
+    public void replace(SQLBuffer buf, FilterValue from, FilterValue subs, 
FilterValue replacement) {
+       buf.append(replaceFunctionName).append("(");
+       from.appendTo(buf);
+       buf.append(", ");
+       subs.appendTo(buf);
+       buf.append(", ");
+       replacement.appendTo(buf);
+       buf.append(")");
+    }
+    
     public void left(SQLBuffer buf, FilterValue str, FilterValue length) {
        buf.append(leftFunctionName).append("(");
        str.appendTo(buf);
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 78367318d..b0e7c0e20 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
@@ -728,7 +728,7 @@ public class InMemoryExpressionFactory
     
     @Override
     public Value replace(Value orig, Value pattern, Value replacement) {
-        throw new UnsupportedException("not implemented yet (JPA 3.2)");
+       return new Replace((Val) orig, (Val) pattern, (Val) replacement);
     }
 
     @Override
diff --git 
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Replace.java 
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Replace.java
new file mode 100644
index 000000000..5ba38ecfd
--- /dev/null
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Replace.java
@@ -0,0 +1,76 @@
+/*
+ * 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 replaces part of the given string
+ *
+ * @author Abe White
+ * @author Paulo Cristovão Filho
+ */
+class Replace
+    extends Val {
+
+    
+    private static final long serialVersionUID = 1L;
+    private final Val _orig;
+    private final Val _patt;
+    private final Val _repl;
+
+    /**
+     * Constructor. Provides values of replacement.
+     */
+    public Replace(Val orig, Val pattern, Val replacement) {
+        _orig = orig;
+        _patt = pattern;
+        _repl = replacement;
+    }
+
+    @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) {
+        String str = _orig.eval(candidate, orig, ctx, params).toString();
+        String patt = _patt.eval(candidate, orig, ctx, params).toString();
+        String repl = _repl.eval(candidate, orig, ctx, params).toString();
+        while (str.indexOf(patt) != -1) {
+               str = str.replace(patt, repl);
+        }
+        return str;
+    }
+
+    @Override
+    public void acceptVisit(ExpressionVisitor visitor) {
+        visitor.enter(this);
+        _orig.acceptVisit(visitor);
+        _patt.acceptVisit(visitor);
+        _repl.acceptVisit(visitor);
+        visitor.exit(this);
+    }
+}
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 ede12ed8e..d986005af 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
@@ -185,10 +185,11 @@ public class TestJPQLParser {
                String query = "SELECT CAST(u.birthYear as double) FROM User AS 
u WHERE extract(year from u.dateOfBirth) = 1983";
                JPQLNode node = (JPQLNode) new JPQL(query).parseQuery();
                assertNotNull(node);
+               return;
        } catch (ParseException ex) {
                ex.printStackTrace();
-               fail();
        }
+       fail();
     }
     
     @Test
@@ -197,10 +198,11 @@ public class TestJPQLParser {
                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);
+               return;
        } catch (ParseException ex) {
                ex.printStackTrace();
-               fail();
        }
+       fail();
        
     }
 
@@ -210,11 +212,25 @@ public class TestJPQLParser {
                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);
+               return;
        } catch (ParseException ex) {
                ex.printStackTrace();
-               fail();
        }
+       fail();
        
     }
+    
+    @Test
+    public void testReplaceStringFunction() {
+       try {
+               String query = "SELECT REPLACE(u.name, 'John', u.lastName) FROM 
User u WHERE REPLACE(u.name, 'ohn', 'ack') = 'Jack'";
+               JPQLNode node = (JPQLNode) new JPQL(query).parseQuery();
+               assertNotNull(node);
+               return;
+       } catch (ParseException ex) {
+               ex.printStackTrace();
+       }
+       fail();
+    }
 
 }
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 d3e86b251..ccff30ea7 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
@@ -1820,7 +1820,27 @@ public class TestTypesafeCriteria extends CriteriaTest {
 
         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"));
+       cq.where(cb.equal(cb.right(c.get("name"), 3), "Doe"));
+       em.createQuery(cq).getResultList();
+       
+       assertEquivalence(cq, jpql);
+    }
+    
+    public void testReplace() {
+       if (getDictionary() instanceof DerbyDictionary) {
+               // TODO Derby DB does not support LEFT, RIGHT or REPLACE 
functions
+               return;
+       }
+        em.getTransaction().begin();
+        Person p = new Person();
+        p.setName("John Fitzgerald Doe");
+        em.persist(p);
+        em.getTransaction().commit();
+
+        String jpql = "select p from Person p where REPLACE(p.name, 'ohn', 
'ack') = 'Jack Fitzgerald Doe'";
+        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
+       Root<Person> c = cq.from(Person.class);
+       cq.where(cb.equal(cb.replace(c.get("name"), "ohn", "ack"), "Jack 
Fitzgerald 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 7053d7ee7..efb40cf7d 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
@@ -976,6 +976,22 @@ public class TestEJBQLFunction extends AbstractTestCase {
        endEm(em);
        
     }
+    
+    public void testReplace() {
+       if (getDbDictionary(getEmf()) instanceof DerbyDictionary) {
+               // Derby does not support REPLACE function
+               return;
+       }
+       EntityManager em = currentEntityManager();
+       String query = "SELECT replace(u.name, '_J', 'J') FROM CompUser AS u 
WHERE REPLACE(u.address.city, 'cester', 'st') = :value";
+       
+       List result = em.createQuery(query).setParameter("value", 
"Worst").getResultList();
+       
+       assertEquals(1, result.size());
+       assertEquals("Jacob", (String) result.get(0));
+       
+       endEm(em);
+    }
 
     public CompUser createUser(String name, String cName, Address add, int age,
         boolean isMale) {
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 f0883b02f..1fe77e0e7 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
@@ -1111,27 +1111,23 @@ public class CriteriaBuilderImpl implements 
OpenJPACriteriaBuilder, ExpressionPa
        }
 
        @Override
-       public Expression<String> replace(Expression<String> x, 
Expression<String> substring, Expression<String> replacement) {
-               // TODO Auto-generated method stub
-       throw new UnsupportedOperationException("Not yet implemented (JPA 
3.2)");
+       public Expression<String> replace(Expression<String> str, 
Expression<String> substring, Expression<String> replacement) {
+       return new Expressions.Replace(str, substring, replacement);
        }
 
        @Override
-       public Expression<String> replace(Expression<String> x, String 
substring, Expression<String> replacement) {
-               // TODO Auto-generated method stub
-       throw new UnsupportedOperationException("Not yet implemented (JPA 
3.2)");
+       public Expression<String> replace(Expression<String> str, String 
substring, Expression<String> replacement) {
+               return new Expressions.Replace(str, new 
Expressions.Constant<String>(substring), replacement);
        }
 
        @Override
-       public Expression<String> replace(Expression<String> x, 
Expression<String> substring, String replacement) {
-               // TODO Auto-generated method stub
-       throw new UnsupportedOperationException("Not yet implemented (JPA 
3.2)");
+       public Expression<String> replace(Expression<String> str, 
Expression<String> substring, String replacement) {
+               return new Expressions.Replace(str, substring, new 
Expressions.Constant<String>(replacement));
        }
 
        @Override
-       public Expression<String> replace(Expression<String> x, String 
substring, String replacement) {
-               // TODO Auto-generated method stub
-       throw new UnsupportedOperationException("Not yet implemented (JPA 
3.2)");
+       public Expression<String> replace(Expression<String> str, String 
substring, String replacement) {
+               return new Expressions.Replace(str, new 
Expressions.Constant<String>(substring), new 
Expressions.Constant<String>(replacement));
        }
 
        @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 7c5464d02..fb68f67c9 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
@@ -772,6 +772,36 @@ class Expressions {
             return Expressions.asValue(q, "SUBSTRING", OPEN_BRACE, e, COMMA, 
from, COMMA, len, CLOSE_BRACE);
         }
     }
+    
+    public static class Replace extends UnaryFunctionalExpression<String> {
+       private ExpressionImpl<String> patt;
+       private ExpressionImpl<String> repl;
+       
+       public Replace(Expression<String> str, Expression<String> patt, 
Expression<String> repl) {
+               super(String.class, str);
+               this.patt = (ExpressionImpl<String>) patt;
+               this.repl = (ExpressionImpl<String>) repl;
+       }
+       
+       @Override
+       public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) 
{
+               return factory.replace(
+                               Expressions.toValue(e, factory, q), 
+                               patt.toValue(factory, q), 
+                               repl.toValue(factory, q));
+       }
+       
+       @Override
+       public void acceptVisit(CriteriaExpressionVisitor visitor) {
+               super.acceptVisit(visitor);
+               Expressions.acceptVisit(visitor, patt, repl);
+       }
+       
+       @Override
+       public StringBuilder asValue(AliasContext q) {
+            return Expressions.asValue(q, "REPLACE", OPEN_BRACE, e, COMMA, 
patt, COMMA, repl, CLOSE_BRACE);
+       }
+    }
 
     public static class Locate extends ExpressionImpl<Integer> {
         private ExpressionImpl<String> pattern;

Reply via email to