Skip to content

Commit

Permalink
Issue #317 - Implemented LOCAL DATE, LOCAL TIME and LOCAL DATETIME in…
Browse files Browse the repository at this point in the history
… JPQL query.

Signed-off-by: Tomas Kraus <tomas.kraus@oracle.com>
  • Loading branch information
Tomas-Kraus authored and lukasj committed May 31, 2022
1 parent e6109f9 commit 9581b0b
Show file tree
Hide file tree
Showing 17 changed files with 963 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
// IBM - Bug 537795: CASE THEN and ELSE scalar expression Constants should not be casted to CASE operand type
// 04/21/2022: Tomas Kraus
// - Issue 1474: Update JPQL Grammar for Jakarta Persistence 2.2, 3.0 and 3.1
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.internal.jpa.jpql;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -106,6 +109,8 @@
import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression;
import org.eclipse.persistence.jpa.jpql.parser.LengthExpression;
import org.eclipse.persistence.jpa.jpql.parser.LikeExpression;
import org.eclipse.persistence.jpa.jpql.parser.LocalDateTime;
import org.eclipse.persistence.jpa.jpql.parser.LocalExpression;
import org.eclipse.persistence.jpa.jpql.parser.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LowerExpression;
import org.eclipse.persistence.jpa.jpql.parser.MathDoubleExpression;
Expand Down Expand Up @@ -1265,6 +1270,39 @@ public void visit(LikeExpression expression) {
type[0] = Boolean.class;
}

@Override
public void visit(LocalExpression expression) {
expression.getDateType().accept(this);
// Type is set by child expression
}

// LocalDateTime visitor helper method
private void buildLocalDate() {
queryExpression = queryContext.getBaseExpression().localDate();
type[0] = LocalDate.class;
}

// LocalDateTime visitor helper method
private void buildLocalTime() {
queryExpression = queryContext.getBaseExpression().localTime();
type[0] = LocalTime.class;
}

// LocalDateTime visitor helper method
private void buildLocalDateTime() {
queryExpression = queryContext.getBaseExpression().localDateTime();
type[0] = LocalDateTime.class;
}

@Override
public void visit(LocalDateTime expression) {
expression.runByType(
this::buildLocalDate,
this::buildLocalTime,
this::buildLocalDateTime
);
}

@Override
public void visit(LocateExpression expression) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Oracle - initial API and implementation
// 04/21/2022: Tomas Kraus
// - Issue 1474: Update JPQL Grammar for Jakarta Persistence 2.2, 3.0 and 3.1
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.internal.jpa.jpql;

import java.lang.reflect.Field;
Expand All @@ -23,6 +24,8 @@
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -755,6 +758,21 @@ public void visit(LikeExpression expression) {
type = Boolean.class;
}

@Override
public void visit(LocalExpression expression) {
// Visit the child expression (LocalDateTime) in order to set the type
expression.getDateType().accept(this);
}

@Override
public void visit(LocalDateTime expression) {
type = expression.getValueByType(
() -> LocalDate.class,
() -> LocalTime.class,
() -> LocalDateTime.class
);
}

