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 141bcd6e8 [OPENJPA-2933] Implements new date JPA 3.1 JPQL functions and equivalent Criteria API (#123) 141bcd6e8 is described below commit 141bcd6e8e61486a4f1d653ec040c6bdc7976745 Author: Paulo Cristovão de Araújo Silva Filho <pcris...@gmail.com> AuthorDate: Sat Feb 8 10:49:14 2025 -0300 [OPENJPA-2933] Implements new date JPA 3.1 JPQL functions and equivalent Criteria API (#123) * OPENJPA-2908 * Bumped jakarta persistence API to 3.1.0 * Added necessary stub impl of methods (throws UnsupportedOperationException) * Tests passes, but actual impls must be made in probably different issues, one for each new features (https://jakarta.ee/specifications/persistence/3.1/jakarta-persistence-spec-3.1#jakarta-persistence-3-1) * [OPENJPA-2933] Implements LOCAL DATE, LOCAL TIME, LOCAL DATETIME and EXTRACT functions * Added JPA 3.1 date/time JPQL new functions LOCAL DATE, LOCAL TIME and LOCAL DATETIME and Criteria equivalents * Added JPQL EXTRACT date/time field and EXTRACT date/time (as a CAST) JPQL functions * Added tests to verify validity of BNF changes in JPQL.jjt and each new function * [OPENJPA-2933] Fixing typo and postgresql extract function * [OPENJPA-2933] Decreasing lookahead instruction of extract for better performance * [OPENJPA-2933] Finishing implementation * Updated round function usage and test due a corner case on postgresql tests * Made EXTRACT function test be resistent to date/time divergence between db and test environments * Updated manual BNF and Date Time functions chapters --- .../openjpa/jdbc/kernel/exps/CurrentTemporal.java | 97 ++++ .../exps/{Math.java => ExtractDateTimeField.java} | 112 ++-- .../exps/{Math.java => ExtractDateTimePart.java} | 108 ++-- .../jdbc/kernel/exps/JDBCExpressionFactory.java | 22 +- .../org/apache/openjpa/jdbc/kernel/exps/Math.java | 4 + .../org/apache/openjpa/jdbc/kernel/exps/Val.java | 2 + .../org/apache/openjpa/jdbc/sql/DBDictionary.java | 1 + .../openjpa/kernel/exps/CurrentTemporal.java | 62 +++ .../openjpa/kernel/exps/DateTimeExtractField.java | 75 +++ .../openjpa/kernel/exps/DateTimeExtractPart.java | 35 ++ .../openjpa/kernel/exps/ExpressionFactory.java | 17 + .../openjpa/kernel/exps/ExtractDateTimeField.java | 100 ++++ .../openjpa/kernel/exps/ExtractDateTimePart.java | 112 ++++ .../kernel/exps/InMemoryExpressionFactory.java | 16 + .../openjpa/kernel/jpql/JPQLExpressionBuilder.java | 60 +++ .../jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt | 78 ++- .../apache/openjpa/kernel/jpql/TestJPQLParser.java | 74 +++ .../TestContainerSpecCompatibilityOptions.java | 2 +- .../compat/TestSpecCompatibilityOptions.java | 2 +- .../persistence/criteria/TestTypesafeCriteria.java | 63 ++- .../jpql/functions/TestEJBQLFunction.java | 233 +++++++- .../persistence/simple/TestJava8TimeTypes.java | 30 ++ .../test/AbstractPersistenceTestCase.java | 2 +- .../persistence/criteria/CriteriaBuilderImpl.java | 9 +- .../openjpa/persistence/criteria/Expressions.java | 51 ++ .../src/doc/manual/jpa_overview_query.xml | 594 ++++++++++++++++----- 26 files changed, 1686 insertions(+), 275 deletions(-) diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CurrentTemporal.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CurrentTemporal.java new file mode 100644 index 000000000..58b909b92 --- /dev/null +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/CurrentTemporal.java @@ -0,0 +1,97 @@ +/* + * 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.Date; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; + +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.util.InternalException; + +/** + * A literal current LOCALDATE, LOCALTIME or LOCALDATETIME value in a filter. + * + */ +class CurrentTemporal + extends Const { + + + private static final long serialVersionUID = 1L; + private final Class<? extends Temporal> _type; + + public CurrentTemporal(Class<? extends Temporal> type) { + _type = type; + } + + @Override + public Class<? extends Temporal> getType() { + return _type; + } + + @Override + public void setImplicitType(Class type) { + } + + @Override + public Object load(ExpContext ctx, ExpState state, Result res) throws SQLException { + if (LocalDateTime.class.isAssignableFrom(_type)) { + return LocalDateTime.ofInstant(res.getTimestamp(this, null).toInstant(), ZoneId.systemDefault()); + } else if (LocalTime.class.isAssignableFrom(_type)) { + return res.getTime(this, null).toLocalTime(); + } else if (LocalDate.class.isAssignableFrom(_type)) { + return res.getDate(this, null).toLocalDate(); + } else { + throw new InternalException(); + } + } + + @Override + public Object getValue(Object[] params) { + if (LocalDateTime.class.isAssignableFrom(_type)) { + return LocalDateTime.now(); + } else if (LocalDate.class.isAssignableFrom(_type)) { + return LocalDate.now(); + } else if (LocalTime.class.isAssignableFrom(_type)) { + return LocalTime.now(); + } + return null; + } + + @Override + public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) { + if (LocalDateTime.class.isAssignableFrom(_type)) { + sql.append(ctx.store.getDBDictionary().currentTimestampFunction); + } else if (LocalTime.class.isAssignableFrom(_type)) { + sql.append(ctx.store.getDBDictionary().currentTimeFunction); + } else if (LocalDate.class.isAssignableFrom(_type)) { + sql.append(ctx.store.getDBDictionary().currentDateFunction); + } else { + throw new InternalException(); + } + } +} 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/ExtractDateTimeField.java similarity index 59% copy from openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java copy to openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ExtractDateTimeField.java index dfbab290d..9fe2a99ee 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/ExtractDateTimeField.java @@ -18,59 +18,42 @@ */ package org.apache.openjpa.jdbc.kernel.exps; +import java.lang.Math; import java.sql.SQLException; import org.apache.openjpa.jdbc.meta.JavaSQLTypes; +import org.apache.openjpa.jdbc.sql.DBDictionary; +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.DateTimeExtractField; import org.apache.openjpa.kernel.exps.ExpressionVisitor; import org.apache.openjpa.meta.ClassMetaData; /** - * Value produced by a mathematical operation on two values. + * Returns the temporal field of a given date or time. * - * @author Abe White */ -public class Math +public class ExtractDateTimeField extends AbstractVal { - private static final long serialVersionUID = 1L; - public static final String ADD = "+"; - public static final String SUBTRACT = "-"; - 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; - private final String _op; + private final Val _val; + private final DateTimeExtractField _field; private ClassMetaData _meta = null; - private Class _cast = null; /** - * Constructor. Provide the values to operate on, and the operator. + * Constructor. Provide the date and the field to operate on. */ - public Math(Val val1, Val val2, String op) { - _val1 = val1; - _val2 = val2; - _op = op; + public ExtractDateTimeField(Val val, DateTimeExtractField field) { + _val = val; + _field = field; } - public Val getVal1() { - return _val1; - } - - public Val getVal2() { - return _val2; - } - - public String getOperation() { - return _op; + public Val getVal() { + return _val; } @Override @@ -85,23 +68,31 @@ public class Math @Override public Class getType() { - if (_cast != null) - return _cast; - Class c1 = _val1.getType(); - Class c2 = _val2.getType(); - return Filters.promote(c1, c2); + return _field == DateTimeExtractField.SECOND ? float.class : int.class; } @Override public void setImplicitType(Class type) { - _cast = type; } @Override public ExpState initialize(Select sel, ExpContext ctx, int flags) { - ExpState s1 = _val1.initialize(sel, ctx, 0); - ExpState s2 = _val2.initialize(sel, ctx, 0); - return new BinaryOpExpState(sel.and(s1.joins, s2.joins), s1, s2); + ExpState valueState = _val.initialize(sel, ctx, 0); + return new ExtractDateTimeFieldExpState(valueState.joins, valueState); + } + + /** + * Expression state. + */ + private static class ExtractDateTimeFieldExpState + extends ExpState { + + public final ExpState valueState; + + public ExtractDateTimeFieldExpState(Joins joins, ExpState valueState) { + super(joins); + this.valueState = valueState; + } } @Override @@ -111,11 +102,9 @@ public class Math } @Override - public void selectColumns(Select sel, ExpContext ctx, ExpState state, - boolean pks) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - _val1.selectColumns(sel, ctx, bstate.state1, true); - _val2.selectColumns(sel, ctx, bstate.state2, true); + public void selectColumns(Select sel, ExpContext ctx, ExpState state, boolean pks) { + ExtractDateTimeFieldExpState edtstate = (ExtractDateTimeFieldExpState) state; + _val.selectColumns(sel, ctx, edtstate.valueState, true); } @Override @@ -139,16 +128,15 @@ public class Math @Override public Object load(ExpContext ctx, ExpState state, Result res) throws SQLException { - return Filters.convert(res.getObject(this, JavaSQLTypes.JDBC_DEFAULT, - null), getType()); + return Filters.convert(res.getObject(this, + JavaSQLTypes.JDBC_DEFAULT, null), getType()); } @Override public void calculateValue(Select sel, ExpContext ctx, ExpState state, Val other, ExpState otherState) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - _val1.calculateValue(sel, ctx, bstate.state1, _val2, bstate.state2); - _val2.calculateValue(sel, ctx, bstate.state2, _val1, bstate.state1); + ExtractDateTimeFieldExpState edtstate = (ExtractDateTimeFieldExpState) state; + _val.calculateValue(sel, ctx, edtstate.valueState, null, null); } @Override @@ -159,23 +147,33 @@ public class Math @Override public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - ctx.store.getDBDictionary().mathFunction(sql, _op, - new FilterValueImpl(sel, ctx, bstate.state1, _val1), - new FilterValueImpl(sel, ctx, bstate.state2, _val2)); + DBDictionary dict = ctx.store.getDBDictionary(); + String func = dict.extractDateTimeFieldFunction; + + int fieldPart = func.indexOf("{0}"); + int fromPart = func.indexOf("{1}"); + String part1 = func.substring(0, fieldPart); + String part2 = func.substring(fieldPart + 3, fromPart); + String part3 = func.substring(fromPart + 3); + + ExtractDateTimeFieldExpState edtstate = (ExtractDateTimeFieldExpState) state; + sql.append(part1); + sql.append(_field.name()); + sql.append(part2); + _val.appendTo(sel, ctx, edtstate.valueState, sql, 0); + sql.append(part3); } @Override public void acceptVisit(ExpressionVisitor visitor) { visitor.enter(this); - _val1.acceptVisit(visitor); - _val2.acceptVisit(visitor); + _val.acceptVisit(visitor); visitor.exit(this); } @Override public int getId() { - return Val.MATH_VAL; + return Val.EXTRACTDTF_VAL; } } 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/ExtractDateTimePart.java similarity index 60% copy from openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Math.java copy to openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ExtractDateTimePart.java index dfbab290d..1634a5a2c 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/ExtractDateTimePart.java @@ -18,59 +18,43 @@ */ package org.apache.openjpa.jdbc.kernel.exps; +import java.sql.Date; import java.sql.SQLException; +import java.sql.Time; import org.apache.openjpa.jdbc.meta.JavaSQLTypes; +import org.apache.openjpa.jdbc.sql.DBDictionary; +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.DateTimeExtractPart; import org.apache.openjpa.kernel.exps.ExpressionVisitor; import org.apache.openjpa.meta.ClassMetaData; /** - * Value produced by a mathematical operation on two values. + * Returns the date or time part of a given temporal. * - * @author Abe White */ -public class Math +public class ExtractDateTimePart extends AbstractVal { - private static final long serialVersionUID = 1L; - public static final String ADD = "+"; - public static final String SUBTRACT = "-"; - 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; - private final String _op; + private final Val _val; + private final DateTimeExtractPart _part; private ClassMetaData _meta = null; - private Class _cast = null; /** - * Constructor. Provide the values to operate on, and the operator. + * Constructor. Provide the date and the field to operate on. */ - public Math(Val val1, Val val2, String op) { - _val1 = val1; - _val2 = val2; - _op = op; + public ExtractDateTimePart(Val val, DateTimeExtractPart part) { + _val = val; + _part = part; } - public Val getVal1() { - return _val1; - } - - public Val getVal2() { - return _val2; - } - - public String getOperation() { - return _op; + public Val getVal() { + return _val; } @Override @@ -85,23 +69,36 @@ public class Math @Override public Class getType() { - if (_cast != null) - return _cast; - Class c1 = _val1.getType(); - Class c2 = _val2.getType(); - return Filters.promote(c1, c2); + if (_part == DateTimeExtractPart.TIME) { + return Time.class; + } else if (_part == DateTimeExtractPart.DATE) { + return Date.class; + } + throw new IllegalStateException(); } @Override public void setImplicitType(Class type) { - _cast = type; } @Override public ExpState initialize(Select sel, ExpContext ctx, int flags) { - ExpState s1 = _val1.initialize(sel, ctx, 0); - ExpState s2 = _val2.initialize(sel, ctx, 0); - return new BinaryOpExpState(sel.and(s1.joins, s2.joins), s1, s2); + ExpState valueState = _val.initialize(sel, ctx, 0); + return new ExtractDateTimePartExpState(valueState.joins, valueState); + } + + /** + * Expression state. + */ + private static class ExtractDateTimePartExpState + extends ExpState { + + public final ExpState valueState; + + public ExtractDateTimePartExpState(Joins joins, ExpState valueState) { + super(joins); + this.valueState = valueState; + } } @Override @@ -111,11 +108,9 @@ public class Math } @Override - public void selectColumns(Select sel, ExpContext ctx, ExpState state, - boolean pks) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - _val1.selectColumns(sel, ctx, bstate.state1, true); - _val2.selectColumns(sel, ctx, bstate.state2, true); + public void selectColumns(Select sel, ExpContext ctx, ExpState state, boolean pks) { + ExtractDateTimePartExpState edtstate = (ExtractDateTimePartExpState) state; + _val.selectColumns(sel, ctx, edtstate.valueState, true); } @Override @@ -139,16 +134,15 @@ public class Math @Override public Object load(ExpContext ctx, ExpState state, Result res) throws SQLException { - return Filters.convert(res.getObject(this, JavaSQLTypes.JDBC_DEFAULT, - null), getType()); + return Filters.convert(res.getObject(this, + JavaSQLTypes.JDBC_DEFAULT, null), getType()); } @Override public void calculateValue(Select sel, ExpContext ctx, ExpState state, Val other, ExpState otherState) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - _val1.calculateValue(sel, ctx, bstate.state1, _val2, bstate.state2); - _val2.calculateValue(sel, ctx, bstate.state2, _val1, bstate.state1); + ExtractDateTimePartExpState edtstate = (ExtractDateTimePartExpState) state; + _val.calculateValue(sel, ctx, edtstate.valueState, null, null); } @Override @@ -159,23 +153,23 @@ public class Math @Override public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) { - BinaryOpExpState bstate = (BinaryOpExpState) state; - ctx.store.getDBDictionary().mathFunction(sql, _op, - new FilterValueImpl(sel, ctx, bstate.state1, _val1), - new FilterValueImpl(sel, ctx, bstate.state2, _val2)); + ExtractDateTimePartExpState edtstate = (ExtractDateTimePartExpState) state; + sql.append("CAST( "); + _val.appendTo(sel, ctx, edtstate.valueState, sql, 0); + sql.append(" AS "); + sql.append(_part == DateTimeExtractPart.DATE ? "DATE)" : "TIME)"); } @Override public void acceptVisit(ExpressionVisitor visitor) { visitor.enter(this); - _val1.acceptVisit(visitor); - _val2.acceptVisit(visitor); + _val.acceptVisit(visitor); visitor.exit(this); } @Override public int getId() { - return Val.MATH_VAL; + return Val.EXTRACTDTP_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 41e6f5b30..55812de59 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 @@ -19,6 +19,7 @@ package org.apache.openjpa.jdbc.kernel.exps; import java.io.Serializable; +import java.time.temporal.Temporal; import java.util.Date; import org.apache.openjpa.jdbc.meta.ClassMapping; @@ -28,6 +29,8 @@ import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy; import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.kernel.exps.AggregateListener; import org.apache.openjpa.kernel.exps.Arguments; +import org.apache.openjpa.kernel.exps.DateTimeExtractField; +import org.apache.openjpa.kernel.exps.DateTimeExtractPart; import org.apache.openjpa.kernel.exps.Expression; import org.apache.openjpa.kernel.exps.ExpressionFactory; import org.apache.openjpa.kernel.exps.FilterListener; @@ -355,12 +358,27 @@ public class JDBCExpressionFactory @Override public <T extends Date> Value getCurrentTime(Class<T> dateType) { - return new CurrentDate(dateType); + return new CurrentDate(dateType); } @Override public <T extends Date> Value getCurrentTimestamp(Class<T> dateType) { - return new CurrentDate(dateType); + return new CurrentDate(dateType); + } + + @Override + public <T extends Temporal> Value getCurrentLocalDateTime(Class<T> temporalType) { + return new CurrentTemporal(temporalType); + } + + @Override + public Value getDateTimeField(DateTimeExtractField field, Value value) { + return new ExtractDateTimeField((Val) value, field); + } + + @Override + public Value getDateTimePart(DateTimeExtractPart part, Value value) { + return new ExtractDateTimePart((Val) value, part); } @Override 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 dfbab290d..5927fa446 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 @@ -59,6 +59,10 @@ public class Math _val1 = val1; _val2 = val2; _op = op; + if (op == ROUND) { + _val1.setImplicitType(Double.class); + _val2.setImplicitType(Integer.class); + } } public Val getVal1() { 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 958bb024d..ccc6aaf87 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 @@ -80,6 +80,8 @@ public interface Val int FLOOR_VAL = 21; int LN_VAL = 22; int SIGN_VAL = 23; + int EXTRACTDTF_VAL = 24; + int EXTRACTDTP_VAL = 25; /** * 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 9dbba34c9..537c6a286 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 @@ -298,6 +298,7 @@ public class DBDictionary public String currentTimeFunction = "CURRENT_TIME"; public String currentTimestampFunction = "CURRENT_TIMESTAMP"; public String dropTableSQL = "DROP TABLE {0}"; + public String extractDateTimeFieldFunction = "EXTRACT({0} FROM {1})"; // types public boolean storageLimitationsFatal = false; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CurrentTemporal.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CurrentTemporal.java new file mode 100644 index 000000000..a44431546 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CurrentTemporal.java @@ -0,0 +1,62 @@ +/* + * 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.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.Temporal; + +import org.apache.openjpa.kernel.StoreContext; + +/** + * Represents the current temporal. + * + */ +class CurrentTemporal + extends Val { + + private static final long serialVersionUID = 1L; + private final Class<? extends Temporal> _type; + + public CurrentTemporal(Class<? extends Temporal> type) { + _type = type; + } + + @Override + public Class getType() { + return _type; + } + + @Override + public void setImplicitType(Class type) { + } + + @Override + protected Object eval(Object candidate, Object orig, StoreContext ctx, Object[] params) { + if (LocalDateTime.class.isAssignableFrom(_type)) { + return LocalDateTime.now(); + } else if (LocalDate.class.isAssignableFrom(_type)) { + return LocalDate.now(); + } else if (LocalTime.class.isAssignableFrom(_type)) { + return LocalTime.now(); + } + return null; + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractField.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractField.java new file mode 100644 index 000000000..94db6b6f7 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractField.java @@ -0,0 +1,75 @@ +/* + * 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.time.temporal.ChronoField; + +/** + * Type identifiers used by EXTRACT function + */ +public enum DateTimeExtractField { + + /** + * Means the calendar year + */ + YEAR(ChronoField.YEAR), + /** + * Means the calendar quarter, numbered from 1 to 4 + */ + QUARTER, + /** + * Means the calendar month of the year, numbered from 1 + */ + MONTH(ChronoField.MONTH_OF_YEAR), + /** + * Means the ISO-8601 week number + */ + WEEK(ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR), + /** + * Means the calendar day of the month, numbered from 1 + */ + DAY(ChronoField.DAY_OF_MONTH), + /** + * Means the hour of the day in 24-hour time, numbered from 0 to 23 + */ + HOUR(ChronoField.HOUR_OF_DAY), + /** + * Mans the minute of the hour, numbered from 0 to 59 + */ + MINUTE(ChronoField.MINUTE_OF_HOUR), + /** + * Means the second of the minute, numbered from 0 to 59, including a fractional part representing fractions of a second. + */ + SECOND(ChronoField.SECOND_OF_MINUTE); + + private final ChronoField equivalent; + + private DateTimeExtractField() { + this.equivalent = null; + } + + private DateTimeExtractField(ChronoField equivalent) { + this.equivalent = equivalent; + } + + public ChronoField getEquivalent() { + return equivalent; + } + +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractPart.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractPart.java new file mode 100644 index 000000000..91d6558a6 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/DateTimeExtractPart.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Date and time identifiers used by EXTRACT DATETIME PART + */ +public enum DateTimeExtractPart { + + /** + * Means the date + */ + DATE, + /** + * Means the time + */ + TIME; + +} 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 38f433935..ded5ab1cf 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 @@ -18,6 +18,8 @@ */ package org.apache.openjpa.kernel.exps; +import java.time.temporal.ChronoField; +import java.time.temporal.Temporal; import java.util.Date; import org.apache.openjpa.meta.ClassMetaData; @@ -242,6 +244,21 @@ public interface ExpressionFactory { */ <T extends Date> Value getCurrentTimestamp(Class<T> timestampType); + /** + * Return a value representing the current local temporal. + */ + <T extends Temporal> Value getCurrentLocalDateTime(Class<T> temporalType); + + /** + * Returns the integer or double value of the required ChronoField from the temporal value + */ + Value getDateTimeField(DateTimeExtractField field, Value value); + + /** + * Return the Date or time part of the given temporal value + */ + Value getDateTimePart(DateTimeExtractPart part, Value value); + /** * Return a value representing a parameter for the given value. The * type may be <code>Object</code> if the parameter is not declared. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimeField.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimeField.java new file mode 100644 index 000000000..33c25b284 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimeField.java @@ -0,0 +1,100 @@ +/* + * 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.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.temporal.ChronoField; +import java.time.temporal.Temporal; + +import org.apache.openjpa.kernel.StoreContext; + +/** + * Extract the field value of a temporal type + * + */ +class ExtractDateTimeField + extends Val { + + + private static final long serialVersionUID = 1L; + private final DateTimeExtractField _field; + private final Val _val; + + /** + * Constructor. Provide target field and the value. + */ + public ExtractDateTimeField(DateTimeExtractField field, Val val) { + _field = field; + _val = val; + } + + @Override + public Class getType() { + if (_field == DateTimeExtractField.SECOND) { + return float.class; + } else { + return int.class; + } + } + + @Override + public void setImplicitType(Class type) { + } + + @Override + protected Object eval(Object candidate, Object orig, + StoreContext ctx, Object[] params) { + + Object r = _val.eval(candidate, orig, ctx, params); + Temporal t = null; + if (Date.class.isAssignableFrom(r.getClass())) { + t = ((Date) r).toLocalDate(); + } else if (Time.class.isAssignableFrom(r.getClass())) { + t = ((Time) r).toLocalTime(); + } else if (Timestamp.class.isAssignableFrom(r.getClass())) { + t = ((Timestamp) r).toInstant(); + } else if (Temporal.class.isAssignableFrom(r.getClass())) { + t = (Temporal) r; + } + if (t == null) { + throw new IllegalArgumentException(); + } + switch (_field) { + case QUARTER: + int month = t.get(ChronoField.MONTH_OF_YEAR); + return (int) Math.round(Math.ceil(month/3f)); + case SECOND: + int seconds = t.get(ChronoField.SECOND_OF_MINUTE); + int mili = t.get(ChronoField.MILLI_OF_SECOND); + return (float) seconds + (float) (mili/1000f); + default: + return t.get(_field.getEquivalent()); + } + } + + @Override + public void acceptVisit(ExpressionVisitor visitor) { + visitor.enter(this); + _val.acceptVisit(visitor); + visitor.exit(this); + } +} + diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimePart.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimePart.java new file mode 100644 index 000000000..86cea17a9 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/ExtractDateTimePart.java @@ -0,0 +1,112 @@ +/* + * 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.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.time.temporal.Temporal; + +import org.apache.openjpa.kernel.StoreContext; + +/** + * Extract the part value of a temporal type + * + */ +class ExtractDateTimePart + extends Val { + + + private static final long serialVersionUID = 1L; + private final DateTimeExtractPart _part; + private final Val _val; + + /** + * Constructor. Provide target field and the value. + */ + public ExtractDateTimePart(DateTimeExtractPart part, Val val) { + _part = part; + _val = val; + } + + @Override + public Class getType() { + if (_part == DateTimeExtractPart.TIME) { + return Time.class; + } else if (_part == DateTimeExtractPart.DATE) { + return Date.class; + } + throw new IllegalStateException(); + } + + @Override + public void setImplicitType(Class type) { + } + + @Override + protected Object eval(Object candidate, Object orig, + StoreContext ctx, Object[] params) { + + Object r = _val.eval(candidate, orig, ctx, params); + Class<?> clazz = r.getClass(); + if (_part == DateTimeExtractPart.TIME) { + if (Time.class.isAssignableFrom(clazz)) { + return (Time) r; + } else if (Timestamp.class.isAssignableFrom(clazz)) { + return Time.valueOf(((Timestamp) r).toLocalDateTime().toLocalTime()); + } else if (LocalDateTime.class.isAssignableFrom(clazz)) { + return Time.valueOf(((LocalDateTime) r).toLocalTime()); + } else if (LocalTime.class.isAssignableFrom(clazz)) { + return Time.valueOf((LocalTime) r); + } else if (Instant.class.isAssignableFrom(clazz)) { + LocalDateTime ldt = LocalDateTime.ofInstant((Instant) r, ZoneId.systemDefault()); + return Time.valueOf(ldt.toLocalTime()); + } + } else if (_part == DateTimeExtractPart.DATE) { + if (Date.class.isAssignableFrom(clazz)) { + return (Date) r; + } else if (Timestamp.class.isAssignableFrom(clazz)) { + return Date.valueOf(((Timestamp) r).toLocalDateTime().toLocalDate()); + } else if (LocalDateTime.class.isAssignableFrom(clazz)) { + return Date.valueOf(((LocalDateTime) r).toLocalDate()); + } else if (LocalDate.class.isAssignableFrom(clazz)) { + return Date.valueOf((LocalDate) r); + } else if (Instant.class.isAssignableFrom(clazz)) { + LocalDateTime ldt = LocalDateTime.ofInstant((Instant) r, ZoneId.systemDefault()); + return Date.valueOf(ldt.toLocalDate()); + } + } + throw new IllegalArgumentException(); + } + + @Override + public void acceptVisit(ExpressionVisitor visitor) { + visitor.enter(this); + _val.acceptVisit(visitor); + visitor.exit(this); + } + +} + 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 00a399d8c..e58a737f6 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 @@ -18,6 +18,7 @@ */ package org.apache.openjpa.kernel.exps; +import java.time.temporal.Temporal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -539,6 +540,21 @@ public class InMemoryExpressionFactory return new CurrentDate(dateType); } + @Override + public <T extends Temporal> Value getCurrentLocalDateTime(Class<T> temporalType) { + return new CurrentTemporal(temporalType); + } + + @Override + public Value getDateTimeField(DateTimeExtractField field, Value value) { + return new ExtractDateTimeField(field, (Val) value); + } + + @Override + public Value getDateTimePart(DateTimeExtractPart part, Value value) { + return new ExtractDateTimePart(part, (Val) value); + } + @Override public Parameter newParameter(Object name, Class type) { return new Param(name, type); 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 68c71456e..f93c60345 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 @@ -25,6 +25,10 @@ import java.math.BigDecimal; import java.security.AccessController; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -47,6 +51,8 @@ import org.apache.openjpa.kernel.ResultShape; import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.kernel.exps.AbstractExpressionBuilder; import org.apache.openjpa.kernel.exps.Context; +import org.apache.openjpa.kernel.exps.DateTimeExtractField; +import org.apache.openjpa.kernel.exps.DateTimeExtractPart; import org.apache.openjpa.kernel.exps.Expression; import org.apache.openjpa.kernel.exps.ExpressionFactory; import org.apache.openjpa.kernel.exps.Literal; @@ -1460,6 +1466,51 @@ public class JPQLExpressionBuilder case JJTCURRENTTIMESTAMP: return factory.getCurrentTimestamp(Timestamp.class); + case JJTLOCALDATETIME: + return factory.getCurrentLocalDateTime(LocalDateTime.class); + + case JJTLOCALDATE: + return factory.getCurrentLocalDateTime(LocalDate.class); + + case JJTLOCALTIME: + return factory.getCurrentLocalDateTime(LocalTime.class); + + case JJTYEAR: + return DateTimeExtractField.YEAR; + + case JJTQUARTER: + return DateTimeExtractField.QUARTER; + + case JJTMONTH: + return DateTimeExtractField.MONTH; + + case JJTWEEK: + return DateTimeExtractField.WEEK; + + case JJTDAY: + return DateTimeExtractField.DAY; + + case JJTHOUR: + return DateTimeExtractField.HOUR; + + case JJTMINUTE: + return DateTimeExtractField.MINUTE; + + case JJTSECOND: + return DateTimeExtractField.SECOND; + + case JJTDATE: + return DateTimeExtractPart.DATE; + + case JJTTIME: + return DateTimeExtractPart.TIME; + + case JJTEXTRACTDATETIMEFIELD: + return factory.getDateTimeField((DateTimeExtractField) eval(firstChild(node)), getValue(secondChild(node))); + + case JJTEXTRACTDATETIMEPART: + return factory.getDateTimePart((DateTimeExtractPart) eval(firstChild(node)), getValue(secondChild(node))); + case JJTSELECTEXTENSION: assertQueryExtensions("SELECT"); return eval(onlyChild(node)); @@ -2526,5 +2577,14 @@ public class JPQLExpressionBuilder } } } + + private DateTimeExtractField resolveDateTimeExtractFieldType(JPQLNode node) { + String value = node.text; + try { + return DateTimeExtractField.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException ex) { + return null; + } + } } 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 a14aec2ea..a746ceffe 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 @@ -168,6 +168,8 @@ TOKEN [ IGNORE_CASE ]: /* basics */ | < VALUE: "VALUE" > | < TYPE: "TYPE" > | < ENTRY: "ENTRY" > + | < LOCAL: "LOCAL" > + | < EXTRACT: "EXTRACT" > } TOKEN [ IGNORE_CASE ]: /* aggregates */ @@ -221,12 +223,33 @@ TOKEN [ IGNORE_CASE ]: /* functions returning numerics */ | < INDEX: "INDEX" > } +TOKEN [ IGNORE_CASE ]: /* datetime fields */ +{ + < YEAR: "YEAR" > + | < QUARTER: "QUARTER" > + | < MONTH: "MONTH" > + | < WEEK: "WEEK" > + | < DAY: "DAY" > + | < HOUR: "HOUR" > + | < MINUTE: "MINUTE" > + | < SECOND: "SECOND" > +} + +TOKEN [ IGNORE_CASE ]: /* datetime part */ +{ + < DATE: "DATE" > + | < TIME: "TIME" > +} + TOKEN [ IGNORE_CASE ]: /* functions returning datetime */ { < CURRENT_DATE: "CURRENT_DATE" > | < CURRENT_TIME: "CURRENT_TIME" > | < CURRENT_TIMESTAMP: "CURRENT_TIMESTAMP" > + | < LOCAL_DATETIME: <LOCAL> (" ")+ "DATETIME" > + | < LOCAL_DATE: <LOCAL> (" ")+ "DATE" > + | < LOCAL_TIME: <LOCAL> (" ")+ "TIME" > } TOKEN [ IGNORE_CASE ]: /* type of query */ @@ -933,14 +956,14 @@ void datetime_comp() : { } void scalar_function() : { } { - functions_returning_numerics() - | functions_returning_datetime() + LOOKAHEAD(2) functions_returning_numerics() + | LOOKAHEAD(2) functions_returning_datetime() | functions_returning_strings() } void arithmetic_value() : { } { - path() | functions_returning_numerics() | "(" subquery() ")" + path() | LOOKAHEAD(2) functions_returning_numerics() | "(" subquery() ")" } @@ -1240,7 +1263,7 @@ void trim_specification() : { } void functions_returning_numerics() : { } { - length() | locate() | abs() | ceiling() | exp() | floor() | ln() | sign() | power() | round() | sqrt() | mod() | size() | index() + LOOKAHEAD(2) length() | locate() | abs() | ceiling() | exp() | floor() | ln() | sign() | power() | round() | sqrt() | mod() | size() | index() | extract_datetime_field() } @@ -1332,11 +1355,38 @@ void index() #INDEX : { } <INDEX> "(" identification_variable() ")" } +void datetime_field() : { } +{ + ( <YEAR> #YEAR | <QUARTER> #QUARTER | <MONTH> #MONTH | <WEEK> #WEEK | <DAY> #DAY | <HOUR> #HOUR | <MINUTE> #MINUTE | <SECOND> #SECOND ) +} + +void extract_datetime_field() #EXTRACTDATETIMEFIELD : {} +{ + <EXTRACT> "(" datetime_field() <FROM> datetime_expression() ")" + +} + +void datetime_part() : {} +{ + ( <DATE> #DATE | <TIME> #TIME ) + +} + +void extract_datetime_part() #EXTRACTDATETIMEPART : {} +{ + <EXTRACT> "(" datetime_part() <FROM> datetime_expression() ")" + +} + void functions_returning_datetime() : { } { - (<CURRENT_DATE> #CURRENTDATE) - | (<CURRENT_TIME> #CURRENTTIME) - | (<CURRENT_TIMESTAMP> #CURRENTTIMESTAMP) + LOOKAHEAD(2) <CURRENT_DATE> #CURRENTDATE + | <CURRENT_TIME> #CURRENTTIME + | <CURRENT_TIMESTAMP> #CURRENTTIMESTAMP + | <LOCAL_DATETIME> #LOCALDATETIME + | <LOCAL_DATE> #LOCALDATE + | <LOCAL_TIME> #LOCALTIME + | extract_datetime_part() } @@ -1437,6 +1487,20 @@ void path_component() #IDENTIFICATIONVARIABLE : | t = <CURRENT_DATE> | t = <CURRENT_TIME> | t = <CURRENT_TIMESTAMP> + | t = <LOCAL_DATETIME> + | t = <LOCAL_TIME> + | t = <LOCAL_DATE> + | t = <LOCAL> + | t = <DATE> + | t = <TIME> + | t = <YEAR> + | t = <QUARTER> + | t = <MONTH> + | t = <WEEK> + | t = <DAY> + | t = <HOUR> + | t = <MINUTE> + | t = <SECOND> | t = <SELECT> | t = <DISTINCT> | t = <FROM> 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 new file mode 100644 index 000000000..3b9fb2109 --- /dev/null +++ b/openjpa-kernel/src/test/java/org/apache/openjpa/kernel/jpql/TestJPQLParser.java @@ -0,0 +1,74 @@ +/* + * 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.jpql; + +import static org.junit.Assert.*; + +import org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.JPQLNode; +import org.junit.Test; + +public class TestJPQLParser { + + @Test + public void testSimpleJPQLExtractFieldFromPath() { + try { + String query = "SELECT a FROM Usuario AS a where (extract(year from a.dateOfBirth) - 2000) < 25"; + JPQLNode node = (JPQLNode) new JPQL(query).parseQuery(); + assertNotNull(node); + } catch (ParseException ex) { + fail(); + } + } + + @Test + public void testSimpleJPQLExtractFieldFromDate() { + try { + String query = "SELECT a FROM Usuario AS a where extract (DAY from {d '2005-04-13'}) = 10"; + JPQLNode node = (JPQLNode) new JPQL(query).parseQuery(); + assertNotNull(node); + } catch (ParseException ex) { + ex.printStackTrace(); + fail(); + } + } + + @Test + public void testJPQL() { + try { + String query = "SELECT c FROM CompUser AS u WHERE EXTRACT (YEAR FROM {d '2006-03-21'}) > 2005"; + assertNotNull(new JPQL(query).parseQuery()); + } catch (ParseException ex) { + ex.printStackTrace(); + fail(); + } + } + + @Test + public void testSimpleJPQLExtractPart() { + try { + String query = "SELECT a FROM Usuario AS a where extract(date from a.dateOfBirth) = {d '2025-07-12'}"; + JPQLNode node = (JPQLNode) new JPQL(query).parseQuery(); + assertNotNull(node); + } catch (ParseException ex) { + ex.printStackTrace(); + fail(); + } + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestContainerSpecCompatibilityOptions.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestContainerSpecCompatibilityOptions.java index a391fbf2a..e73734d9e 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestContainerSpecCompatibilityOptions.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestContainerSpecCompatibilityOptions.java @@ -421,7 +421,7 @@ public class TestContainerSpecCompatibilityOptions em.getTransaction().commit(); // on some databases KEY is a forbidden name for columns. - String keyColumn = getDbDictioary(emf).getInvalidColumnWordSet().contains("KEY") + String keyColumn = getDbDictionary(emf).getInvalidColumnWordSet().contains("KEY") ? "KEY0" : "KEY"; assertSQLFragnments(sql, "CREATE TABLE C_U1M_Map_FK", "Uni1MFK_ID", keyColumn); diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestSpecCompatibilityOptions.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestSpecCompatibilityOptions.java index 53b50ba81..1dcf79041 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestSpecCompatibilityOptions.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestSpecCompatibilityOptions.java @@ -412,7 +412,7 @@ extends AbstractCachedEMFTestCase { em.getTransaction().commit(); // on some databases KEY is a forbidden name for columns. - String keyColumn = getDbDictioary(emf).getInvalidColumnWordSet().contains("KEY") + String keyColumn = getDbDictionary(emf).getInvalidColumnWordSet().contains("KEY") ? "KEY0" : "KEY"; assertSQLFragnments(sql, "CREATE TABLE C_U1M_Map_FK", "Uni1MFK_ID", keyColumn); 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 80ddda975..aca4d1bcc 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 @@ -22,6 +22,9 @@ import java.math.BigDecimal; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -1656,7 +1659,65 @@ public class TestTypesafeCriteria extends CriteriaTest { } -// public void testInMemoryAccessPath() { + public void testBasicLocalDateTime() { + em.getTransaction().begin(); + Order pc = new Order(); + em.persist(pc); + em.getTransaction().commit(); + + int oid = pc.getId(); + + CriteriaQuery<LocalDateTime> cquery = cb.createQuery(LocalDateTime.class); + Root<Order> order = cquery.from(Order.class); + cquery.select(cb.localDateTime()); + cquery.where(cb.equal(order.get(Order_.id), oid)); + + TypedQuery<LocalDateTime> tq = em.createQuery(cquery); + Object result = tq.getSingleResult(); + assertTrue(result.getClass() + " not instance of LocalDateTime", result instanceof LocalDateTime); + + } + + public void testBasicLocalTime() { + em.getTransaction().begin(); + Order pc = new Order(); + em.persist(pc); + em.getTransaction().commit(); + + int oid = pc.getId(); + + CriteriaQuery<LocalTime> cquery = cb.createQuery(LocalTime.class); + Root<Order> order = cquery.from(Order.class); + cquery.select(cb.localTime()); + cquery.where(cb.equal(order.get(Order_.id), oid)); + + TypedQuery<LocalTime> tq = em.createQuery(cquery); + Object result = tq.getSingleResult(); + assertTrue(result.getClass() + " not instance of LocalTime", result instanceof LocalTime); + + } + + public void testBasicLocalDate() { + em.getTransaction().begin(); + Order pc = new Order(); + em.persist(pc); + em.getTransaction().commit(); + + int oid = pc.getId(); + + CriteriaQuery<LocalDate> cquery = cb.createQuery(LocalDate.class); + Root<Order> order = cquery.from(Order.class); + cquery.select(cb.localDate()); + cquery.where(cb.equal(order.get(Order_.id), oid)); + + TypedQuery<LocalDate> tq = em.createQuery(cquery); + Object result = tq.getSingleResult(); + assertTrue(result.getClass() + " not instance of LocalDate", result instanceof LocalDate); + + } + + + // public void testInMemoryAccessPath() { // em.getTransaction().begin(); // // must have new/dirty managed instances to exercise the code path // em.persist(new Customer()); 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 845f84276..35d652292 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 @@ -19,6 +19,9 @@ package org.apache.openjpa.persistence.jpql.functions; import java.math.BigDecimal; +import java.sql.Time; +import java.time.LocalTime; +import java.time.temporal.ChronoField; import java.util.List; import jakarta.persistence.EntityManager; @@ -596,13 +599,13 @@ public class TestEJBQLFunction extends AbstractTestCase { public void testROUNDFunc() { EntityManager em = currentEntityManager(); - String query = "SELECT ROUND(SQRT(MIN(c.age)), 3) FROM CompUser c"; + String query = "SELECT ROUND(SUM(c.age)/7.0, 3) FROM CompUser c"; List result = em.createQuery(query).getResultList(); assertNotNull(result); assertEquals(1, result.size()); - assertEquals(3.162, (double) result.get(0)); + assertEquals(21.857, (double) result.get(0)); endEm(em); } @@ -635,6 +638,232 @@ public class TestEJBQLFunction extends AbstractTestCase { endEm(em); } + public void testExtractDateFromInstant() { + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT(DATE FROM {ts '2005-03-21 01:32:20'}) > {d '2005-02-10'}"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractTimeFromInstant() { + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT(TIME FROM {ts '2005-03-21 01:32:20'}) = {t '01:32:20'}"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractDateFromLocalDateTime() { + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT(DATE FROM LOCAL DATETIME) > {d '2025-01-10'}"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractTimeFromLocalTime() { + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT(TIME FROM LOCAL TIME) = {t '01:32:20'}"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(0, result.size()); + endEm(em); + } + + public void testExtractYear() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (YEAR FROM {d '2006-03-21'}) > 2005"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractBirthYear() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT EXTRACT(YEAR FROM {d '2025-01-23'}) - c.age FROM CompUser AS c"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + assertEquals(1989, (int) result.get(0)); + assertEquals(1989, (int) result.get(1)); + assertEquals(2006, (int) result.get(2)); + assertEquals(2015, (int) result.get(3)); + assertEquals(1996, (int) result.get(4)); + assertEquals(2002, (int) result.get(5)); + endEm(em); + } + + public void testExtractQUARTER() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (QUARTER FROM {d '2006-03-21'}) = 2"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(0, result.size()); + endEm(em); + } + + public void testExtractMONTH() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (MONTH FROM {d '2006-03-21'}) <= 3"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractWEEK() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (WEEK FROM {d '2006-03-21'}) <= 12"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractDAY() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (DAY FROM {ts '2006-03-21 18:19:23'}) = 21"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractHOUR() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT (HOUR FROM {ts '2006-03-21 18:19:23'}) <> 18"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(0, result.size()); + endEm(em); + } + + public void testExtractMINUTE() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT c FROM CompUser AS c WHERE EXTRACT(MINUTE FROM {ts '2006-03-21 18:19:23'}) = 19"; + + List<CompUser> result = em.createQuery(query, CompUser.class).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + endEm(em); + } + + public void testExtractSECOND() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + + String query = "SELECT EXTRACT(SECOND FROM {ts '2006-03-21 18:19:23'}) - c.age FROM CompUser AS c ORDER BY c.age"; + + List result = em.createQuery(query).getResultList(); + + assertNotNull(result); + assertEquals(6, result.size()); + assertEquals(13f, (float) result.get(0)); + assertEquals(4f, (float) result.get(1)); + assertEquals(0f, (float) result.get(2)); + assertEquals(-6f, (float) result.get(3)); + assertEquals(-13f, (float) result.get(4)); + assertEquals(-13f, (float) result.get(5)); + endEm(em); + } + + public void testExtractHourFromLocalTime() { + if (getDbDictionary(getEmf()) instanceof DerbyDictionary) { + // Derby does not support EXTRACT + return; + } + EntityManager em = currentEntityManager(); + String query = "SELECT CURRENT_TIME, (EXTRACT(HOUR FROM LOCAL TIME) - c.age) FROM CompUser as c WHERE c.age = 23"; + + List result = em.createQuery(query).getResultList(); + + assertEquals(1, result.size()); + Object[] ret = (Object[]) result.get(0); + assertEquals(2, ret.length); + Time time = (Time) ret[0]; + LocalTime serverTime = time.toLocalTime(); + int expected = serverTime.get(ChronoField.HOUR_OF_DAY) - 23; + + assertEquals(expected, (int) ret[1]); + + endEm(em); + } + public CompUser createUser(String name, String cName, Address add, int age, boolean isMale) { CompUser user = null; diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestJava8TimeTypes.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestJava8TimeTypes.java index 0b75bacdd..a22fec797 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestJava8TimeTypes.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestJava8TimeTypes.java @@ -199,4 +199,34 @@ public class TestJava8TimeTypes extends SingleEMFTestCase { em.close(); } + public void testGetCurrentLocalDate() { + EntityManager em = emf.createEntityManager(); + final TypedQuery<Java8TimeTypes> qry + = em.createQuery("select j from Java8TimeTypes AS j where j.localDateField < LOCAL DATE", Java8TimeTypes.class); + final List<Java8TimeTypes> times = qry.getResultList(); + assertNotNull(times); + assertTrue(!times.isEmpty()); + em.close(); + } + + public void testGetCurrentLocalDateTime() { + EntityManager em = emf.createEntityManager(); + final TypedQuery<Java8TimeTypes> qry + = em.createQuery("select j from Java8TimeTypes AS j where j.localDateTimeField < LOCAL DATETIME", Java8TimeTypes.class); + final List<Java8TimeTypes> times = qry.getResultList(); + assertNotNull(times); + assertTrue(!times.isEmpty()); + em.close(); + } + + public void testGetCurrentLocalTime() { + EntityManager em = emf.createEntityManager(); + final TypedQuery<Java8TimeTypes> qry + = em.createQuery("select j from Java8TimeTypes AS j where j.localTimeField < LOCAL TIME", Java8TimeTypes.class); + final List<Java8TimeTypes> times = qry.getResultList(); + assertNotNull(times); + assertTrue(!times.isEmpty()); + em.close(); + } + } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/AbstractPersistenceTestCase.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/AbstractPersistenceTestCase.java index a11067b89..6f11cb527 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/AbstractPersistenceTestCase.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/AbstractPersistenceTestCase.java @@ -358,7 +358,7 @@ public abstract class AbstractPersistenceTestCase extends TestCase { } } - protected DBDictionary getDbDictioary(EntityManagerFactory emf) { + protected DBDictionary getDbDictionary(EntityManagerFactory emf) { return ((JDBCConfiguration)((OpenJPAEntityManagerFactory) emf).getConfiguration()).getDBDictionaryInstance(); } 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 c9c0e2bf0..68d2e89c4 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 @@ -1030,20 +1030,17 @@ public class CriteriaBuilderImpl implements OpenJPACriteriaBuilder, ExpressionPa @Override public Expression<LocalDate> localDate() { - // TODO Implement localDate - throw new UnsupportedOperationException(); + return new Expressions.CurrentLocalDate(); } @Override public Expression<LocalDateTime> localDateTime() { - // TODO Implement ceiling op - throw new UnsupportedOperationException(); + return new Expressions.CurrentLocalDateTime(); } @Override public Expression<LocalTime> localTime() { - // TODO Implement localTime op - throw new UnsupportedOperationException(); + return new Expressions.CurrentLocalTime(); } } 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 e8502a5b0..7cb4f23ba 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 @@ -21,6 +21,9 @@ package org.apache.openjpa.persistence.criteria; import java.lang.reflect.Array; import java.lang.reflect.ParameterizedType; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -979,6 +982,54 @@ class Expressions { } } + public static class CurrentLocalDateTime extends ExpressionImpl<java.time.LocalDateTime> { + public CurrentLocalDateTime() { + super(java.time.LocalDateTime.class); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + return factory.getCurrentLocalDateTime(LocalDateTime.class); + } + + @Override + public StringBuilder asValue(AliasContext q) { + return new StringBuilder("LOCAL DATETIME"); + } + } + + public static class CurrentLocalDate extends ExpressionImpl<java.time.LocalDate> { + public CurrentLocalDate() { + super(java.time.LocalDate.class); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + return factory.getCurrentLocalDateTime(LocalDate.class); + } + + @Override + public StringBuilder asValue(AliasContext q) { + return new StringBuilder("LOCAL DATE"); + } + } + + public static class CurrentLocalTime extends ExpressionImpl<java.time.LocalTime> { + public CurrentLocalTime() { + super(java.time.LocalTime.class); + } + + @Override + public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) { + return factory.getCurrentLocalDateTime(LocalTime.class); + } + + @Override + public StringBuilder asValue(AliasContext q) { + return new StringBuilder("LOCAL TIME"); + } + } + public static class Equal extends BinaryLogicalExpression { public <X,Y> Equal(Expression<X> x, Expression<Y> y) { super(x,y); diff --git a/openjpa-project/src/doc/manual/jpa_overview_query.xml b/openjpa-project/src/doc/manual/jpa_overview_query.xml index f681f7374..edfffdc39 100644 --- a/openjpa-project/src/doc/manual/jpa_overview_query.xml +++ b/openjpa-project/src/doc/manual/jpa_overview_query.xml @@ -747,7 +747,65 @@ SELECT w.name FROM Course c JOIN c.studentWaitlist w WHERE c.name = ‘Calculus <literal>CURRENT_TIMESTAMP</literal>: Returns the current timestamp. </para> </listitem> - </itemizedlist> + <listitem> + <para> + <indexterm> + <primary> + LOCAL DATE function + </primary> + </indexterm> +<literal>LOCAL DATE</literal>: returns the value of current date on the database server. + </para> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + LOCAL TIME function + </primary> + </indexterm> +<literal>LOCAL TIME</literal>: returns the value of current time on the database server. + </para> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + LOCAL DATETIME function + </primary> + </indexterm> +<literal>LOCAL DATETIME</literal>: returns the value of current timestamp on the database server. + </para> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + EXTRACT date field function + </primary> + </indexterm> +<literal>EXTRACT</literal>(date_field <literal>FROM</literal> date_expression) : extracts a given field +(year, quarter, month, week, day, hour, minute or second) from a datetime. + </para> +<programlisting> +SELECT c FROM Customer AS c WHERE EXTRACT(YEAR FROM c.birthDate) < (EXTRACT(YEAR FROM CURRENT_DATE) - 13) +</programlisting> + </listitem> + <listitem> + <para> + <indexterm> + <primary> + EXTRACT date part function + </primary> + </indexterm> +<literal>EXTRACT</literal>(date_part <literal>FROM</literal> date_expression) : extracts a date part +(date or time) from a datetime. + </para> +<programlisting> +SELECT c FROM Customer AS c WHERE EXTRACT(DATE FROM c.createdTimestamp) < { d '1969-07-20' } +</programlisting> + </listitem> + </itemizedlist> </section> <section id="jpa_overview_query_inheritance"> <title> @@ -1637,6 +1695,11 @@ query language. The following are reserved identifiers: <para> <literal>BETWEEN</literal> </para> + </listitem> + <listitem> + <para> +<literal>BIT_LENGTH</literal> + </para> </listitem> <listitem> <para> @@ -1655,6 +1718,21 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>CEILING</literal> + </para> + </listitem> + <listitem> + <para> +<literal>CHAR_LENGTH</literal> + </para> + </listitem> + <listitem> + <para> +<literal>CHARACTER_LENGTH</literal> + </para> + </listitem> + <listitem> + <para> <literal>CLASS</literal> </para> </listitem> @@ -1731,10 +1809,20 @@ query language. The following are reserved identifiers: <listitem> <para> <literal>EXISTS</literal> - </para> - </listitem> - <listitem> - <para> + </para> + </listitem> + <listitem> + <para> +<literal>EXP</literal> + </para> + </listitem> + <listitem> + <para> +<literal>EXTRACT</literal> + </para> + </listitem> + <listitem> + <para> <literal>FALSE</literal> </para> </listitem> @@ -1745,6 +1833,11 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>FLOOR</literal> + </para> + </listitem> + <listitem> + <para> <literal>FROM</literal> </para> </listitem> @@ -1810,21 +1903,31 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>LOCAL</literal> + </para> + </listitem> + <listitem> + <para> +<literal>LN</literal> + </para> + </listitem> + <listitem> + <para> <literal>LOCATE</literal> </para> </listitem> <listitem> <para> <literal>LOWER</literal> - </para> - </listitem> - <listitem> - <para> + </para> + </listitem> + <listitem> + <para> <literal>MAX</literal> - </para> - </listitem> - <listitem> - <para> + </para> + </listitem> + <listitem> + <para> <literal>MEMBER</literal> </para> </listitem> @@ -1870,6 +1973,11 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>ON</literal> + </para> + </listitem> + <listitem> + <para> <literal>OR</literal> </para> </listitem> @@ -1885,6 +1993,21 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>POSITION</literal> + </para> + </listitem> + <listitem> + <para> +<literal>POWER</literal> + </para> + </listitem> + <listitem> + <para> +<literal>ROUND</literal> + </para> + </listitem> + <listitem> + <para> <literal>SELECT</literal> </para> </listitem> @@ -1895,6 +2018,11 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>SIGN</literal> + </para> + </listitem> + <listitem> + <para> <literal>SIZE</literal> </para> </listitem> @@ -1930,6 +2058,11 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>TREAT</literal> + </para> + </listitem> + <listitem> + <para> <literal>TRIM</literal> </para> </listitem> @@ -1945,6 +2078,11 @@ query language. The following are reserved identifiers: </listitem> <listitem> <para> +<literal>UNKNOWN</literal> + </para> + </listitem> + <listitem> + <para> <literal>UPDATE</literal> </para> </listitem> @@ -1968,31 +2106,6 @@ query language. The following are reserved identifiers: <literal>WHERE</literal> </para> </listitem> - <listitem> - <para> -<literal>CHARACTER_LENGTH</literal> - </para> - </listitem> - <listitem> - <para> -<literal>CHAR_LENGTH</literal> - </para> - </listitem> - <listitem> - <para> -<literal>BIT_LENGTH</literal> - </para> - </listitem> - <listitem> - <para> -<literal>POSITION</literal> - </para> - </listitem> - <listitem> - <para> -<literal>UNKNOWN</literal> - </para> - </listitem> </itemizedlist> <para> Reserved identifiers are case insensitive. Reserved identifiers must not be @@ -3547,12 +3660,98 @@ which an order column has been specified. JPQL Datetime Functions </title> <para> -functions_returning_datetime:= CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP +functions_returning_datetime:= CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP | LOCAL DATE +| LOCAL TIME | LOCAL DATETIME | extract_datetime_part + </para> + <para> +The functions LOCAL DATE, LOCAL TIME, and LOCAL DATETIME return the value of the current date, time, +or timestamp on the database server, respectively. Their types are java.time.LocalDate, +java.time.LocalTime, and java.time.LocalDateTime respectively. </para> <para> -The datetime functions return the value of current date, time, and timestamp on -the database server. +The functions CURRENT_DATE, CURRENT_TIME, and CURRENT_TIMESTAMP return the value of the current date, +time, or timestamp on the database server, respectively. Their types are java.sql.Date, java.sql.Time, +and java.sql.Timestamp respectively. </para> + <para> +The EXTRACT function takes a datetime argument and one of the following field type identifiers: YEAR, +QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, DATE, TIME. + </para> + <para> +EXTRACT returns the value of the corresponding field or part of the datetime. + </para> + <para> +For the following field type identifiers, EXTRACT returns an integer value: + </para> + <itemizedlist> + <listitem> + <para> +<literal>YEAR</literal> means the calendar year. + </para> + </listitem> + <listitem> + <para> +<literal>QUARTER</literal> means the calendar quarter, numbered from 1 to 4. + </para> + </listitem> + <listitem> + <para> +<literal>MONTH</literal> means the calendar month of the year, numbered from 1. + </para> + </listitem> + <listitem> + <para> +<literal>WEEK</literal> means the ISO-8601 week number. + </para> + </listitem> + <listitem> + <para> +<literal>DAY</literal> means the calendar day of the month, numbered from 1. + </para> + </listitem> + <listitem> + <para> +<literal>HOUR</literal> means the hour of the day in 24-hour time, numbered from 0 to 23. + </para> + </listitem> + <listitem> + <para> +<literal>MINUTE</literal> means the minute of the hour, numbered from 0 to 59. + </para> + </listitem> + </itemizedlist> + <para> +For the SECOND field type identifier, EXTRACT returns a floating point value: + </para> + <itemizedlist> + <listitem> + <para> +<literal>SECOND</literal> means the second of the minute, numbered from 0 to 59, including a + fractional part representing fractions of a second. + </para> + </listitem> + </itemizedlist> + <para> +It is illegal to pass a datetime argument which does not have the given field type to EXTRACT. + </para> + <para> +For the following field type identifiers, EXTRACT returns a part of the datetime value: + </para> + <itemizedlist> + <listitem> + <para> +<literal>DATE</literal> means the date part of a datetime. + </para> + </listitem> + <listitem> + <para> +<literal>TIME</literal> means the time part of a datetime. + </para> + </listitem> + </itemizedlist> + <para> +It is illegal to pass a datetime argument which does not have the given part to EXTRACT. + </para> </section> </section> <section id="jpa_langref_case_expressions"> @@ -4540,8 +4739,28 @@ Comparisons over instances of embeddable class or map entry types are not suppor JPQL BNF </title> <para> -The following is the BNF for the Java Persistence query language, from section -4.14 of the JSR 317 specification. +BNF notation summary: + </para> + <itemizedlist> + <listitem> +{ ... } grouping + </listitem> + <listitem> +[ ... ] optional constructs + </listitem> + <listitem> +* zero or more + </listitem> + <listitem> ++ one or more + </listitem> + <listitem> +| alternates + </listitem> + </itemizedlist> + <para> +The following is the BNF for the Jakarta Persistence query language, from section +4.14 of the Jarkata Persistence 3.1 specification. </para> <itemizedlist> <listitem> @@ -4586,7 +4805,7 @@ identification_variable <listitem> <para> join ::= join_spec join_association_path_expression [ <literal>AS</literal> ] -identification_variable +identification_variable [join_conition] </para> </listitem> <listitem> @@ -4600,23 +4819,28 @@ join_association_path_expression join_spec ::= [ <literal>LEFT</literal> [ <literal>OUTER</literal> ]| <literal> INNER</literal> ] <literal>JOIN</literal> </para> + </listitem> + <listitem> + <para> +join_condition ::= ON conditional_expression + </para> </listitem> <listitem> <para> join_association_path_expression ::= join_collection_valued_path_expression | -join_single_valued_path_expression +join_single_valued_path_expression | <literal>TREAT</literal>(join_collection_valued_path_expression AS subtype) | <literal>TREAT</literal>(join_single_valued_path_expression AS subtype) </para> </listitem> <listitem> <para> join_collection_valued_path_expression ::= -identification_variable.{single_valued_embeddable_object_field.}*collection_valued_field +identification_variable.{single_valued_embeddable_object_field.}* collection_valued_field </para> </listitem> <listitem> <para> join_single_valued_path_expression ::= -identification_variable.{single_valued_embeddable_object_field.}*single_valued_object_field +identification_variable.{single_valued_embeddable_object_field.}* single_valued_object_field </para> </listitem> <listitem> @@ -4629,15 +4853,21 @@ identification_variable <listitem> <para> qualified_identification_variable ::= -KEY(identification_variable) | -VALUE(identification_variable) | -ENTRY(identification_variable) +map_field_identification_variable | <literal>ENTRY</literal>(identification_variable) + </para> + </listitem> + <listitem> + <para> +map_field_identification_variable ::= +<literal>KEY</literal>(identification_variable) | +<literal>VALUE</literal>(identification_variable) </para> </listitem> <listitem> <para> single_valued_path_expression ::= qualified_identification_variable | +<literal>TREAT</literal>(qualified_identification_variable <literal>AS</literal> subtype) | state_field_path_expression | single_valued_object_path_expression </para> @@ -4646,27 +4876,46 @@ single_valued_object_path_expression <para> general_identification_variable ::= identification_variable | -KEY(identification_variable) | -VALUE(identification_variable) +map_field_identification_variable </para> </listitem> <listitem> <para> -state_field_path_expression ::= -general_identification_variable.{single_valued_object_field.}*state_field +general_subpath ::= simple_subpath | treated_subpath{.single_valued_object_field}* + </para> + </listitem> + <listitem> + <para> +simple_subpath ::= + general_identification_variable | + general_identification_variable{.single_valued_object_field}* + </para> + </listitem> + <listitem> + <para> +treated_subpath ::= <literal>TREAT</literal>(general_subpath AS subtype) + </para> + </listitem> + <listitem> + <para> +state_field_path_expression ::= general_subpath.state_field + </para> + </listitem> + <listitem> + <para> +state_valued_path_expression ::= + state_field_path_expression | general_identification_variable </para> </listitem> <listitem> <para> single_valued_object_path_expression ::= -general_identification_variable.{single_valued_object_field.}* -single_valued_object_field + general_subpath.single_valued_object_field </para> </listitem> <listitem> <para> -collection_valued_path_expression ::= -general_identification_variable.{single_valued_object_field.}*collection_valued_field +collection_valued_path_expression ::= general_subpath.{collection_valued_field} </para> </listitem> <listitem> @@ -4678,15 +4927,14 @@ update_item}* </listitem> <listitem> <para> -update_item ::= [identification_variable.]{state_field | -single_valued_object_field}= new_value +update_item ::= [identification_variable.]{single_valued_embeddable_object_field.}* + {state_field | single_valued_object_field} = new_value </para> </listitem> <listitem> <para> new_value ::= scalar_expression | -simple_entity_expression | <literal>NULL -</literal> +simple_entity_expression | <literal>NULL</literal> </para> </listitem> <listitem> @@ -4708,11 +4956,13 @@ select_item ::= select_expression [[AS] result_variable] </listitem> <listitem> <para> -select_expression ::= single_valued_path_expression | -scalar_expression | -aggregate_expression | -identification_variable | <literal>OBJECT</literal> (identification_variable)| -constructor_expression +select_expression ::= + single_valued_path_expression | + scalar_expression | + aggregate_expression | + identification_variable | + <literal>OBJECT</literal> (identification_variable) | + constructor_expression </para> </listitem> <listitem> @@ -4735,7 +4985,7 @@ aggregate_expression ::= { <literal>AVG</literal> | <literal>MAX</literal> | <literal>MIN</literal> | <literal>SUM</literal> }([ <literal>DISTINCT</literal> ] state_field_path_expression) | <literal>COUNT</literal> ([ <literal>DISTINCT </literal> ] identification_variable | state_field_path_expression | -single_valued_object_path_expression) +single_valued_object_path_expression) | function_invocation </para> </listitem> <listitem> @@ -4767,7 +5017,9 @@ orderby_item}* </listitem> <listitem> <para> -orderby_item ::= state_field_path_expression | result_variable [ <literal>ASC</literal> | +orderby_item ::= state_field_path_expression | + general_identification_variable | + result_variable [ <literal>ASC</literal> | <literal>DESC</literal> ] </para> </listitem> @@ -4795,14 +5047,31 @@ identification_variable_declaration | derived_path_expression [ <literal>AS <listitem> <para> derived_path_expression ::= -superquery_identification_variable.{single_valued_object_field.}*collection_valued_field | -superquery_identification_variable.{single_valued_object_field.}*single_valued_object_field + general_derived_path.single_valued_object_field | + general_derived_path.collection_valued_field + </para> + </listitem> + <listitem> + <para> +general_derived_path ::= + simple_derived_path | + treated_derived_path{.single_valued_object_field}* + </para> + </listitem> + <listitem> + <para> +simple_derived_path ::= superquery_identification_variable{.single_valued_object_field}* + </para> + </listitem> + <listitem> + <para> +treated_derived_path ::= <literal>TREAT</literal>(general_derived_path AS subtype) </para> </listitem> <listitem> <para> derived_collection_member_declaration ::= -IN superquery_identification_variable.{single_valued_object_field.}*collection_valued_field +<literal>IN</literal> superquery_identification_variable.{single_valued_object_field.}*collection_valued_field </para> </listitem> <listitem> @@ -4820,11 +5089,11 @@ scalar_expression | aggregate_expression | identification_variable <listitem> <para> scalar_expression ::= -simple_arithmetic_expression | -string_primary | -enum_primary | -datetime_primary | -boolean_primary | +arithmetic_expression | +string_expression | +enum_expression | +datetime_expression | +boolean_expression | case_expression | entity_type_expression </para> @@ -4848,13 +5117,13 @@ conditional_factor ::= [ <literal>NOT</literal> ] conditional_primary </listitem> <listitem> <para> -conditional_primary ::= simple_cond_expression |(conditional_expression) +conditional_primary ::= simple_cond_expression | (conditional_expression) </para> </listitem> <listitem> <para> simple_cond_expression ::= comparison_expression | between_expression | -like_expression | in_expression | null_comparison_expression | +in_expression | like_expression | null_comparison_expression | empty_collection_comparison_expression | collection_member_expression | exists_expression </para> @@ -4936,158 +5205,203 @@ all_or_any_expression ::= { <literal>ALL</literal> | <literal>ANY</literal> | <listitem> <para> comparison_expression ::= -string_expressioncomparison_operator{string_expression|all_or_any_expression}| -boolean_expression {=|<>} {boolean_expression | all_or_any_expression} | -enum_expression {=|<>} {enum_expression | all_or_any_expression} | +string_expression comparison_operator {string_expression | all_or_any_expression} | +boolean_expression {= | <>} {boolean_expression | all_or_any_expression} | +enum_expression {= |<>} {enum_expression | all_or_any_expression} | datetime_expression comparison_operator {datetime_expression | -all_or_any_expression} | entity_expression {= |<> } {entity_expression | +all_or_any_expression} | entity_expression {= | <>} {entity_expression | all_or_any_expression} | arithmetic_expression comparison_operator {arithmetic_expression | all_or_any_expression} | -entity_type_expression { =|<>>} entity_type_expression} +entity_type_expression {= |<>} entity_type_expression} </para> </listitem> <listitem> <para> -comparison_operator ::== |> |>= |< |<= |<> +comparison_operator ::= = | > | >= | < | <= | <> </para> </listitem> <listitem> <para> -arithmetic_expression ::= simple_arithmetic_expression |(subquery) +arithmetic_expression ::= arithmetic_term | arithmetic_expression {+ | -} arithmetic_term </para> </listitem> <listitem> <para> -simple_arithmetic_expression ::= arithmetic_term | simple_arithmetic_expression -{+ |- } arithmetic_term +arithmetic_term ::= arithmetic_factor | arithmetic_term {* | /} arithmetic_factor </para> </listitem> <listitem> <para> -arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } -arithmetic_factor +arithmetic_factor ::= [{+ | -}] arithmetic_primary + </para> + </listitem> + <listitem> + <para> +arithmetic_primary ::= + state_valued_path_expression | numeric_literal | + (arithmetic_expression) | + input_parameter | + functions_returning_numerics | + aggregate_expression | + case_expression | + function_invocation | + (subquery) </para> </listitem> <listitem> <para> -arithmetic_factor ::= [{+ |-}] arithmetic_primary +string_expression ::= + state_valued_path_expression | + string_literal | + input_parameter | + functions_returning_strings | + aggregate_expression | + case_expression | + function_invocation | + (subquery) </para> </listitem> <listitem> <para> -arithmetic_primary ::= state_field_path_expression | numeric_literal | -(simple_arithmetic_expression) | input_parameter | functions_returning_numerics -| aggregate_expression | -case_expression +datetime_expression ::= + state_valued_path_expression | + input_parameter | + functions_returning_datetime | + aggregate_expression | + case_expression | + function_invocation | + date_time_timestamp_literal | + (subquery) </para> </listitem> <listitem> <para> -string_expression ::= string_primary |(subquery) +boolean_expression ::= + state_valued_path_expression | + boolean_literal | + input_parameter | + case_expression | + function_invocation | + (subquery) </para> </listitem> <listitem> <para> -string_primary ::= state_field_path_expression | string_literal | -input_parameter | functions_returning_strings | aggregate_expression | -case_expression +enum_expression ::= + state_valued_path_expression | + enum_literal | + input_parameter | + case_expression | + (subquery) </para> </listitem> <listitem> <para> -datetime_expression ::= datetime_primary |(subquery) +entity_expression ::= single_valued_object_path_expression | +simple_entity_expression </para> </listitem> <listitem> <para> -datetime_primary ::= state_field_path_expression | input_parameter | -functions_returning_datetime | aggregate_expression | -case_expression | -date_time_timestamp_literal +simple_entity_expression ::= identification_variable | input_parameter </para> </listitem> <listitem> <para> -boolean_expression ::= boolean_primary |(subquery) +entity_type_expression ::= +type_discriminator | +entity_type_literal | +input_parameter </para> </listitem> <listitem> <para> -boolean_primary ::= state_field_path_expression | boolean_literal | -input_parameter | -case_expression +type_discriminator ::= +<literal>TYPE</literal>(identification_variable | +single_valued_object_path_expression | +input_parameter) </para> </listitem> <listitem> <para> -enum_expression ::= enum_primary |(subquery) +functions_returning_numerics ::= <literal>LENGTH</literal> (string_primary)| +<literal>LOCATE</literal> (string_primary,string_primary [, +arithmetic_expression]) | <literal>ABS</literal> +(arithmetic_expression) | <literal>CEILING</literal> +(arithmetic_expression) | <literal>EXP</literal> +(arithmetic_expression) | <literal>FLOOR</literal> +(arithmetic_expression) | <literal>LN</literal> +(arithmetic_expression) | <literal>SIGN</literal> +(arithmetic_expression) | <literal>SQRT</literal> +(arithmetic_expression) | <literal>MOD</literal> +(arithmetic_expression, arithmetic_expression) | <literal>POWER</literal> +(arithmetic_expression) | <literal>ROUND</literal> +(arithmetic_expression, arithmetic_expression) | <literal>SIZE +</literal> (collection_valued_path_expression) | +<literal>INDEX</literal>(identification_variable) | +extract_datetime_field </para> </listitem> <listitem> <para> -enum_primary ::= state_field_path_expression | enum_literal | input_parameter | -case_expression +functions_returning_datetime ::= <literal>CURRENT_DATE</literal> | <literal> +CURRENT_TIME</literal> | <literal>CURRENT_TIMESTAMP</literal> | +<literal>LOCAL DATE</literal> | +<literal>LOCAL TIME</literal> | +<literal>LOCAL DATETIME</literal> | +extract_datetime_part </para> </listitem> <listitem> <para> -entity_expression ::= single_valued_object_path_expression | -simple_entity_expression +functions_returning_strings ::= <literal>CONCAT</literal> (string_expression, +string_expression) | <literal>SUBSTRING</literal> (string_expression, +arithmetic_expression[,arithmetic_expression])| <literal>TRIM +</literal> ([[trim_specification] [trim_character] <literal>FROM</literal> ] +string_expression) | <literal>LOWER</literal> (string_expression) | <literal>UPPER +</literal> (string_expression) </para> </listitem> <listitem> <para> -simple_entity_expression ::= identification_variable | input_parameter +trim_specification ::= <literal>LEADING</literal> | <literal>TRAILING</literal> +| <literal>BOTH</literal> </para> </listitem> <listitem> <para> -entity_type_expression ::= -type_discriminator | -entity_type_literal | -input_parameter +function_invocation ::= <literal>FUNCTION</literal>(function_name{, function_arg}*) </para> </listitem> <listitem> <para> -type_discriminator ::= -<literal>TYPE</literal>(identification_variable | -single_valued_object_path_expression | -input_parameter) +extract_datetime_field := + <literal>EXTRACT</literal>(datetime_field FROM datetime_expression) </para> </listitem> <listitem> <para> -functions_returning_numerics ::= <literal>LENGTH</literal> (string_primary)| -<literal>LOCATE</literal> (string_primary,string_primary [, -simple_arithmetic_expression]) | <literal>ABS</literal> -(simple_arithmetic_expression) | <literal>SQRT</literal> -(simple_arithmetic_expression) | <literal>MOD</literal> -(simple_arithmetic_expression, simple_arithmetic_expression) | <literal>SIZE -</literal> (collection_valued_path_expression) | -<literal>INDEX</literal>(identification_variable) +datetime_field := identification_variable </para> </listitem> <listitem> <para> -functions_returning_datetime ::= <literal>CURRENT_DATE</literal> | <literal> -CURRENT_TIME</literal> | <literal>CURRENT_TIMESTAMP</literal> +extract_datetime_part := + <literal>EXTRACT</literal>(datetime_part FROM datetime_expression) </para> </listitem> <listitem> <para> -functions_returning_strings ::= <literal>CONCAT</literal> (string_primary, -string_primary) | <literal>SUBSTRING</literal> (string_primary, -simple_arithmetic_expression[,simple_arithmetic_expression])| <literal>TRIM -</literal> ([[trim_specification] [trim_character] <literal>FROM</literal> ] -string_primary) | <literal>LOWER</literal> (string_primary) | <literal>UPPER -</literal> (string_primary) +datetime_part := identification_variable </para> </listitem> <listitem> <para> -trim_specification ::= <literal>LEADING</literal> | <literal>TRAILING</literal> -| <literal>BOTH</literal> +function_arg ::= + literal | + state_valued_path_expression | + input_parameter | + scalar_expression </para> </listitem> <listitem>