Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1376,23 +1376,27 @@ public void setClobString(PreparedStatement stmnt, int idx, String val,
*/
public void setDate(PreparedStatement stmnt, int idx, Date val, Column col)
throws SQLException {
if (col != null && col.getType() == Types.DATE)
if (col != null && col.getType() == Types.DATE) {
setDate(stmnt, idx, new java.sql.Date(val.getTime()), null, col);
else if (col != null && col.getType() == Types.TIME)
} else if (col != null && col.getType() == Types.TIME) {
setTime(stmnt, idx, new Time(val.getTime()), null, col);
else if (val instanceof Timestamp)
setTimestamp(stmnt, idx,(Timestamp) val, null, col);
else
} else if (val instanceof Timestamp timestamp) {
setTimestamp(stmnt, idx, timestamp, null, col);
} else {
setTimestamp(stmnt, idx, new Timestamp(val.getTime()), null, col);
}
}

/**
* Set the given value as a parameter to the statement.
* Without a calendar, a value for a DATE column or for no known column is bound as the date of its day,
* normalized to the start of the day as JDBC requires: drivers such as Oracle's store and compare the
* time of day of a {@link java.sql.Date} otherwise.
*/
public void setDate(PreparedStatement stmnt, int idx, java.sql.Date val, Calendar cal, Column col)
throws SQLException {
if (cal == null) {
stmnt.setDate(idx, val);
stmnt.setDate(idx, col == null || col.getType() == Types.DATE ? Filters.toSqlDate(val) : val);
}
else {
stmnt.setDate(idx, val, cal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,9 +787,26 @@ public LocalDate getLocalDate(ResultSet rs, int column) throws SQLException {
return rs.getObject(column, LocalDate.class);
}

/**
* A time held in a DATE column, the default mapping of times on Oracle, loses the fraction of its
* second when it is stored. A time bound for such a column is bound at that precision, so that a
* time parameter equals the value stored for the same time.
*/
@Override
public void setLocalTime(PreparedStatement stmnt, int idx, LocalTime val, Column col) throws SQLException {
stmnt.setObject(idx, val);
stmnt.setObject(idx, isTimeInDateColumn(col) ? val.withNano(0) : val);
}

private boolean isTimeInDateColumn(Column col) {
if (col == null) {
return false;
}
String typeName = col.getTypeName();
if (typeName != null) {
return typeName.toUpperCase(Locale.ENGLISH).startsWith("DATE");
}
return (col.getType() == Types.TIME && "DATE".equalsIgnoreCase(timeTypeName))
|| (col.getType() == Types.TIME_WITH_TIMEZONE && "DATE".equalsIgnoreCase(timeWithZoneTypeName));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.apache.openjpa.enhance.Reflection;
Expand Down Expand Up @@ -496,6 +497,21 @@ private static Instant toInstant(Date date) {
return (date instanceof Timestamp) ? date.toInstant() : Instant.ofEpochMilli(date.getTime());
}

/**
* The {@link java.sql.Date} of the day of the given date in the default time zone, normalized to the
* start of that day as JDBC requires. {@code new java.sql.Date(millis)} keeps the time of day, which
* drivers such as Oracle's bind as part of a DATE value.
*/
public static java.sql.Date toSqlDate(Date date) {
Calendar day = new GregorianCalendar();
day.setTime(date);
Calendar start = new GregorianCalendar();
start.clear();
start.set(Calendar.ERA, day.get(Calendar.ERA));
start.set(day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH));
return new java.sql.Date(start.getTimeInMillis());
}

private static boolean allowNumericConversion(Class<?> actual, Class<?> target, boolean strict) {
if (!strict || actual == target)
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.query;

import java.time.LocalDate;
import java.util.Calendar;
import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Parameter;
import jakarta.persistence.Query;
import jakarta.persistence.TemporalType;

import org.apache.openjpa.persistence.detach.Entity20;
import org.apache.openjpa.persistence.simple.TemporalFieldTypes;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

/**
* A Calendar, java.util.Date or java.sql.Date bound for a DATE column or with TemporalType.DATE, as a query
* parameter or as the value of a DATE attribute, is bound as a java.sql.Date without its time of day, so that
* dates compare equal also on databases such as Oracle whose DATE values have a time of day.
*/
public class TestDateTemporalTypeParameter extends SingleEMFTestCase {

@Override
public void setUp() {
setUp(CLEAR_TABLES, Entity20.class, TemporalFieldTypes.class);
}

public void testCalendarAndDateParametersAreBoundWithoutTimeOfDay() {
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR_OF_DAY, 13);
java.sql.Date today = java.sql.Date.valueOf(LocalDate.of(now.get(Calendar.YEAR),
now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH)));
persistEntity20(today);

try (EntityManager em = emf.createEntityManager()) {
Query q = em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = ?1")
.setParameter(1, now, TemporalType.DATE);
assertEquals(today, q.getParameterValue(1));
assertIds(q.getResultList());

q = em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = :d")
.setParameter("d", now.getTime(), TemporalType.DATE);
assertEquals(today, q.getParameterValue("d"));
assertIds(q.getResultList());

// the parameter is of the type of the attribute, a Calendar is bound to it with a TemporalType
q = em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = :d");
@SuppressWarnings("unchecked")
Parameter<Calendar> d = (Parameter<Calendar>) q.getParameter("d");
q.setParameter(d, now, TemporalType.DATE);
assertSame(now, q.getParameterValue("d"));
assertIds(q.getResultList());
}
}

public void testDateAttributesAreStoredWithoutTimeOfDay() {
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR_OF_DAY, 13);
java.sql.Date today = java.sql.Date.valueOf(LocalDate.of(now.get(Calendar.YEAR),
now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH)));

try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
TemporalFieldTypes e = new TemporalFieldTypes();
e.setDateDateField(now.getTime());
e.setCalendarDateField((Calendar) now.clone());
em.persist(e);
em.getTransaction().commit();
em.clear();

assertEquals(1, em.createQuery("SELECT t FROM TemporalFieldTypes t WHERE t.dateDateField = :d")
.setParameter("d", now, TemporalType.DATE).getResultList().size());
assertEquals(1, em.createQuery("SELECT t FROM TemporalFieldTypes t WHERE t.calendarDateField = :d")
.setParameter("d", now, TemporalType.DATE).getResultList().size());
assertEquals(1, em.createQuery("SELECT t FROM TemporalFieldTypes t WHERE t.dateDateField = :d")
.setParameter("d", today).getResultList().size());
assertEquals(1, em.createQuery("SELECT t FROM TemporalFieldTypes t WHERE t.calendarDateField = :d")
.setParameter("d", today).getResultList().size());
}
}

public void testSqlDateWithTimeOfDayIsBoundWithoutIt() {
Calendar now = Calendar.getInstance();
now.set(Calendar.HOUR_OF_DAY, 13);
java.sql.Date today = java.sql.Date.valueOf(LocalDate.of(now.get(Calendar.YEAR),
now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH)));
java.sql.Date withTimeOfDay = new java.sql.Date(now.getTimeInMillis());
persistEntity20(withTimeOfDay);

try (EntityManager em = emf.createEntityManager()) {
assertIds(em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = :d")
.setParameter("d", today).getResultList());
assertIds(em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = :d")
.setParameter("d", withTimeOfDay).getResultList());
assertIds(em.createQuery("SELECT e FROM Entity20 e WHERE e.sqlDate = :d")
.setParameter("d", now, TemporalType.DATE).getResultList());
}
}

