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;