@Override
public void visit(LocateExpression expression) {
type = Integer.class;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/

// Contributors:
// 04/21/2022: Tomas Kraus
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.jpa.test.jpql;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

import org.eclipse.persistence.jpa.test.criteria.model.DateTimeEntity;
import org.eclipse.persistence.jpa.test.framework.DDLGen;
import org.eclipse.persistence.jpa.test.framework.Emf;
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.framework.Property;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(EmfRunner.class)
public class TestDateTimeFunctions {

@Emf(createTables = DDLGen.DROP_CREATE,
classes = {
DateTimeEntity.class
},
properties = {
@Property(name = "eclipselink.cache.shared.default", value = "false"),
// This property remaps String from VARCHAR->NVARCHAR(or db equivalent)
@Property(name = "eclipselink.target-database-properties",
value = "UseNationalCharacterVaryingTypeForString=true"),
})
private EntityManagerFactory emf;

private final LocalDateTime[] TS = {
LocalDateTime.of(1970, 1, 1, 1, 11, 11),
LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)
};

private final DateTimeEntity[] ENTITY = {
new DateTimeEntity(1, TS[0].toLocalTime(), TS[0].toLocalDate(), TS[0]),
new DateTimeEntity(2, TS[0].toLocalTime(), TS[0].toLocalDate(), TS[0]),
new DateTimeEntity(3, TS[0].toLocalTime(), TS[0].toLocalDate(), TS[0]),
new DateTimeEntity(4, TS[1].toLocalTime(), TS[1].toLocalDate(), TS[1])
};

// Database vs. Java timezone offset in seconds. Must be applied to LocalDateTime calculations.
private long dbOffset = 0;

// Update database vs. Java timezone offset using current database time.
private void updateDbOffset() {
final EntityManager em = emf.createEntityManager();
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<LocalTime> cq = cb.createQuery(LocalTime.class);
cq.select(cb.localTime());
Root<DateTimeEntity> entity = cq.from(DateTimeEntity.class);
cq.where(cb.equal(entity.get("id"), 1));
LocalTime dbTime = em.createQuery(cq).getSingleResult();
LocalTime javaTime = LocalTime.now();
this.dbOffset = dbTime.truncatedTo(ChronoUnit.SECONDS).toSecondOfDay() - javaTime.truncatedTo(ChronoUnit.SECONDS).toSecondOfDay();
} catch (Throwable t) {
AbstractSessionLog.getLog().log(SessionLog.WARNING, "Can't update DB offset: " + t.getMessage());
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}

@Before
public void setup() {
final EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
for (DateTimeEntity e : ENTITY) {
em.persist(e);
}
em.flush();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
updateDbOffset();
}

@After
public void cleanup() {
final EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
em.createQuery("DELETE FROM DateTimeEntity e").executeUpdate();
em.flush();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}

// Test JPQL with localTime in WHERE condition.
@Test
public void testCriteriaQueryWhereLocalTime() {
final EntityManager em = emf.createEntityManager();
try {
TypedQuery<Integer> query = em.createQuery(
"SELECT e.id FROM DateTimeEntity e WHERE e.time < LOCAL TIME AND e.id = :id",
Integer.class);
query.setParameter("id", 4);
em.getTransaction().begin();
query.getSingleResult();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}

// Test JPQL with localDate in WHERE condition.
@Test
public void testCriteriaQueryWhereLocalDate() {
final EntityManager em = emf.createEntityManager();
try {
TypedQuery<Integer> query = em.createQuery(
"SELECT e.id FROM DateTimeEntity e WHERE e.date < LOCAL DATE AND e.id = :id",
Integer.class);
query.setParameter("id", 4);
em.getTransaction().begin();
query.getSingleResult();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}

// Test JPQL with localDateTime in WHERE condition.
@Test
public void testCriteriaQueryWhereLocalDateTime() {
final EntityManager em = emf.createEntityManager();
try {
TypedQuery<Integer> query = em.createQuery(
"SELECT e.id FROM DateTimeEntity e WHERE e.datetime < LOCAL DATETIME AND e.id = :id",
Integer.class);
query.setParameter("id", 4);
em.getTransaction().begin();
query.getSingleResult();
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
em.close();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Oracle - initial API and implementation
// 04/21/2022: Tomas Kraus
// - Issue 1474: Update JPQL Grammar for Jakarta Persistence 2.2, 3.0 and 3.1
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.jpa.jpql.parser;

/**
Expand Down Expand Up @@ -199,13 +200,21 @@ public void visit(KeywordExpression expression) {
public void visit(LengthExpression expression) {
}

@Override
public void visit(LocalExpression expression) {
}

@Override
public void visit(LocalDateTime expression) {
}

@Override
public void visit(LikeExpression expression) {
}

@Override
public void visit(LocateExpression expression) {
}
@Override
public void visit(LocateExpression expression) {
}

@Override
public void visit(LowerExpression expression) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Oracle - initial API and implementation
// 04/21/2022: Tomas Kraus
// - Issue 1474: Update JPQL Grammar for Jakarta Persistence 2.2, 3.0 and 3.1
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.jpa.jpql.parser;

/**
Expand Down Expand Up @@ -253,6 +254,16 @@ public void visit(LikeExpression expression) {
visit((Expression) expression);
}

@Override
public void visit(LocalExpression expression) {
visit((Expression) expression);
}

@Override
public void visit(LocalDateTime expression) {
visit((Expression) expression);
}

@Override
public void visit(LocateExpression expression) {
visit((Expression) expression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Oracle - initial API and implementation
// 04/21/2022: Tomas Kraus
// - Issue 1474: Update JPQL Grammar for Jakarta Persistence 2.2, 3.0 and 3.1
// - Issue 317: Implement LOCAL DATE, LOCAL TIME and LOCAL DATETIME.
package org.eclipse.persistence.jpa.jpql.parser;

import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
Expand Down Expand Up @@ -167,6 +168,16 @@ public interface Expression {
*/
String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";

/**
* The constant for 'DATE'.
*/
String DATE = "DATE";

/**
* The constant for 'DATETIME'.
*/
String DATETIME = "DATETIME";

/**
* The constant for 'DELETE'.
*/
Expand Down Expand Up @@ -434,6 +445,11 @@ public interface Expression {
*/
String LN = "LN";

/**
* The constant for 'LOCAL'.
*/
String LOCAL = "LOCAL";

/**
* The constant for 'LOCATE'.
*/
Expand Down Expand Up @@ -727,6 +743,11 @@ public interface Expression {
*/
String THEN = "THEN";

/**
* The constant for 'TIME'.
*/
String TIME = "TIME";

/**
* The constant for 'TIMESTAMP'.
*
Expand Down

0 comments on commit 9581b0b

Please sign in to comment.