This is an automated email from the ASF dual-hosted git repository. solomax pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/master by this push: new 4cc305099 [OPENJPA-2932] Implements numeric JPA 3.1 JPQL functions and equivalent Criteria API (#122) 4cc305099 is described below commit 4cc3050998afbdf8d59d5e96ccc9ff0580ee07ff Author: Paulo Cristovão de Araújo Silva Filho <pcris...@gmail.com> AuthorDate: Sun Feb 2 11:34:55 2025 -0300 [OPENJPA-2932] Implements numeric JPA 3.1 JPQL functions and equivalent Criteria API (#122) * [OPENJPA-2932] Implements numeric JPA 3.1 JPQL functions and equivalent Criteria API * Implements CEILING, EXP, FLOOR, SIGN, LN, POWER and ROUND JPQL functions * Ajusts JPQL.jjt * Added kernel and jdbc equivalent expressions * Added one test for each new expression in JPQL and Criteria API * Updates manual page referring new FUNCTIONS * Added DatabaseHelper to add necessary functions on DerbyDb (it does not natively support POWER and ROUND functions) --- .../apache/openjpa/jdbc/kernel/JDBCStoreQuery.java | 3 + .../apache/openjpa/jdbc/kernel/exps/Ceiling.java | 66 +++++++++ .../openjpa/jdbc/kernel/exps/Exponential.java | 52 +++++++ .../org/apache/openjpa/jdbc/kernel/exps/Floor.java | 66 +++++++++ .../jdbc/kernel/exps/JDBCExpressionFactory.java | 35 +++++ .../org/apache/openjpa/jdbc/kernel/exps/Math.java | 2 + .../openjpa/jdbc/kernel/exps/NaturalLogarithm.java | 52 +++++++ .../org/apache/openjpa/jdbc/kernel/exps/Sign.java | 57 ++++++++ .../org/apache/openjpa/jdbc/kernel/exps/Val.java | 5 + .../org/apache/openjpa/jdbc/sql/DBDictionary.java | 9 +- .../java/org/apache/openjpa/kernel/Filters.java | 43 +++++- .../org/apache/openjpa/kernel/exps/Ceiling.java | 76 ++++++++++ .../apache/openjpa/kernel/exps/Exponential.java | 47 +++++++ .../openjpa/kernel/exps/ExpressionFactory.java | 35 +++++ .../java/org/apache/openjpa/kernel/exps/Floor.java | 76 ++++++++++ .../kernel/exps/InMemoryExpressionFactory.java | 35 +++++ .../openjpa/kernel/exps/NaturalLogarithm.java | 47 +++++++ .../java/org/apache/openjpa/kernel/exps/Power.java | 46 ++++++ .../java/org/apache/openjpa/kernel/exps/Round.java | 45 ++++++ .../java/org/apache/openjpa/kernel/exps/Sign.java | 59 ++++++++ .../openjpa/kernel/jpql/JPQLExpressionBuilder.java | 21 +++ .../jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt | 57 +++++++- .../persistence/common/utils/DatabaseHelper.java | 156 +++++++++++++++++++++ .../persistence/criteria/TestTypesafeCriteria.java | 76 ++++++++++ .../jpql/functions/TestEJBQLFunction.java | 143 +++++++++++++++++++ .../persistence/criteria/CriteriaBuilderImpl.java | 87 ++++++------ .../openjpa/persistence/criteria/Expressions.java | 133 ++++++++++++++++++ .../src/doc/manual/jpa_overview_query.xml | 149 ++++++++++++++++++-- 28 files changed, 1613 insertions(+), 65 deletions(-) diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java index ce69d3634..ebd107f2d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreQuery.java @@ -841,6 +841,9 @@ public class JDBCStoreQuery return Filters.divide(val1, c1, val2, c2); else if (op.equals(org.apache.openjpa.jdbc.kernel.exps.Math.MOD)) return Filters.mod(val1, c1, val2, c2); + else if (op.equals(org.apache.openjpa.jdbc.kernel.exps.Math.POWER)) { + return Filters.power(val1, c1, val2, c2); + } throw new UnsupportedException(); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Ceiling.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Ceiling.java new file mode 100644 index 000000000..4cd583234 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Ceiling.java @@ -0,0 +1,66 @@ +/* + * 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.math.BigDecimal; +import java.math.BigInteger; + +import org.apache.openjpa.kernel.Filters; + +/** + * Ceiling value. + */ +public class Ceiling + extends UnaryOp { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value to operate on. + */ + public Ceiling(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + Class wrap = Filters.wrap(c); + if (wrap == Integer.class + || wrap == Float.class + || wrap == Double.class + || wrap == Long.class + || wrap == BigDecimal.class + || wrap == BigInteger.class) { + return Filters.unwrap(c); + } + return int.class; + } + + @Override + protected String getOperator() { + return "CEILING"; + } + + @Override + public int getId() { + return Val.CEILING_VAL; + } +} + diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Exponential.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Exponential.java new file mode 100644 index 000000000..c6fdc867e --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Exponential.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * Exponential value. + */ +public class Exponential + extends UnaryOp { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value to which the Euler's constant should be powered. + */ + public Exponential(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return double.class; + } + + @Override + protected String getOperator() { + return "EXP"; + } + + @Override + public int getId() { + return Val.EXP_VAL; + } +} + diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Floor.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Floor.java new file mode 100644 index 000000000..e8f7740aa --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Floor.java @@ -0,0 +1,66 @@ +/* + * 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.math.BigDecimal; +import java.math.BigInteger; + +import org.apache.openjpa.kernel.Filters; + +/** + * Floor value. + */ +public class Floor + extends UnaryOp { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value to operate on. + */ + public Floor(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + Class wrap = Filters.wrap(c); + if (wrap == Integer.class + || wrap == Float.class + || wrap == Double.class + || wrap == Long.class + || wrap == BigDecimal.class + || wrap == BigInteger.class) { + return Filters.unwrap(c); + } + return int.class; + } + + @Override + protected String getOperator() { + return "FLOOR"; + } + + @Override + public int getId() { + return Val.FLOOR_VAL; + } +} + 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 fbb273270..41e6f5b30 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 @@ -448,6 +448,41 @@ public class JDBCExpressionFactory public Value abs(Value val) { return new Abs((Val) val); } + + @Override + public Value ceiling(Value val) { + return new Ceiling((Val) val); + } + + @Override + public Value exp(Value val) { + return new Exponential((Val) val); + } + + @Override + public Value floor(Value val) { + return new Floor((Val) val); + } + + @Override + public Value ln(Value val) { + return new NaturalLogarithm((Val) val); + } + + @Override + public Value sign(Value val) { + return new Sign((Val) val); + } + + @Override + public Value power(Value base, Value exponent) { + return new Math((Val) base, (Val) exponent, Math.POWER); + } + + @Override + public Value round(Value num, Value precision) { + return new Math((Val) num, (Val) precision, Math.ROUND); + } @Override public Value indexOf(Value v1, Value v2) { diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java index 744130d3d..dfbab290d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java @@ -43,6 +43,8 @@ public class Math public static final String MULTIPLY = "*"; public static final String DIVIDE = "/"; public static final String MOD = "MOD"; + public static final String POWER = "POWER"; + public static final String ROUND = "ROUND"; private final Val _val1; private final Val _val2; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NaturalLogarithm.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NaturalLogarithm.java new file mode 100644 index 000000000..9c940a3f8 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/NaturalLogarithm.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * Natural logarithm (base e) value. + */ +public class NaturalLogarithm + extends UnaryOp { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value from which the natural logarithm should be calculated. + */ + public NaturalLogarithm(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return double.class; + } + + @Override + protected String getOperator() { + return "LN"; + } + + @Override + public int getId() { + return Val.LN_VAL; + } +} + diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sign.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sign.java new file mode 100644 index 000000000..1079a3557 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Sign.java @@ -0,0 +1,57 @@ +/* + * 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.math.BigDecimal; +import java.math.BigInteger; + +import org.apache.openjpa.kernel.Filters; + +/** + * Sign value. + */ +public class Sign + extends UnaryOp { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value whose sign will be found. + */ + public Sign(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return int.class; + } + + @Override + protected String getOperator() { + return "SIGN"; + } + + @Override + public int getId() { + return Val.SIGN_VAL; + } +} + diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java index 049cec503..958bb024d 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Val.java @@ -75,6 +75,11 @@ public interface Val int WHENSCALAR_VAL = 16; int COALESCE_VAL = 17; int NULLIF_VAL = 18; + int CEILING_VAL = 19; + int EXP_VAL = 20; + int FLOOR_VAL = 21; + int LN_VAL = 22; + int SIGN_VAL = 23; /** * Initialize the value. This method should recursively initialize any 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 025306b5a..9dbba34c9 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 @@ -3181,12 +3181,15 @@ public class DBDictionary } boolean mod = "MOD".equals(op); - if (mod) { - if (supportsModOperator) + boolean power = "POWER".equals(op); + boolean round = "ROUND".equals(op); + if (mod || power || round) { + if (supportsModOperator && mod) op = "%"; else buf.append(op); } + buf.append("("); if (castlhs) @@ -3194,7 +3197,7 @@ public class DBDictionary else lhs.appendTo(buf); - if (mod && !supportsModOperator) + if ((mod && !supportsModOperator) || power || round) buf.append(", "); else buf.append(" ").append(op).append(" "); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Filters.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Filters.java index 5cc92f006..36df09254 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Filters.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/Filters.java @@ -21,6 +21,7 @@ package org.apache.openjpa.kernel; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import java.security.AccessController; import java.security.PrivilegedActionException; import java.sql.Time; @@ -65,6 +66,8 @@ public class Filters { private static final int OP_MULTIPLY = 2; private static final int OP_DIVIDE = 3; private static final int OP_MOD = 4; + private static final int OP_POWER = 5; + private static final int OP_ROUND = 6; private static final Localizer _loc = Localizer.forPackage(Filters.class); @@ -480,6 +483,17 @@ public class Filters { return op(o1, c1, o2, c2, OP_MOD); } + /** + * Power the base to the exponent + */ + public static Object power(Object o1, Class<?> c1, Object o2, Class<?> c2) { + return op(o1, c1, o2, c2, OP_POWER); + } + + public static Object round(Object o1, Class<?> c1, Object o2, Class<?> c2) { + return op(o1, c1, o2, c2, OP_ROUND); + } + /** * Perform the given operation on two numbers. */ @@ -546,6 +560,10 @@ public class Filters { case OP_MOD: tot = n1 % n2; break; + case OP_POWER: + return Math.pow(n1, n2); + case OP_ROUND: + tot = n1; default: throw new InternalException(); } @@ -573,7 +591,12 @@ public class Filters { case OP_MOD: tot = n1 % n2; break; - default: + case OP_POWER: + return Math.pow(n1, n2); + case OP_ROUND: + BigDecimal bg = new BigDecimal(Float.toString(n1)); + return bg.setScale(Math.toIntExact(Math.round(n2)), RoundingMode.HALF_EVEN).floatValue(); + default: throw new InternalException(); } return tot; @@ -600,6 +623,12 @@ public class Filters { case OP_MOD: tot = n1 % n2; break; + case OP_POWER: + tot = Math.pow(n1, n2); + break; + case OP_ROUND: + BigDecimal bg = new BigDecimal(Double.toString(n1)); + return bg.setScale(Math.toIntExact(Math.round(n2)), RoundingMode.HALF_EVEN).doubleValue(); default: throw new InternalException(); } @@ -627,6 +656,10 @@ public class Filters { case OP_MOD: tot = n1 % n2; break; + case OP_POWER: + return Math.pow(n1, n2); + case OP_ROUND: + return n1; default: throw new InternalException(); } @@ -649,6 +682,10 @@ public class Filters { return n1.divide(n2, scale, BigDecimal.ROUND_HALF_UP); case OP_MOD: throw new UserException(_loc.get("mod-bigdecimal")); + case OP_POWER: + return n1.pow(n2.intValue()); + case OP_ROUND: + return n1.setScale(n2.intValue(), RoundingMode.HALF_EVEN); default: throw new InternalException(); } @@ -667,6 +704,10 @@ public class Filters { return n1.multiply(n2); case OP_DIVIDE: return n1.divide(n2); + case OP_POWER: + return n1.pow(n2.intValue()); + case OP_ROUND: + return n1; default: throw new InternalException(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Ceiling.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Ceiling.java new file mode 100644 index 000000000..19123b661 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Ceiling.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 java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import org.apache.openjpa.kernel.Filters; + +/** + * Take the ceiling value of a number. + */ +class Ceiling + extends UnaryMathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the number whose ceiling value to calculate. + */ + public Ceiling(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + Class wrap = Filters.wrap(c); + if (wrap == Integer.class + || wrap == Float.class + || wrap == Double.class + || wrap == Long.class + || wrap == BigDecimal.class + || wrap == BigInteger.class) { + return Filters.unwrap(c); + } + return int.class; + } + + @Override + protected Object operate(Object o, Class c) { + c = Filters.wrap(c); + if (c == Integer.class) + return Math.ceil(((Number) o).intValue()); + if (c == Float.class) + return Math.ceil(((Number) o).floatValue()); + if (c == Double.class) + return Math.ceil(((Number) o).doubleValue()); + if (c == Long.class) + return Math.ceil(((Number) o).longValue()); + if (c == BigDecimal.class) + return ((BigDecimal) o).setScale(0, RoundingMode.CEILING); + if (c == BigInteger.class) + return ((BigInteger) o); + + // default to int + return Math.ceil(((Number) o).intValue()); + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Exponential.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Exponential.java new file mode 100644 index 000000000..ef34c7a78 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Exponential.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Exponential value. + * + */ +class Exponential + extends UnaryMathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value to which the Euler's constant should be powered. + */ + public Exponential(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return double.class; + } + + @Override + protected Object operate(Object o, Class c) { + return Math.exp(((Number) o).doubleValue()); + } +} 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 8c6b86cfd..38f433935 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 @@ -327,6 +327,41 @@ public interface ExpressionFactory { * Return a value representing the absolute value of the given one. */ Value abs(Value num); + + /** + * Returns a value representing the ceiling value of the given one. + */ + Value ceiling(Value num); + + /** + * Returns a value representing the Euler's e constant powered to the given value. + */ + Value exp(Value num); + + /** + * Returns a value representing the floor of the given value. + */ + Value floor(Value num); + + /** + * Returns the natural logarithm of the given value + */ + Value ln(Value num); + + /** + * Returns the sign of the given value as a number (-1 for negative, 0 for zeroes and 1 for positive) + */ + Value sign(Value num); + + /** + * Returns a value representing base powered by the exponent + */ + Value power(Value base, Value exponent); + + /* + * Returns the number rounded to the given precision. + */ + Value round(Value num, Value precision); /** * Return a value representing the indexOf (LOCATE in JPQL) function on diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Floor.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Floor.java new file mode 100644 index 000000000..95b1ffdf3 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Floor.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 java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import org.apache.openjpa.kernel.Filters; + +/** + * Take the floor value of a number. + */ +class Floor + extends UnaryMathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the number whose floor value will be calculated. + */ + public Floor(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + Class wrap = Filters.wrap(c); + if (wrap == Integer.class + || wrap == Float.class + || wrap == Double.class + || wrap == Long.class + || wrap == BigDecimal.class + || wrap == BigInteger.class) { + return Filters.unwrap(c); + } + return int.class; + } + + @Override + protected Object operate(Object o, Class c) { + c = Filters.wrap(c); + if (c == Integer.class) + return Math.floor(((Number) o).intValue()); + if (c == Float.class) + return Math.floor(((Number) o).floatValue()); + if (c == Double.class) + return Math.floor(((Number) o).doubleValue()); + if (c == Long.class) + return Math.floor(((Number) o).longValue()); + if (c == BigDecimal.class) + return ((BigDecimal) o).setScale(0, RoundingMode.FLOOR); + if (c == BigInteger.class) + return ((BigInteger) o); + + // default to int + return Math.floor(((Number) o).intValue()); + } +} 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 8e83bbe5f..00a399d8c 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 @@ -624,6 +624,41 @@ public class InMemoryExpressionFactory public Value abs(Value val) { return new Abs((Val) val); } + + @Override + public Value ceiling(Value val) { + return new Ceiling((Val) val); + } + + @Override + public Value exp(Value val) { + return new Exponential((Val) val); + } + + @Override + public Value floor(Value val) { + return new Floor((Val) val); + } + + @Override + public Value ln(Value val) { + return new NaturalLogarithm(((Val) val)); + } + + @Override + public Value sign(Value val) { + return new Sign((Val) val); + } + + @Override + public Value power(Value base, Value exponent) { + return new Power((Val) base, (Val) exponent); + } + + @Override + public Value round(Value num, Value precision) { + return new Round((Val) num, (Val) precision); + } @Override public Value indexOf(Value val1, Value val2) { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/NaturalLogarithm.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/NaturalLogarithm.java new file mode 100644 index 000000000..38bb1e805 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/NaturalLogarithm.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Natural logarithm (base e) value. + * + */ +class NaturalLogarithm + extends UnaryMathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value from which the natural logarithm should be calculated. + */ + public NaturalLogarithm(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return double.class; + } + + @Override + protected Object operate(Object o, Class c) { + return Math.log(((Number) o).doubleValue()); + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Power.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Power.java new file mode 100644 index 000000000..70c7a539f --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Power.java @@ -0,0 +1,46 @@ +/* + * 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.Filters; + +/** + * Value produced by one value being powered by another. + * + * @author Abe White + */ +class Power + extends MathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the values to divide. + */ + public Power(Val val1, Val val2) { + super(val1, val2); + } + + @Override + protected Object operate(Object o1, Class c1, Object o2, Class c2) { + return Filters.power(o1, c1, o2, c2); + } +} + diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Round.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Round.java new file mode 100644 index 000000000..ccb6fbaf2 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Round.java @@ -0,0 +1,45 @@ +/* + * 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.Filters; + +/** + * Value produced by one value being rounded to the another precision. + * + */ +class Round + extends MathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Rounds the value to a given precision. + */ + public Round(Val val1, Val val2) { + super(val1, val2); + } + + @Override + protected Object operate(Object o1, Class c1, Object o2, Class c2) { + return Filters.round(o1, c1, o2, c2); + } +} + diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Sign.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Sign.java new file mode 100644 index 000000000..0816bfc57 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Sign.java @@ -0,0 +1,59 @@ +/* + * 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 java.math.BigDecimal; +import java.math.BigInteger; + +import org.apache.openjpa.kernel.Filters; + +/** + * Take the sign of a number. + */ +class Sign + extends UnaryMathVal { + + + private static final long serialVersionUID = 1L; + + /** + * Constructor. Provide the value whose sign will be found. + */ + public Sign(Val val) { + super(val); + } + + @Override + protected Class getType(Class c) { + return int.class; + } + + @Override + protected Object operate(Object o, Class c) { + c = Filters.wrap(c); + float value = 0; + if (c == Integer.class || c == Float.class || c == Double.class || c == Long.class) + value = ((Number) o).floatValue(); + else if (c == BigDecimal.class) + value = ((BigDecimal) o).floatValue(); + else if (c == BigInteger.class) + value = ((BigInteger) o).floatValue(); + return Math.round(Math.signum(value)); + } +} 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 fb7af4277..68c71456e 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 @@ -1273,6 +1273,27 @@ public class JPQLExpressionBuilder case JJTABS: return factory.abs(getNumberValue(onlyChild(node))); + + case JJTCEILING: + return factory.ceiling(getNumberValue(onlyChild(node))); + + case JJTEXP: + return factory.exp(getNumberValue(onlyChild(node))); + + case JJTFLOOR: + return factory.floor(getNumberValue(onlyChild(node))); + + case JJTLN: + return factory.ln(getNumberValue(onlyChild(node))); + + case JJTSIGN: + return factory.sign(getNumberValue(onlyChild(node))); + + case JJTPOWER: + return factory.power(getNumberValue(firstChild(node)), getNumberValue(secondChild(node))); + + case JJTROUND: + return factory.round(getNumberValue(firstChild(node)), getNumberValue(secondChild(node))); case JJTSQRT: return factory.sqrt(getNumberValue(onlyChild(node))); 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 8ee0aa099..a14aec2ea 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 @@ -208,6 +208,13 @@ TOKEN [ IGNORE_CASE ]: /* functions returning numerics */ < LENGTH: "LENGTH" > | < LOCATE: "LOCATE" > | < ABS: "ABS" > + | < CEILING: "CEILING" > + | < EXP: "EXP" > + | < FLOOR: "FLOOR" > + | < LN: "LN" > + | < SIGN: "SIGN" > + | < POWER: "POWER" > + | < ROUND: "ROUND" > | < SQRT: "SQRT" > | < MOD: "MOD" > | < SIZE: "SIZE" > @@ -1233,7 +1240,7 @@ void trim_specification() : { } void functions_returning_numerics() : { } { - length() | locate() | abs() | sqrt() | mod() | size() | index() + length() | locate() | abs() | ceiling() | exp() | floor() | ln() | sign() | power() | round() | sqrt() | mod() | size() | index() } @@ -1258,6 +1265,47 @@ void abs() #ABS : { } } +void ceiling() #CEILING : { } +{ + <CEILING> "(" arithmetic_expression() ")" + +} + +void exp() #EXP : { } +{ + <EXP> "(" arithmetic_expression() ")" + +} + +void floor() #FLOOR : { } +{ + <FLOOR> "(" arithmetic_expression() ")" + +} + +void ln() #LN : { } +{ + <LN> "(" arithmetic_expression() ")" + +} + +void sign() #SIGN : { } +{ + <SIGN> "(" arithmetic_expression() ")" + +} + +void power() #POWER : { } +{ + <POWER> "(" arithmetic_expression() <COMMA> arithmetic_expression() ")" + +} + +void round() #ROUND : { } +{ + <ROUND> "(" arithmetic_expression() <COMMA> arithmetic_expression() ")" + +} void sqrt() #SQRT : { } { @@ -1376,6 +1424,13 @@ void path_component() #IDENTIFICATIONVARIABLE : | t = <LENGTH> | t = <LOCATE> | t = <ABS> + | t = <CEILING> + | t = <EXP> + | t = <FLOOR> + | t = <LN> + | t = <SIGN> + | t = <POWER> + | t = <ROUND> | t = <SQRT> | t = <MOD> | t = <SIZE> diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/common/utils/DatabaseHelper.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/common/utils/DatabaseHelper.java new file mode 100644 index 000000000..b61ce71ca --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/common/utils/DatabaseHelper.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.common.utils; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.apache.openjpa.jdbc.sql.DBDictionary; +import org.apache.openjpa.jdbc.sql.DerbyDictionary; +import org.apache.openjpa.kernel.Broker; +import org.apache.openjpa.persistence.JPAFacadeHelper; + +import jakarta.persistence.EntityManager; + +/** + * Allows augmentation of databases, if they don't have support to some + * necessary functions, such as DerbyDb's lack of POWER and ROUND + */ +public class DatabaseHelper { + + private static final String CREATE_DERBYDB_POWER_FUNCTION_SQL = "CREATE FUNCTION POWER(a DOUBLE, b DOUBLE) " + + "RETURNS DOUBLE PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'java.lang.Math.pow'"; + + private static final String DROP_DERBYDB_POWER_FUNCTION_SQL = "DROP FUNCTION POWER"; + + private static final String CREATE_DERBYDB_ROUND_FUNCTION_SQL = "CREATE FUNCTION ROUND(a DOUBLE, b INTEGER) " + + "RETURNS DOUBLE PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA " + + "EXTERNAL NAME 'org.apache.openjpa.persistence.common.utils.DatabaseHelper.roundFunction'"; + + private static final String DROP_DERBYDB_ROUND_FUNCTION_SQL = "DROP FUNCTION ROUND"; + + /** + * Creates the POWER function on DerbyDB, ignoring exceptions if it already exists. + * + */ + public static void createPowerFunctionIfNecessary(EntityManager em, DBDictionary dict) { + if (dict instanceof DerbyDictionary) { + try { + exec(em, true, 10, CREATE_DERBYDB_POWER_FUNCTION_SQL); + } catch (Exception ex) { + // swallowing because the function probably already exists and any exceptions + // should have been ignored on exec. + } + } + } + + /** + * Drops the POWER function on DerbyDB. + * + */ + public static void dropPowerFunction(EntityManager em, DBDictionary dict) { + if (dict instanceof DerbyDictionary) { + try { + exec(em, true, 10, DROP_DERBYDB_POWER_FUNCTION_SQL); + } catch (Exception ex) { + // swallowing because this is just a clean-up + } + } + } + + /** + * Creates the ROUND function on DerbyDB, ignoring exceptions if it already exists. + * + */ + public static void createRoundFunctionIfNecessary(EntityManager em, DBDictionary dict) { + if (dict instanceof DerbyDictionary) { + try { + exec(em, true, 10, CREATE_DERBYDB_ROUND_FUNCTION_SQL); + } catch (Exception ex) { + // swallowing because the function probably already exists and any exceptions + // should have been ignored on exec. + } + } + } + + public static void dropRoundFunction(EntityManager em, DBDictionary dict) { + if (dict instanceof DerbyDictionary) { + try { + exec(em, true, 10, DROP_DERBYDB_ROUND_FUNCTION_SQL); + } catch (Exception ex) { + // swallowing because this is just a clean-up + } + } + + } + + /** + * Rounds the number to given precision. + */ + public static double roundFunction(Double num, int precision) { + BigDecimal db = new BigDecimal(Double.toString(num)); + return db.setScale(precision, RoundingMode.HALF_EVEN).doubleValue(); + } + + /** + * Convenience method to execute SQL statements. Does not close EntityManager + * after executing. + * + * @param em EntityManager whose connection will be extracted + * @param ignoreExceptions indicate if exceptions should be ignored during executions + * @param timeoutSecs timeout, in seconds, of execution + * @param sql SQL to be executed + * @throws SQLException + */ + static void exec(EntityManager em, boolean ignoreExceptions, int timeoutSecs, String sql) + throws SQLException { + Statement s = null; + try { + assertNotNull(em); + Broker broker = JPAFacadeHelper.toBroker(em); + Connection conn = (Connection) broker.getConnection(); + s = conn.createStatement(); + if (timeoutSecs > 0) { + s.setQueryTimeout(timeoutSecs); + } + s.execute(sql); + } catch (SQLException sqe) { + if (!ignoreExceptions) { + // fail(sqe.toString()); + throw sqe; + } + } finally { + if (s != null) { + try { + s.close(); + } catch (Exception e) { + // ignore + } + } + } + } + + +} 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 09b313327..80ddda975 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 @@ -49,6 +49,7 @@ import jakarta.persistence.metamodel.Metamodel; import org.apache.openjpa.jdbc.sql.AbstractSQLServerDictionary; import org.apache.openjpa.jdbc.sql.OracleDictionary; +import org.apache.openjpa.persistence.common.utils.DatabaseHelper; import org.apache.openjpa.persistence.test.AllowFailure; /** @@ -185,6 +186,80 @@ public class TestTypesafeCriteria extends CriteriaTest { assertEquivalence(c, jpql); } + public void testCeilingExpression() { + String jpql = "select a from Account a where ceiling(a.balance)=100"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.ceiling(account.get(Account_.balance)), 100)); + assertEquivalence(c, jpql); + } + + public void testExponentialExpression() { + String jpql = "select a from Account a where exp(a.balance)=100"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.exp(account.get(Account_.balance)), 100)); + assertEquivalence(c, jpql); + } + + public void testFloorExpression() { + String jpql = "select a from Account a where floor(a.balance)=100"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.floor(account.get(Account_.balance)), 100)); + assertEquivalence(c, jpql); + } + + public void testLnExpression() { + String jpql = "select a from Account a where ln(a.balance)=100"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.ln(account.get(Account_.balance)), 100)); + assertEquivalence(c, jpql); + } + + public void testSignExpression() { + String jpql = "select a from Account a where sign(a.balance)=1"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.sign(account.get(Account_.balance)), 1)); + assertEquivalence(c, jpql); + } + + public void testPowerExpression() { + DatabaseHelper.createPowerFunctionIfNecessary(getEntityManager(), getDictionary()); + String jpql = "select a from Account a where power(a.balance, 7)=1"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.power(account.get(Account_.balance), 7), 1)); + assertEquivalence(c, jpql); + DatabaseHelper.dropPowerFunction(getEntityManager(), getDictionary()); + } + + public void testRoundExpression() { + DatabaseHelper.createRoundFunctionIfNecessary(getEntityManager(), getDictionary()); + String jpql = "select a from Account a where round(a.balance, 1)=1"; + + CriteriaQuery<Account> c = cb.createQuery(Account.class); + Root<Account> account = c.from(Account.class); + + c.select(account).where(cb.equal(cb.round(account.get(Account_.balance), 1), 1)); + assertEquivalence(c, jpql); + DatabaseHelper.dropRoundFunction(getEntityManager(), getDictionary()); + } + public void testAvgExpression() { String jpql = "select avg(a.balance) from Account a"; @@ -1645,4 +1720,5 @@ public class TestTypesafeCriteria extends CriteriaTest { 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 c337e0f8a..845f84276 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 @@ -18,12 +18,14 @@ */ package org.apache.openjpa.persistence.jpql.functions; +import java.math.BigDecimal; import java.util.List; import jakarta.persistence.EntityManager; import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.sql.DBDictionary; +import org.apache.openjpa.jdbc.sql.DerbyDictionary; import org.apache.openjpa.jdbc.sql.OracleDictionary; import org.apache.openjpa.jdbc.sql.SybaseDictionary; import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI; @@ -32,6 +34,7 @@ import org.apache.openjpa.persistence.common.apps.CompUser; import org.apache.openjpa.persistence.common.apps.FemaleUser; import org.apache.openjpa.persistence.common.apps.MaleUser; import org.apache.openjpa.persistence.common.utils.AbstractTestCase; +import org.apache.openjpa.persistence.common.utils.DatabaseHelper; public class TestEJBQLFunction extends AbstractTestCase { @@ -85,11 +88,25 @@ public class TestEJBQLFunction extends AbstractTestCase { if(dict instanceof SybaseDictionary) { expectedShannonName="Shannon"; } + DatabaseHelper.createPowerFunctionIfNecessary(em, dict); + DatabaseHelper.createRoundFunctionIfNecessary(em, dict); endTx(em); endEm(em); } + @Override + public void tearDown() throws Exception { + OpenJPAEntityManagerSPI em = (OpenJPAEntityManagerSPI) currentEntityManager(); + + DBDictionary dict = ((JDBCConfiguration) em.getConfiguration()).getDBDictionaryInstance(); + DatabaseHelper.dropPowerFunction(em, dict); + DatabaseHelper.dropRoundFunction(em, dict); + + endEm(em); + super.tearDown(); + } + public void testConcatSubStringFunc() { EntityManager em = currentEntityManager(); startTx(em); @@ -491,6 +508,132 @@ public class TestEJBQLFunction extends AbstractTestCase { endEm(em); } + + public void testCEILINGFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT CEILING(SUM(c.age) + 0.4) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(154L, ((BigDecimal) result.get(0)).longValue()); + + endEm(em); + } + + public void testCEILINGFuncNegative() { + EntityManager em = currentEntityManager(); + + String query = "SELECT CEILING(0.4 - SUM(c.age)) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(-152L, ((BigDecimal) result.get(0)).longValue()); + + endEm(em); + } + + public void testEXPFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT EXP(MIN(c.age)) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(Math.exp(10), (double) result.get(0)); + + endEm(em); + } + + public void testFLOORFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT FLOOR(SUM(c.age) - 0.4) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(152L, ((BigDecimal) result.get(0)).longValue()); + + endEm(em); + } + + public void testFLOORFuncNegative() { + EntityManager em = currentEntityManager(); + + String query = "SELECT FLOOR(10.4 - SUM(c.age)) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(-143L, ((BigDecimal) result.get(0)).longValue()); + + endEm(em); + } + + public void testPOWERFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT POWER(MIN(c.age), 3) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(1000L, result.get(0)); + + endEm(em); + } + + public void testROUNDFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT ROUND(SQRT(MIN(c.age)), 3) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(3.162, (double) result.get(0)); + + endEm(em); + } + + public void testSIGNFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT SIGN(1 - SUM(c.age)) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(-1, ((Integer) result.get(0)).intValue()); + + endEm(em); + } + + public void testLNFunc() { + EntityManager em = currentEntityManager(); + + String query = "SELECT LN(MIN(c.age)) FROM CompUser c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(Math.log(10), (double) 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 7d109f25e..c9c0e2bf0 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 @@ -153,6 +153,46 @@ public class CriteriaBuilderImpl implements OpenJPACriteriaBuilder, ExpressionPa public <N extends Number> Expression<N> abs(Expression<N> x) { return new Expressions.Abs<>(x); } + + @Override + public <N extends Number> Expression<N> ceiling(Expression<N> x) { + return new Expressions.Ceiling<>(x); + } + + @Override + public Expression<Double> exp(Expression<? extends Number> x) { + return new Expressions.Exponential(x); + } + + @Override + public <N extends Number> Expression<N> floor(Expression<N> x) { + return new Expressions.Floor<>(x); + } + + @Override + public Expression<Double> ln(Expression<? extends Number> x) { + return new Expressions.NaturalLogarithm(x); + } + + @Override + public Expression<Integer> sign(Expression<? extends Number> x) { + return new Expressions.Sign(x); + } + + @Override + public Expression<Double> power(Expression<? extends Number> x, Number y) { + return new Expressions.Power<>(x, y); + } + + @Override + public Expression<Double> power(Expression<? extends Number> x, Expression<? extends Number> y) { + return new Expressions.Power<>(x, y); + } + + @Override + public <T extends Number> Expression<T> round(Expression<T> x, Integer n) { + return new Expressions.Round<>(x, new Expressions.Constant<>(n)); + } @Override public <Y> Expression<Y> all(Subquery<Y> subquery) { @@ -988,30 +1028,6 @@ public class CriteriaBuilderImpl implements OpenJPACriteriaBuilder, ExpressionPa return new ComparisonStyle.Default(); } - @Override - public <N extends Number> Expression<N> ceiling(Expression<N> x) { - // TODO Implement ceiling op - throw new UnsupportedOperationException(); - } - - @Override - public Expression<Double> exp(Expression<? extends Number> x) { - // TODO Implement exp op - throw new UnsupportedAddressTypeException(); - } - - @Override - public <N extends Number> Expression<N> floor(Expression<N> x) { - // TODO Implement floor op - throw new UnsupportedOperationException(); - } - - @Override - public Expression<Double> ln(Expression<? extends Number> x) { - // TODO Implement ln op - throw new UnsupportedOperationException(); - } - @Override public Expression<LocalDate> localDate() { // TODO Implement localDate @@ -1030,27 +1046,4 @@ public class CriteriaBuilderImpl implements OpenJPACriteriaBuilder, ExpressionPa throw new UnsupportedOperationException(); } - @Override - public Expression<Double> power(Expression<? extends Number> x, Number y) { - // TODO Implement power op - throw new UnsupportedOperationException(); - } - - @Override - public Expression<Double> power(Expression<? extends Number> x, Expression<? extends Number> y) { - // TODO Implement power op - throw new UnsupportedOperationException(); - } - - @Override - public <T extends Number> Expression<T> round(Expression<T> x, Integer n) { - // TODO Implement round op - throw new UnsupportedOperationException(); - } - - @Override - public Expression<Integer> sign(Expression<? extends Number> x) { - // TODO Implement sign op - throw new UnsupportedOperationException(); - } } 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 f7ca4633b..e8502a5b0 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 @@ -298,6 +298,139 @@ class Expressions { } } + public static class Ceiling<X> extends UnaryFunctionalExpression<X> { + public Ceiling(Expression<X> x) { + super(x); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.ceiling(Expressions.toValue(e, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "CEILING", OPEN_BRACE, e, CLOSE_BRACE); + } + } + + public static class Exponential extends UnaryFunctionalExpression<Double> { + public Exponential(Expression<? extends Number> x) { + super(Double.class, x); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.exp(Expressions.toValue(e, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "EXP", OPEN_BRACE, e, CLOSE_BRACE); + } + } + + public static class Floor<X> extends UnaryFunctionalExpression<X> { + public Floor(Expression<X> x) { + super(x); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.floor(Expressions.toValue(e, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "FLOOR", OPEN_BRACE, e, CLOSE_BRACE); + } + } + + public static class NaturalLogarithm extends UnaryFunctionalExpression<Double> { + public NaturalLogarithm(Expression<? extends Number> x) { + super(Double.class, x); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.ln(Expressions.toValue(e, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "LN", OPEN_BRACE, e, CLOSE_BRACE); + } + } + + public static class Sign extends UnaryFunctionalExpression<Integer> { + public Sign(Expression<? extends Number> x) { + super(Integer.class, x); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.sign(Expressions.toValue(e, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "SIGN", OPEN_BRACE, e, CLOSE_BRACE); + } + } + + public static class Power<X, Y extends Number> extends BinarayFunctionalExpression<Double> { + public Power(Expression<X> x, Expression<Y> y) { + super(double.class, x, y); + } + public Power(Expression<X> x, Y y) { + this(x,new Constant<>(y)); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.power( + Expressions.toValue(e1, factory, q), + Expressions.toValue(e2, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "POWER", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE); + } + } + + public static class Round<X> extends BinarayFunctionalExpression<X> { + public Round(Expression<?> x, Expression<?> y) { + super(((Class<X>) x.getJavaType()), x, y); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + Value value = factory.round( + Expressions.toValue(e1, factory, q), + Expressions.toValue(e2, factory, q)); + value.setImplicitType(getJavaType()); + return value; + } + + @Override + public StringBuilder asValue(AliasContext q) { + return Expressions.asValue(q, "ROUND", OPEN_BRACE, e1, COMMA, e2, CLOSE_BRACE); + } + } + public static class Count extends UnaryFunctionalExpression<Long> { private boolean _distinct; public Count(Expression<?> x) { diff --git a/openjpa-project/src/doc/manual/jpa_overview_query.xml b/openjpa-project/src/doc/manual/jpa_overview_query.xml index c338634b6..f681f7374 100644 --- a/openjpa-project/src/doc/manual/jpa_overview_query.xml +++ b/openjpa-project/src/doc/manual/jpa_overview_query.xml @@ -565,6 +565,64 @@ SELECT x FROM Magazine x WHERE LOCATE('D', x.title) = 2 </para> <programlisting> SELECT x FROM Magazine x WHERE ABS(x.price) >= 5.00 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + CEILING function + </primary> + </indexterm> + +<literal>CEILING(number)</literal>: Returns the smallest (closest to negative infinity) + value that is greater than or equal to the argument and is equal to a mathematical integer. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE CEILING(x.price) >= 10 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + EXP function + </primary> + </indexterm> + +<literal>EXP(number)</literal>: Returns the Euler's number <literal>e</literal> raised to the power of a value. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE EXP(x.price) >= 10 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + FLOOR function + </primary> + </indexterm> + +<literal>FLOOR(number)</literal>: Returns the largest (closest to positive infinity) + value that is less than or equal to the argument and is equal to a mathematical integer. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE FLOOR(x.price) >= 10 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + LN function + </primary> + </indexterm> + +<literal>LN(number)</literal>: Returns the natural logarithm (base <litera>e</literal>) of a value. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE LN(x.price) >= 10 </programlisting> </listitem> <listitem> @@ -574,6 +632,7 @@ SELECT x FROM Magazine x WHERE ABS(x.price) >= 5.00 SQRT function </primary> </indexterm> + <literal>SQRT(number)</literal>: Returns the square root of the argument. </para> <programlisting> @@ -592,6 +651,48 @@ SELECT x FROM Magazine x WHERE SQRT(x.price) >= 1.00 </para> <programlisting> SELECT x FROM Magazine x WHERE MOD(x.price, 10) = 0 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + POWER function + </primary> + </indexterm> + +<literal>POWER(base, exponent)</literal>: Returns the value of the first argument raised to the power of the second argument. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE POWER(x.price, 2) >= 10 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + ROUND function + </primary> + </indexterm> + +<literal>ROUND(number, precision)</literal>: Rounds the number to given precision. + </para> +<programlisting> +SELECT x FROM Magazine x WHERE ROUND(x.price, 2) >= 10 +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + SIGN function + </primary> + </indexterm> + +<literal>SIGN(number)</literal>: Returns the sign (-1, 0, 1) of a number + </para> +<programlisting> +SELECT x FROM Magazine x WHERE SIGN(x.price) >= 0 </programlisting> </listitem> <listitem> @@ -3365,6 +3466,13 @@ that represents the string position at which the search is started (by default, the beginning of the string to be searched). The first position in a string is denoted by 1. If the string is not found, 0 is returned. </para> + <para> +Note that not all databases support the use of a trim character other than the +space character; use of this argument may result in queries that are not +portable. Note that not all databases support the use of the third argument to +<literal>LOCATE</literal>; use of this argument may result in queries that are +not portable. + </para> <para> The <literal>LENGTH </literal> function returns the length of the string in characters as an @@ -3378,33 +3486,48 @@ integer. <para> <itemizedlist><listitem><para>functions_returning_numerics ::= ABS(simple_arithmetic_expression) | +CEILING(arithmetic_expression) | +EXP(arithmetic_expression) | +FLOOR(arithmetic_expression) | +LN(arithmetic_expression) | +POWER(arithmetic_expression, arithmetic_expression) | +ROUND(arithmetic_expression, arithmetic_expression) | +SIGN(arithmetic_expression) | SQRT(simple_arithmetic_expression) | MOD(simple_arithmetic_expression, simple_arithmetic_expression) | SIZE(collection_valued_path_expression) | -INDEX(identification_variable) +INDEX(identification_variable) | +extract_datetime_field </para> </listitem> </itemizedlist> </para> <para> -The <literal>ABS</literal> function takes a numeric argument and returns a -number (integer, float, or double) of the same type as the argument to the -function. +The <literal>ABS</literal>, <literal>CEILING</literal> and <literal>FLOOR</literal> functions +accept a numeric argument and return a number (integer, float, or double) of the same type as +the argument. </para> <para> -The <literal>SQRT</literal> function takes a numeric argument and -returns a double. +The <literal>SIGN</literal> function accepts a numeric argument and +returns an integer. </para> <para> -Note that not all databases support the use of a trim character other than the -space character; use of this argument may result in queries that are not -portable. Note that not all databases support the use of the third argument to -<literal>LOCATE</literal>; use of this argument may result in queries that are -not portable. +The <literal>SQRT</literal>, <literal>EXP</literal> and <literal>LN</literal> functions accept +a numeric argument and return a double. </para> <para> -The <literal>MOD</literal> function takes two integer arguments and returns an -integer. +The <literal>MOD</literal> function takes two integer arguments and returns an integer. + </para> + <para> +The <literal>ROUND</literal> function accepts a numeric argument and an integer argument and +returns a number of the same type as the first argument. + </para> + <para> +The <literal>POWER<literal> function accepts two numeric arguments and returns a double. + </para> + <para> +Numeric arguments to these functions may correspond to the numeric Java object types as well +as the primitive numeric types. </para> <para> The <literal>SIZE</literal> function returns an integer value, the