private void persistEntity20(java.sql.Date date) {
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
Entity20 e = new Entity20(1);
e.setDate(date);
em.persist(e);
em.getTransaction().commit();
}
}

private static void assertIds(List<?> result) {
assertEquals(1, result.size());
assertEquals(Integer.valueOf(1), ((Entity20) result.get(0)).getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.simple;

import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.util.List;

import jakarta.persistence.EntityManager;

import org.apache.openjpa.persistence.test.SingleEMFTestCase;

/**
* A time parameter with a fraction of a second finds the entity stored with the same time, also where
* the time column cannot hold the fraction, such as the DATE column a time is mapped to on Oracle.
*/
public class TestTimeParameterPrecision extends SingleEMFTestCase {

private static final LocalTime TIME = LocalTime.of(10, 11, 12, 345_678_000);
private static final OffsetTime OFFSET_TIME = OffsetTime.of(TIME, ZoneOffset.UTC);

@Override
public void setUp() {
setUp(CLEAR_TABLES, Java8TimeTypes.class);
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
Java8TimeTypes e = new Java8TimeTypes();
e.setId(1);
e.setLocalTimeField(TIME);
e.setOffsetTimeField(OFFSET_TIME);
em.persist(e);
em.getTransaction().commit();
}
}

public void testFindByLocalTime() {
try (EntityManager em = emf.createEntityManager()) {
List<Integer> ids = em.createQuery("SELECT e.id FROM Java8TimeTypes e WHERE e.localTimeField = :t",
Integer.class).setParameter("t", TIME).getResultList();
assertEquals(List.of(1), ids);
}
}

public void testFindByOffsetTime() {
try (EntityManager em = emf.createEntityManager()) {
List<Integer> ids = em.createQuery("SELECT e.id FROM Java8TimeTypes e WHERE e.offsetTimeField = :t",
Integer.class).setParameter("t", OFFSET_TIME).getResultList();
assertEquals(List.of(1), ids);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,14 @@ public OpenJPAQuery<X> setParameter(int position, Date value, TemporalType type)

/**
* Converts the given Date to a value corresponding to given temporal type.
* A {@link java.sql.Date} is the date of the day of the value, normalized to the start of the day as JDBC
* requires: drivers such as Oracle's bind the time of day of a date otherwise, and the value then never
* equals a stored date.
*/
Object convertTemporalType(Date value, TemporalType type) {
switch (type) {
case DATE:
return new java.sql.Date(value.getTime());
return Filters.toSqlDate(value);
case TIME:
return new Time(value.getTime());
case TIMESTAMP:
Expand Down
Loading