[Git][java-team/libpostgresql-jdbc-java][master] 3 commits: New upstream version 42.3.4
Christoph Berg (@myon)
gitlab at salsa.debian.org
Mon May 2 15:01:18 BST 2022
Christoph Berg pushed to branch master at Debian Java Maintainers / libpostgresql-jdbc-java
Commits:
98ccfa54 by Christoph Berg at 2022-05-02T15:56:32+02:00
New upstream version 42.3.4
- - - - -
6326e0b4 by Christoph Berg at 2022-05-02T15:56:35+02:00
Update upstream source from tag 'upstream/42.3.4'
Update to upstream version '42.3.4'
with Debian dir 3cfc65a7c93b5b80f338d5df10dccad07bfeed34
- - - - -
27013f2d by Christoph Berg at 2022-05-02T15:57:40+02:00
releasing package libpgjava version 42.3.4-1
- - - - -
19 changed files:
- debian/changelog
- pom.xml
- src/main/java/org/postgresql/Driver.java
- src/main/java/org/postgresql/PGProperty.java
- src/main/java/org/postgresql/ds/common/BaseDataSource.java
- src/main/java/org/postgresql/gss/GssAction.java
- src/main/java/org/postgresql/gss/GssEncAction.java
- src/main/java/org/postgresql/gss/MakeGSS.java
- src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
- src/main/java/org/postgresql/jdbc/PgResultSet.java
- src/main/java/org/postgresql/jdbc/TimestampUtils.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/java/org/postgresql/util/PGInterval.java
- src/main/resources/META-INF/MANIFEST.MF
- src/test/java/org/postgresql/test/jdbc2/DateTest.java
- src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
- src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java
- src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
- src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+libpgjava (42.3.4-1) unstable; urgency=medium
+
+ * New upstream version 42.3.4.
+
+ -- Christoph Berg <myon at debian.org> Mon, 02 May 2022 15:56:41 +0200
+
libpgjava (42.3.3-1) unstable; urgency=medium
* New upstream version 42.3.3.
=====================================
pom.xml
=====================================
@@ -10,7 +10,7 @@
<artifactId>postgresql</artifactId>
<packaging>jar</packaging>
<name>PostgreSQL JDBC Driver - JDBC 4.2</name>
- <version>42.3.3</version>
+ <version>42.3.4</version>
<description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
<url>https://github.com/pgjdbc/pgjdbc</url>
=====================================
src/main/java/org/postgresql/Driver.java
=====================================
@@ -240,7 +240,9 @@ public class Driver implements java.sql.Driver {
}
// parse URL and add more properties
if ((props = parseURL(url, props)) == null) {
- return null;
+ throw new PSQLException(
+ GT.tr("Unable to parse URL "),
+ PSQLState.UNEXPECTED_ERROR);
}
try {
@@ -654,9 +656,9 @@ public class Driver implements java.sql.Driver {
* @return the address portion of the URL
*/
private static HostSpec[] hostSpecs(Properties props) {
- String[] hosts = castNonNull(props.getProperty("PGHOST")).split(",");
- String[] ports = castNonNull(props.getProperty("PGPORT")).split(",");
- String localSocketAddress = props.getProperty("localSocketAddress");
+ String[] hosts = castNonNull(PGProperty.PG_HOST.get(props)).split(",");
+ String[] ports = castNonNull(PGProperty.PG_PORT.get(props)).split(",");
+ String localSocketAddress = PGProperty.LOCAL_SOCKET_ADDRESS.get(props);
HostSpec[] hostSpecs = new HostSpec[hosts.length];
for (int i = 0; i < hostSpecs.length; ++i) {
hostSpecs[i] = new HostSpec(hosts[i], Integer.parseInt(ports[i]), localSocketAddress);
=====================================
src/main/java/org/postgresql/PGProperty.java
=====================================
@@ -292,6 +292,15 @@ public enum PGProperty {
"false",
"If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates"),
+ /**
+ * <p>If this is set then the client side will bind to this address. This is useful if you need
+ * to choose which interface to connect to.</p>
+ */
+ LOCAL_SOCKET_ADDRESS(
+ "localSocketAddress",
+ null,
+ "Local Socket address, if set bind the client side of the socket to this address"),
+
/**
* This property is no longer used by the driver and will be ignored.
* Logging is configured via java.util.logging.
@@ -742,7 +751,6 @@ public enum PGProperty {
private final boolean required;
private final String description;
private final String /* @Nullable */ [] choices;
- private final boolean deprecated;
PGProperty(String name, /* @Nullable */ String defaultValue, String description) {
this(name, defaultValue, description, false);
@@ -759,11 +767,6 @@ public enum PGProperty {
this.required = required;
this.description = description;
this.choices = choices;
- try {
- this.deprecated = PGProperty.class.getField(name()).getAnnotation(Deprecated.class) != null;
- } catch (NoSuchFieldException e) {
- throw new RuntimeException(e);
- }
}
private static final Map<String, PGProperty> PROPS_BY_NAME = new HashMap<String, PGProperty>();
@@ -822,15 +825,6 @@ public enum PGProperty {
return choices;
}
- /**
- * Returns whether this connection parameter is deprecated.
- *
- * @return whether this connection parameter is deprecated
- */
- public boolean isDeprecated() {
- return deprecated;
- }
-
/**
* Returns the value of the connection parameters according to the given {@code Properties} or the
* default value.
=====================================
src/main/java/org/postgresql/ds/common/BaseDataSource.java
=====================================
@@ -1207,6 +1207,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
return PGProperty.REPLICATION.get(properties);
}
+ /**
+ * @return the localSocketAddress
+ * @see PGProperty#LOCAL_SOCKET_ADDRESS
+ */
+ public /* @Nullable */ String getLocalSocketAddress() {
+ return PGProperty.LOCAL_SOCKET_ADDRESS.get(properties);
+ }
+
+ /**
+ * @param localSocketAddress local address to bind client side to
+ * @see PGProperty#LOCAL_SOCKET_ADDRESS
+ */
+ public void setLocalSocketAddress( String localSocketAddress ) {
+ PGProperty.LOCAL_SOCKET_ADDRESS.set(properties,localSocketAddress);
+ }
+
/**
* This property is no longer used by the driver and will be ignored.
* @return loggerLevel in properties
=====================================
src/main/java/org/postgresql/gss/GssAction.java
=====================================
@@ -35,15 +35,17 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
private final PGStream pgStream;
private final String host;
private final String kerberosServerName;
+ private final String user;
private final boolean useSpnego;
private final Subject subject;
private final boolean logServerErrorDetail;
- GssAction(PGStream pgStream, Subject subject, String host,
+ GssAction(PGStream pgStream, Subject subject, String host, String user,
String kerberosServerName, boolean useSpnego, boolean logServerErrorDetail) {
this.pgStream = pgStream;
this.subject = subject;
this.host = host;
+ this.user = user;
this.kerberosServerName = kerberosServerName;
this.useSpnego = useSpnego;
this.logServerErrorDetail = logServerErrorDetail;
@@ -68,25 +70,44 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
GSSManager manager = GSSManager.getInstance();
GSSCredential clientCreds = null;
Oid[] desiredMechs = new Oid[1];
- if (useSpnego && hasSpnegoSupport(manager)) {
- desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
- } else {
- desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+
+ //Try to get credential from subject first.
+ GSSCredential gssCredential = null;
+ if (subject != null) {
+ Set<GSSCredential> gssCreds = subject.getPrivateCredentials(GSSCredential.class);
+ if (gssCreds != null && !gssCreds.isEmpty()) {
+ gssCredential = gssCreds.iterator().next();
+ }
}
- Set<Principal> principals = subject.getPrincipals();
- Iterator<Principal> principalIterator = principals.iterator();
- Principal principal = null;
- if (principalIterator.hasNext()) {
- principal = principalIterator.next();
+ //If failed to get credential from subject,
+ //then call createCredential to create one.
+ if (gssCredential == null) {
+ if (useSpnego && hasSpnegoSupport(manager)) {
+ desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
+ } else {
+ desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+ }
+ String principalName = this.user;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ Iterator<Principal> principalIterator = principals.iterator();
+
+ Principal principal = null;
+ if (principalIterator.hasNext()) {
+ principal = principalIterator.next();
+ principalName = principal.getName();
+ }
+ }
+
+ GSSName clientName = manager.createName(principalName, GSSName.NT_USER_NAME);
+ clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
+ GSSCredential.INITIATE_ONLY);
} else {
- return new Exception("No principal found, unexpected error please report");
+ desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+ clientCreds = gssCredential;
}
- GSSName clientName = manager.createName(principal.getName(), GSSName.NT_USER_NAME);
- clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
- GSSCredential.INITIATE_ONLY);
-
GSSName serverName =
manager.createName(kerberosServerName + "@" + host, GSSName.NT_HOSTBASED_SERVICE);
=====================================
src/main/java/org/postgresql/gss/GssEncAction.java
=====================================
@@ -69,24 +69,43 @@ public class GssEncAction implements PrivilegedAction</* @Nullable */ Exception>
GSSManager manager = GSSManager.getInstance();
GSSCredential clientCreds = null;
Oid[] desiredMechs = new Oid[1];
- if (useSpnego && hasSpnegoSupport(manager)) {
- desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
- } else {
- desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
- }
- Set<Principal> principals = subject.getPrincipals();
- Iterator<Principal> principalIterator = principals.iterator();
- Principal principal = null;
- if (principalIterator.hasNext()) {
- principal = principalIterator.next();
- } else {
- return new Exception("No principal found, unexpected error please report");
+
+ //Try to get credential from subject first.
+ GSSCredential gssCredential = null;
+ if (subject != null) {
+ Set<GSSCredential> gssCreds = subject.getPrivateCredentials(GSSCredential.class);
+ if (gssCreds != null && !gssCreds.isEmpty()) {
+ gssCredential = gssCreds.iterator().next();
+ }
}
- GSSName clientName = manager.createName(principal.getName(), GSSName.NT_USER_NAME);
- clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
- GSSCredential.INITIATE_ONLY);
+ //If failed to get credential from subject,
+ //then call createCredential to create one.
+ if (gssCredential == null) {
+ if (useSpnego && hasSpnegoSupport(manager)) {
+ desiredMechs[0] = new Oid("1.3.6.1.5.5.2");
+ } else {
+ desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+ }
+ String principalName = this.user;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ Iterator<Principal> principalIterator = principals.iterator();
+
+ Principal principal = null;
+ if (principalIterator.hasNext()) {
+ principal = principalIterator.next();
+ principalName = principal.getName();
+ }
+ }
+ GSSName clientName = manager.createName(principalName, GSSName.NT_USER_NAME);
+ clientCreds = manager.createCredential(clientName, 8 * 3600, desiredMechs,
+ GSSCredential.INITIATE_ONLY);
+ } else {
+ desiredMechs[0] = new Oid("1.2.840.113554.1.2.2");
+ clientCreds = gssCredential;
+ }
GSSName serverName =
manager.createName(kerberosServerName + "@" + host, GSSName.NT_HOSTBASED_SERVICE);
=====================================
src/main/java/org/postgresql/gss/MakeGSS.java
=====================================
@@ -14,9 +14,12 @@ import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
// import org.checkerframework.checker.nullness.qual.Nullable;
+import org.ietf.jgss.GSSCredential;
import java.io.IOException;
+import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -45,16 +48,27 @@ public class MakeGSS {
try {
boolean performAuthentication = jaasLogin;
- LoginContext lc = new LoginContext(castNonNull(jaasApplicationName), new GSSCallbackHandler(user, password));
- lc.login();
- Subject sub = lc.getSubject();
+ //Check if we can get credential from subject to avoid login.
+ Subject sub = Subject.getSubject(AccessController.getContext());
+ if (sub != null) {
+ Set<GSSCredential> gssCreds = sub.getPrivateCredentials(GSSCredential.class);
+ if (gssCreds != null && !gssCreds.isEmpty()) {
+ performAuthentication = false;
+ }
+ }
+ if (performAuthentication) {
+ LoginContext lc = new LoginContext(castNonNull(jaasApplicationName), new GSSCallbackHandler(user, password));
+ lc.login();
+ sub = lc.getSubject();
+ }
+
if ( encrypted ) {
PrivilegedAction</* @Nullable */ Exception> action = new GssEncAction(pgStream, sub, host, user,
kerberosServerName, useSpnego, logServerErrorDetail);
result = Subject.doAs(sub, action);
} else {
- PrivilegedAction</* @Nullable */ Exception> action = new GssAction(pgStream, sub, host,
+ PrivilegedAction</* @Nullable */ Exception> action = new GssAction(pgStream, sub, host, user,
kerberosServerName, useSpnego, logServerErrorDetail);
result = Subject.doAs(sub, action);
=====================================
src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
=====================================
@@ -69,6 +69,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
+import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Map;
@@ -614,6 +615,9 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
} else if (in instanceof LocalTime) {
setTime(parameterIndex, (LocalTime) in);
break;
+ } else if (in instanceof OffsetTime) {
+ setTime(parameterIndex, (OffsetTime) in);
+ break;
} else {
tmpt = getTimestampUtils().toTime(getDefaultCalendar(), in.toString());
}
@@ -988,6 +992,8 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
setDate(parameterIndex, (LocalDate) x);
} else if (x instanceof LocalTime) {
setTime(parameterIndex, (LocalTime) x);
+ } else if (x instanceof OffsetTime) {
+ setTime(parameterIndex, (OffsetTime) x);
} else if (x instanceof LocalDateTime) {
setTimestamp(parameterIndex, (LocalDateTime) x);
} else if (x instanceof OffsetDateTime) {
@@ -1439,6 +1445,11 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
bindString(i, getTimestampUtils().toString(localTime), oid);
}
+ private void setTime(/* @Positive */ int i, OffsetTime offsetTime) throws SQLException {
+ int oid = Oid.TIMETZ;
+ bindString(i, getTimestampUtils().toString(offsetTime), oid);
+ }
+
private void setTimestamp(/* @Positive */ int i, LocalDateTime localDateTime)
throws SQLException {
int oid = Oid.TIMESTAMP;
=====================================
src/main/java/org/postgresql/jdbc/PgResultSet.java
=====================================
@@ -8,7 +8,6 @@ package org.postgresql.jdbc;
import static org.postgresql.util.internal.Nullness.castNonNull;
import org.postgresql.PGResultSetMetaData;
-import org.postgresql.PGStatement;
import org.postgresql.core.BaseConnection;
import org.postgresql.core.BaseStatement;
import org.postgresql.core.Encoding;
@@ -74,6 +73,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
+import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -586,29 +586,6 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
return getTimestampUtils().toTime(cal, string);
}
- private /* @Nullable */ LocalTime getLocalTime(int i) throws SQLException {
- byte[] value = getRawValue(i);
- if (value == null) {
- return null;
- }
-
- if (isBinary(i)) {
- int col = i - 1;
- int oid = fields[col].getOID();
- if (oid == Oid.TIME) {
- return getTimestampUtils().toLocalTimeBin(value);
- } else {
- throw new PSQLException(
- GT.tr("Cannot convert the column of type {0} to requested type {1}.",
- Oid.toString(oid), "time"),
- PSQLState.DATA_TYPE_MISMATCH);
- }
- }
-
- String string = getString(i);
- return getTimestampUtils().toLocalTime(string);
- }
-
/* @Pure */
@Override
public /* @Nullable */ Timestamp getTimestamp(
@@ -672,6 +649,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
}
+ // TODO: In Java 8 this constant is missing, later versions (at least 11) have LocalDate#EPOCH:
+ private static final LocalDate LOCAL_DATE_EPOCH = LocalDate.of(1970, 1, 1);
+
private /* @Nullable */ OffsetDateTime getOffsetDateTime(int i) throws SQLException {
byte[] value = getRawValue(i);
if (value == null) {
@@ -681,35 +661,48 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
int col = i - 1;
int oid = fields[col].getOID();
+ // TODO: Disallow getting OffsetDateTime from a non-TZ field
if (isBinary(i)) {
if (oid == Oid.TIMESTAMPTZ || oid == Oid.TIMESTAMP) {
return getTimestampUtils().toOffsetDateTimeBin(value);
} else if (oid == Oid.TIMETZ) {
// JDBC spec says timetz must be supported
- Time time = getTime(i);
- if (time == null) {
- return null;
- }
- return getTimestampUtils().toOffsetDateTime(time);
- } else {
- throw new PSQLException(
- GT.tr("Cannot convert the column of type {0} to requested type {1}.",
- Oid.toString(oid), "timestamptz"),
- PSQLState.DATA_TYPE_MISMATCH);
+ return getTimestampUtils().toOffsetTimeBin(value).atDate(LOCAL_DATE_EPOCH);
+ }
+ } else {
+ // string
+ if (oid == Oid.TIMESTAMPTZ || oid == Oid.TIMESTAMP || oid == Oid.TIMETZ) {
+ return getTimestampUtils().toOffsetDateTime(castNonNull(getString(i)), oid != Oid.TIMETZ);
}
}
- // If this is actually a timestamptz, the server-provided timezone will override
- // the one we pass in, which is the desired behaviour. Otherwise, we'll
- // interpret the timezone-less value in the provided timezone.
- String string = castNonNull(getString(i));
+
+ throw new PSQLException(
+ GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+ Oid.toString(oid), "java.time.OffsetDateTime"),
+ PSQLState.DATA_TYPE_MISMATCH);
+ }
+
+ private /* @Nullable */ OffsetTime getOffsetTime(int i) throws SQLException {
+ byte[] value = getRawValue(i);
+ if (value == null) {
+ return null;
+ }
+
+ int col = i - 1;
+ int oid = fields[col].getOID();
+
if (oid == Oid.TIMETZ) {
- // JDBC spec says timetz must be supported
- // If server sends us a TIMETZ, we ensure java counterpart has date of 1970-01-01
- Calendar cal = getDefaultCalendar();
- Time time = getTimestampUtils().toTime(cal, string);
- return getTimestampUtils().toOffsetDateTime(time);
+ if (isBinary(i)) {
+ return getTimestampUtils().toOffsetTimeBin(value);
+ } else {
+ return getTimestampUtils().toOffsetTime(castNonNull(getString(i)));
+ }
}
- return getTimestampUtils().toOffsetDateTime(string);
+
+ throw new PSQLException(
+ GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+ Oid.toString(oid), "java.time.OffsetTime"),
+ PSQLState.DATA_TYPE_MISMATCH);
}
private /* @Nullable */ LocalDateTime getLocalDateTime(int i) throws SQLException {
@@ -720,18 +713,70 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
int col = i - 1;
int oid = fields[col].getOID();
- if (oid != Oid.TIMESTAMP) {
- throw new PSQLException(
- GT.tr("Cannot convert the column of type {0} to requested type {1}.",
- Oid.toString(oid), "timestamp"),
- PSQLState.DATA_TYPE_MISMATCH);
+
+ if (oid == Oid.TIMESTAMP) {
+ if (isBinary(i)) {
+ return getTimestampUtils().toLocalDateTimeBin(value);
+ } else {
+ return getTimestampUtils().toLocalDateTime(castNonNull(getString(i)));
+ }
}
+
+ throw new PSQLException(
+ GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+ Oid.toString(oid), "java.time.LocalDateTime"),
+ PSQLState.DATA_TYPE_MISMATCH);
+ }
+
+ private /* @Nullable */ LocalDate getLocalDate(int i) throws SQLException {
+ byte[] value = getRawValue(i);
+ if (value == null) {
+ return null;
+ }
+
+ int col = i - 1;
+ int oid = fields[col].getOID();
+
if (isBinary(i)) {
- return getTimestampUtils().toLocalDateTimeBin(value);
+ if (oid == Oid.DATE) {
+ return getTimestampUtils().toLocalDateBin(value);
+ } else if (oid == Oid.TIMESTAMP) {
+ return getTimestampUtils().toLocalDateTimeBin(value).toLocalDate();
+ }
+ } else {
+ // string
+ if (oid == Oid.DATE || oid == Oid.TIMESTAMP) {
+ return getTimestampUtils().toLocalDateTime(castNonNull(getString(i))).toLocalDate();
+ }
}
- String string = castNonNull(getString(i));
- return getTimestampUtils().toLocalDateTime(string);
+ throw new PSQLException(
+ GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+ Oid.toString(oid), "java.time.LocalDate"),
+ PSQLState.DATA_TYPE_MISMATCH);
+ }
+
+ private /* @Nullable */ LocalTime getLocalTime(int i) throws SQLException {
+ byte[] value = getRawValue(i);
+ if (value == null) {
+ return null;
+ }
+
+ int col = i - 1;
+ int oid = fields[col].getOID();
+
+ if (oid == Oid.TIME) {
+ if (isBinary(i)) {
+ return getTimestampUtils().toLocalTimeBin(value);
+ } else {
+ return getTimestampUtils().toLocalTime(getString(i));
+ }
+ }
+
+ throw new PSQLException(
+ GT.tr("Cannot convert the column of type {0} to requested type {1}.",
+ Oid.toString(oid), "java.time.LocalTime"),
+ PSQLState.DATA_TYPE_MISMATCH);
}
public java.sql./* @Nullable */ Date getDate(
@@ -3696,51 +3741,15 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
}
// JSR-310 support
} else if (type == LocalDate.class) {
- if (sqlType == Types.DATE) {
- Date dateValue = getDate(columnIndex);
- if (dateValue == null) {
- return null;
- }
- long time = dateValue.getTime();
- if (time == PGStatement.DATE_POSITIVE_INFINITY) {
- return type.cast(LocalDate.MAX);
- }
- if (time == PGStatement.DATE_NEGATIVE_INFINITY) {
- return type.cast(LocalDate.MIN);
- }
- return type.cast(dateValue.toLocalDate());
- } else if (sqlType == Types.TIMESTAMP) {
- LocalDateTime localDateTimeValue = getLocalDateTime(columnIndex);
- if (localDateTimeValue == null) {
- return null;
- }
- return type.cast(localDateTimeValue.toLocalDate());
- } else {
- throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
- PSQLState.INVALID_PARAMETER_VALUE);
- }
+ return type.cast(getLocalDate(columnIndex));
} else if (type == LocalTime.class) {
- if (sqlType == Types.TIME) {
- return type.cast(getLocalTime(columnIndex));
- } else {
- throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
- PSQLState.INVALID_PARAMETER_VALUE);
- }
+ return type.cast(getLocalTime(columnIndex));
} else if (type == LocalDateTime.class) {
- if (sqlType == Types.TIMESTAMP) {
- return type.cast(getLocalDateTime(columnIndex));
- } else {
- throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
- PSQLState.INVALID_PARAMETER_VALUE);
- }
+ return type.cast(getLocalDateTime(columnIndex));
} else if (type == OffsetDateTime.class) {
- if (sqlType == Types.TIMESTAMP_WITH_TIMEZONE || sqlType == Types.TIMESTAMP) {
- OffsetDateTime offsetDateTime = getOffsetDateTime(columnIndex);
- return type.cast(offsetDateTime);
- } else {
- throw new PSQLException(GT.tr("conversion to {0} from {1} not supported", type, getPGType(columnIndex)),
- PSQLState.INVALID_PARAMETER_VALUE);
- }
+ return type.cast(getOffsetDateTime(columnIndex));
+ } else if (type == OffsetTime.class) {
+ return type.cast(getOffsetTime(columnIndex));
} else if (PGobject.class.isAssignableFrom(type)) {
Object object;
if (isBinary(columnIndex)) {
=====================================
src/main/java/org/postgresql/jdbc/TimestampUtils.java
=====================================
@@ -30,6 +30,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
+import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.IsoEra;
@@ -38,6 +39,7 @@ import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
+import java.util.Objects;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
@@ -63,6 +65,8 @@ public class TimestampUtils {
private static final LocalDate MIN_LOCAL_DATE = LocalDate.of(4713, 1, 1).with(ChronoField.ERA, IsoEra.BCE.getValue());
private static final LocalDateTime MIN_LOCAL_DATETIME = MIN_LOCAL_DATE.atStartOfDay();
private static final OffsetDateTime MIN_OFFSET_DATETIME = MIN_LOCAL_DATETIME.atOffset(ZoneOffset.UTC);
+ private static final Duration PG_EPOCH_DIFF =
+ Duration.between(Instant.EPOCH, LocalDate.of(2000, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC));
private static final /* @Nullable */ Field DEFAULT_TIME_ZONE_FIELD;
@@ -108,7 +112,6 @@ public class TimestampUtils {
tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
tzField.setAccessible(true);
TimeZone defaultTz = TimeZone.getDefault();
- @SuppressWarnings("nulllability")
Object tzFromField = tzField.get(null);
if (defaultTz == null || !defaultTz.equals(tzFromField)) {
tzField = null;
@@ -125,10 +128,10 @@ public class TimestampUtils {
// This calendar is used when user provides calendar in setX(, Calendar) method.
// It ensures calendar is Gregorian.
private final Calendar calendarWithUserTz = new GregorianCalendar();
- private final TimeZone utcTz = TimeZone.getTimeZone("UTC");
+ private final TimeZone utcTz = TimeZone.getTimeZone(ZoneOffset.UTC);
private /* @Nullable */ Calendar calCache;
- private int calCacheZone;
+ private /* @Nullable */ ZoneOffset calCacheZone;
/**
* True if the backend uses doubles for time values. False if long is used.
@@ -141,30 +144,18 @@ public class TimestampUtils {
this.timeZoneProvider = timeZoneProvider;
}
- private Calendar getCalendar(int sign, int hr, int min, int sec) {
- int rawOffset = sign * (((hr * 60 + min) * 60 + sec) * 1000);
- if (calCache != null && calCacheZone == rawOffset) {
+ private Calendar getCalendar(ZoneOffset offset) {
+ if (calCache != null && Objects.equals(offset, calCacheZone)) {
return calCache;
}
- StringBuilder zoneID = new StringBuilder("GMT");
- zoneID.append(sign < 0 ? '-' : '+');
- if (hr < 10) {
- zoneID.append('0');
- }
- zoneID.append(hr);
- if (min < 10) {
- zoneID.append('0');
- }
- zoneID.append(min);
- if (sec < 10) {
- zoneID.append('0');
- }
- zoneID.append(sec);
-
- TimeZone syntheticTZ = new SimpleTimeZone(rawOffset, zoneID.toString());
+ // normally we would use:
+ // calCache = new GregorianCalendar(TimeZone.getTimeZone(offset));
+ // But this seems to cause issues for some crazy offsets as returned by server for BC dates!
+ final String tzid = (offset.getTotalSeconds() == 0) ? "UTC" : "GMT".concat(offset.getId());
+ final TimeZone syntheticTZ = new SimpleTimeZone(offset.getTotalSeconds() * 1000, tzid);
calCache = new GregorianCalendar(syntheticTZ);
- calCacheZone = rawOffset;
+ calCacheZone = offset;
return calCache;
}
@@ -181,7 +172,8 @@ public class TimestampUtils {
int second = 0;
int nanos = 0;
- /* @Nullable */ Calendar tz = null;
+ boolean hasOffset = false;
+ ZoneOffset offset = ZoneOffset.UTC;
}
private static class ParsedBinaryTimestamp {
@@ -312,6 +304,8 @@ public class TimestampUtils {
// Possibly read timezone.
sep = charAt(s, start);
if (sep == '-' || sep == '+') {
+ result.hasOffset = true;
+
int tzsign = (sep == '-') ? -1 : 1;
int tzhr;
int tzmin;
@@ -338,10 +332,7 @@ public class TimestampUtils {
start = end;
}
- // Setting offset does not seem to work correctly in all
- // cases.. So get a fresh calendar for a synthetic timezone
- // instead
- result.tz = getCalendar(tzsign, tzhr, tzmin, tzsec);
+ result.offset = ZoneOffset.ofHoursMinutesSeconds(tzsign * tzhr, tzsign * tzmin, tzsign * tzsec);
start = skipWhitespace(s, start); // Skip trailing whitespace
}
@@ -368,7 +359,7 @@ public class TimestampUtils {
} catch (NumberFormatException nfe) {
throw new PSQLException(
- GT.tr("Bad value for type timestamp/date/time: {1}", str),
+ GT.tr("Bad value for type timestamp/date/time: {0}", str),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
@@ -401,7 +392,7 @@ public class TimestampUtils {
}
ParsedTimestamp ts = parseBackendTimestamp(s);
- Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
+ Calendar useCal = ts.hasOffset ? getCalendar(ts.offset) : setupCalendar(cal);
useCal.set(Calendar.ERA, ts.era);
useCal.set(Calendar.YEAR, ts.year);
useCal.set(Calendar.MONTH, ts.month - 1);
@@ -436,12 +427,60 @@ public class TimestampUtils {
return LocalTime.parse(s);
} catch (DateTimeParseException nfe) {
throw new PSQLException(
- GT.tr("Bad value for type timestamp/date/time: {1}", s),
+ GT.tr("Bad value for type timestamp/date/time: {0}", s),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
}
+ /**
+ * Returns the offset time object matching the given bytes with Oid#TIMETZ or Oid#TIME.
+ *
+ * @param bytes The binary encoded TIMETZ/TIME value.
+ * @return The parsed offset time object.
+ * @throws PSQLException If binary format could not be parsed.
+ */
+ public OffsetTime toOffsetTimeBin(byte[] bytes) throws PSQLException {
+ if (bytes.length != 12) {
+ throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
+ PSQLState.BAD_DATETIME_FORMAT);
+ }
+
+ final long micros;
+
+ if (usesDouble) {
+ double seconds = ByteConverter.float8(bytes, 0);
+ micros = (long) (seconds * 1_000_000d);
+ } else {
+ micros = ByteConverter.int8(bytes, 0);
+ }
+
+ // postgres offset is negative, so we have to flip sign:
+ final ZoneOffset timeOffset = ZoneOffset.ofTotalSeconds(-ByteConverter.int4(bytes, 8));
+
+ return OffsetTime.of(LocalTime.ofNanoOfDay(Math.multiplyExact(micros, 1000L)), timeOffset);
+ }
+
+ /**
+ * Parse a string and return a OffsetTime representing its value.
+ *
+ * @param s The ISO formated time string to parse.
+ * @return null if s is null or a OffsetTime of the parsed string s.
+ * @throws SQLException if there is a problem parsing s.
+ */
+ public /* @PolyNull */ OffsetTime toOffsetTime(/* @PolyNull */ String s) throws SQLException {
+ if (s == null) {
+ return null;
+ }
+
+ if (s.startsWith("24:00:00")) {
+ return OffsetTime.MAX;
+ }
+
+ final ParsedTimestamp ts = parseBackendTimestamp(s);
+ return OffsetTime.of(ts.hour, ts.minute, ts.second, ts.nanos, ts.offset);
+ }
+
/**
* Parse a string and return a LocalDateTime representing its value.
*
@@ -478,14 +517,16 @@ public class TimestampUtils {
}
/**
- * Parse a string and return a LocalDateTime representing its value.
+ * Parse a string and return a OffsetDateTime representing its value.
*
* @param s The ISO formated date string to parse.
- * @return null if s is null or a LocalDateTime of the parsed string s.
+ * @param adaptToUTC if true the timezone is adapted to be UTC;
+ * this must be done for timestamp and timestamptz as they have no zone on server side
+ * @return null if s is null or a OffsetDateTime of the parsed string s.
* @throws SQLException if there is a problem parsing s.
*/
public /* @PolyNull */ OffsetDateTime toOffsetDateTime(
- /* @PolyNull */ String s) throws SQLException {
+ /* @PolyNull */ String s, boolean adaptToUTC) throws SQLException {
if (s == null) {
return null;
}
@@ -501,19 +542,12 @@ public class TimestampUtils {
return OffsetDateTime.MIN;
}
- ParsedTimestamp ts = parseBackendTimestamp(s);
-
- Calendar tz = ts.tz;
- int offsetSeconds;
- if (tz == null) {
- offsetSeconds = 0;
- } else {
- offsetSeconds = tz.get(Calendar.ZONE_OFFSET) / 1000;
+ final ParsedTimestamp ts = parseBackendTimestamp(s);
+ OffsetDateTime result =
+ OffsetDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos, ts.offset);
+ if (adaptToUTC) {
+ result = result.withOffsetSameInstant(ZoneOffset.UTC);
}
- ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offsetSeconds);
- // Postgres is always UTC
- OffsetDateTime result = OffsetDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos, zoneOffset)
- .withOffsetSameInstant(ZoneOffset.UTC);
if (ts.era == GregorianCalendar.BC) {
return result.with(ChronoField.ERA, IsoEra.BCE.getValue());
} else {
@@ -521,18 +555,6 @@ public class TimestampUtils {
}
}
- /**
- * Returns the offset date time object matching the given bytes with Oid#TIMETZ.
- *
- * @param t the time value
- * @return the matching offset date time
- */
- public OffsetDateTime toOffsetDateTime(Time t) {
- // hardcode utc because the backend does not provide us the timezone
- // hardcode UNIX epoch, JDBC requires OffsetDateTime but doesn't describe what date should be used
- return t.toLocalTime().atDate(LocalDate.of(1970, 1, 1)).atOffset(ZoneOffset.UTC);
- }
-
/**
* Returns the offset date time object matching the given bytes with Oid#TIMESTAMPTZ.
*
@@ -561,8 +583,8 @@ public class TimestampUtils {
return null;
}
ParsedTimestamp ts = parseBackendTimestamp(s);
- Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
- if (ts.tz == null) {
+ Calendar useCal = ts.hasOffset ? getCalendar(ts.offset) : setupCalendar(cal);
+ if (!ts.hasOffset) {
// When no time zone provided (e.g. time or timestamp)
// We get the year-month-day from the string, then truncate the day to 1970-01-01
// This is used for timestamp -> time conversion
@@ -589,7 +611,7 @@ public class TimestampUtils {
useCal.set(Calendar.MILLISECOND, 0);
long timeMillis = useCal.getTimeInMillis() + ts.nanos / 1000000;
- if (ts.tz != null || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
+ if (ts.hasOffset || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
// time with time zone has proper time zone, so the value can be returned as is
return new Time(timeMillis);
}
@@ -873,6 +895,29 @@ public class TimestampUtils {
return sbuf.toString();
}
+ public synchronized String toString(OffsetTime offsetTime) {
+
+ sbuf.setLength(0);
+
+ final LocalTime localTime = offsetTime.toLocalTime();
+ if (localTime.isAfter(MAX_TIME)) {
+ sbuf.append("24:00:00");
+ appendTimeZone(sbuf, offsetTime.getOffset());
+ return sbuf.toString();
+ }
+
+ int nano = offsetTime.getNano();
+ if (nanosExceed499(nano)) {
+ // Technically speaking this is not a proper rounding, however
+ // it relies on the fact that appendTime just truncates 000..999 nanosecond part
+ offsetTime = offsetTime.plus(ONE_MICROSECOND);
+ }
+ appendTime(sbuf, localTime);
+ appendTimeZone(sbuf, offsetTime.getOffset());
+
+ return sbuf.toString();
+ }
+
public synchronized String toString(OffsetDateTime offsetDateTime) {
if (offsetDateTime.isAfter(MAX_OFFSET_DATETIME)) {
return "infinity";
@@ -1019,7 +1064,6 @@ public class TimestampUtils {
// Fast path to getting the default timezone.
if (DEFAULT_TIME_ZONE_FIELD != null) {
try {
- @SuppressWarnings("nullability")
TimeZone defaultTimeZone = (TimeZone) DEFAULT_TIME_ZONE_FIELD.get(null);
if (defaultTimeZone == prevDefaultZoneFieldValue) {
return castNonNull(defaultTimeZoneCache);
@@ -1108,7 +1152,7 @@ public class TimestampUtils {
micros = ByteConverter.int8(bytes, 0);
}
- return LocalTime.ofNanoOfDay(micros * 1000);
+ return LocalTime.ofNanoOfDay(Math.multiplyExact(micros, 1000L));
}
/**
@@ -1229,7 +1273,7 @@ public class TimestampUtils {
long secs = ts.millis / 1000L;
// postgres epoc to java epoc
- secs += 946684800L;
+ secs += PG_EPOCH_DIFF.getSeconds();
long millis = secs * 1000L;
ts.millis = millis;
@@ -1258,6 +1302,29 @@ public class TimestampUtils {
return LocalDateTime.ofEpochSecond(parsedTimestamp.millis / 1000L, parsedTimestamp.nanos, ZoneOffset.UTC);
}
+ /**
+ * Returns the local date time object matching the given bytes with {@link Oid#DATE} or
+ * {@link Oid#TIMESTAMP}.
+ * @param bytes The binary encoded local date value.
+ *
+ * @return The parsed local date object.
+ * @throws PSQLException If binary format could not be parsed.
+ */
+ public LocalDate toLocalDateBin(byte[] bytes) throws PSQLException {
+ if (bytes.length != 4) {
+ throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "date"),
+ PSQLState.BAD_DATETIME_FORMAT);
+ }
+ int days = ByteConverter.int4(bytes, 0);
+ if (days == Integer.MAX_VALUE) {
+ return LocalDate.MAX;
+ } else if (days == Integer.MIN_VALUE) {
+ return LocalDate.MIN;
+ }
+ // adapt from different Postgres Epoch and convert to LocalDate:
+ return LocalDate.ofEpochDay(PG_EPOCH_DIFF.toDays() + days);
+ }
+
/**
* <p>Given a UTC timestamp {@code millis} finds another point in time that is rendered in given time
* zone {@code tz} exactly as "millis in UTC".</p>
@@ -1446,7 +1513,7 @@ public class TimestampUtils {
*/
private static long toJavaSecs(long secs) {
// postgres epoc to java epoc
- secs += 946684800L;
+ secs += PG_EPOCH_DIFF.getSeconds();
// Julian/Gregorian calendar cutoff point
if (secs < -12219292800L) { // October 4, 1582 -> October 15, 1582
@@ -1470,7 +1537,7 @@ public class TimestampUtils {
*/
private static long toPgSecs(long secs) {
// java epoc to postgres epoc
- secs -= 946684800L;
+ secs -= PG_EPOCH_DIFF.getSeconds();
// Julian/Gregorian calendar cutoff point
if (secs < -13165977600L) { // October 15, 1582 -> October 4, 1582
=====================================
src/main/java/org/postgresql/util/DriverInfo.java
=====================================
@@ -16,13 +16,13 @@ public final class DriverInfo {
// Driver name
public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
public static final String DRIVER_SHORT_NAME = "PgJDBC";
- public static final String DRIVER_VERSION = "42.3.3";
+ public static final String DRIVER_VERSION = "42.3.4";
public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
// Driver version
public static final int MAJOR_VERSION = 42;
public static final int MINOR_VERSION = 3;
- public static final int PATCH_VERSION = 3;
+ public static final int PATCH_VERSION = 4;
// JDBC specification
public static final String JDBC_VERSION = "4.2";
=====================================
src/main/java/org/postgresql/util/PGInterval.java
=====================================
@@ -98,13 +98,12 @@ public class PGInterval extends PGobject implements Serializable, Cloneable {
for (int i = 0; i < timeValue.length(); i++) {
int lookAhead = lookAhead(timeValue, i, "HMS");
if (lookAhead > 0) {
- number = Integer.parseInt(timeValue.substring(i, lookAhead));
if (timeValue.charAt(lookAhead) == 'H') {
- setHours(number);
+ setHours(Integer.parseInt(timeValue.substring(i, lookAhead)));
} else if (timeValue.charAt(lookAhead) == 'M') {
- setMinutes(number);
+ setMinutes(Integer.parseInt(timeValue.substring(i, lookAhead)));
} else if (timeValue.charAt(lookAhead) == 'S') {
- setSeconds(number);
+ setSeconds(Double.parseDouble(timeValue.substring(i, lookAhead)));
}
i = lookAhead;
}
=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Implementation-Title: PostgreSQL JDBC Driver
Bundle-License: BSD-2-Clause
Automatic-Module-Name: org.postgresql.jdbc
-Implementation-Version: 42.3.3
+Implementation-Version: 42.3.4
Specification-Vendor: Oracle Corporation
Specification-Title: JDBC
Implementation-Vendor-Id: org.postgresql
=====================================
src/test/java/org/postgresql/test/jdbc2/DateTest.java
=====================================
@@ -8,37 +8,77 @@ package org.postgresql.test.jdbc2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import org.postgresql.test.TestUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
-import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
/*
* Some simple tests based on problems reported by users. Hopefully these will help prevent previous
* problems from re-occurring ;-)
*
*/
-public class DateTest {
- private Connection con;
+ at RunWith(Parameterized.class)
+public class DateTest extends BaseTest4 {
+ private static final TimeZone saveTZ = TimeZone.getDefault();
+
+ private final String type;
+ private final String zoneId;
+
+ public DateTest(String type, String zoneId, BinaryMode binaryMode) {
+ this.type = type;
+ this.zoneId = zoneId;
+ TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+ setBinaryMode(binaryMode);
+ }
+
+ @Parameterized.Parameters(name = "type = {0}, zoneId = {1}, binary = {2}")
+ public static Iterable<Object[]> data() {
+ final List<Object[]> data = new ArrayList<>();
+ for (String type : Arrays.asList("date", "timestamp", "timestamptz")) {
+ Stream<String> tzIds = Stream.of("Africa/Casablanca", "America/New_York", "America/Toronto",
+ "Europe/Berlin", "Europe/Moscow", "Pacific/Apia", "America/Los_Angeles");
+ // some selection of static GMT offsets (not all, as this takes too long):
+ tzIds = Stream.concat(tzIds, IntStream.of(-12, -11, -5, -1, 0, 1, 3, 12, 13)
+ .mapToObj(i -> String.format(Locale.ROOT, "GMT%+02d", i)));
+ for (String tzId : (Iterable<String>) tzIds::iterator) {
+ for (BinaryMode binaryMode : BinaryMode.values()) {
+ data.add(new Object[]{type, tzId, binaryMode});
+ }
+ }
+ }
+ return data;
+ }
@Before
public void setUp() throws Exception {
- con = TestUtil.openDB();
- TestUtil.createTable(con, "testdate", "dt date");
+ super.setUp();
+ TestUtil.createTable(con, "test", "dt ".concat(type));
}
@After
- public void tearDown() throws Exception {
- TestUtil.dropTable(con, "testdate");
- TestUtil.closeDB(con);
+ public void tearDown() throws SQLException {
+ TimeZone.setDefault(saveTZ);
+ TestUtil.dropTable(con, "test");
+ super.tearDown();
}
/*
@@ -46,32 +86,37 @@ public class DateTest {
*/
@Test
public void testGetDate() throws SQLException {
- Statement stmt = con.createStatement();
-
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-02-07'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-06-02'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1999-08-11'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2001-02-13'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-04-02'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-11-30'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1988-01-01'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2003-07-09'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1934-02-28'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1969-04-03'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1982-08-03'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2012-03-15'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1912-05-01'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1971-12-15'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1984-12-03'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2000-01-01'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'3456-01-01'")));
- assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'0101-01-01 BC'")));
-
- /* dateTest() contains all of the tests */
- dateTest();
-
- assertEquals(18, stmt.executeUpdate("DELETE FROM " + "testdate"));
- stmt.close();
+ assumeTrue("TODO: Test fails on some server versions with local time zones (not GMT based)",
+ false == Objects.equals(type, "timestamptz") || zoneId.startsWith("GMT"));
+ try (Statement stmt = con.createStatement()) {
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-02-07'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-06-02'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1999-08-11'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2001-02-13'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-04-02'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-11-30'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1988-01-01'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2003-07-09'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1934-02-28'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1969-04-03'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1982-08-03'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2012-03-15'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1912-05-01'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1971-12-15'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1984-12-03'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2000-01-01'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'3456-01-01'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0101-01-01 BC'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01 BC'")));
+ assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-12-31 BC'")));
+
+ /* dateTest() contains all of the tests */
+ dateTest();
+
+ assertEquals(21, stmt.executeUpdate("DELETE FROM test"));
+ stmt.close();
+ }
}
/*
@@ -79,70 +124,81 @@ public class DateTest {
*/
@Test
public void testSetDate() throws SQLException {
- Statement stmt = con.createStatement();
- PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testdate", "?"));
+ try (Statement stmt = con.createStatement()) {
+ PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("test", "?"));
+
+ ps.setDate(1, makeDate(1950, 2, 7));
+ assertEquals(1, ps.executeUpdate());
+
+ ps.setDate(1, makeDate(1970, 6, 2));
+ assertEquals(1, ps.executeUpdate());
- ps.setDate(1, makeDate(1950, 2, 7));
- assertEquals(1, ps.executeUpdate());
+ ps.setDate(1, makeDate(1999, 8, 11));
+ assertEquals(1, ps.executeUpdate());
- ps.setDate(1, makeDate(1970, 6, 2));
- assertEquals(1, ps.executeUpdate());
+ ps.setDate(1, makeDate(2001, 2, 13));
+ assertEquals(1, ps.executeUpdate());
- ps.setDate(1, makeDate(1999, 8, 11));
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setDate(1, makeDate(2001, 2, 13));
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, "1934-02-28", java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, "1969-04-03", java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, "1934-02-28", java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, "1982-08-03", java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, "1969-04-03", java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, "2012-03-15", java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, "1982-08-03", java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, "2012-03-15", java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE);
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ // We can't use valueOf on BC dates.
+ ps.setObject(1, makeDate(-100, 1, 1));
+ assertEquals(1, ps.executeUpdate());
- ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE);
- assertEquals(1, ps.executeUpdate());
+ ps.setObject(1, makeDate(1, 1, 1));
+ assertEquals(1, ps.executeUpdate());
- // We can't use valueOf on BC dates.
- ps.setObject(1, makeDate(-100, 1, 1));
- assertEquals(1, ps.executeUpdate());
+ // Note: Year 0 in Java is year '0001-01-01 BC' in PostgreSQL.
+ ps.setObject(1, makeDate(0, 1, 1));
+ assertEquals(1, ps.executeUpdate());
- ps.close();
+ ps.setObject(1, makeDate(0, 12, 31));
+ assertEquals(1, ps.executeUpdate());
- dateTest();
+ ps.close();
- assertEquals(18, stmt.executeUpdate("DELETE FROM testdate"));
- stmt.close();
+ dateTest();
+
+ assertEquals(21, stmt.executeUpdate("DELETE FROM test"));
+ stmt.close();
+ }
}
/*
@@ -153,7 +209,7 @@ public class DateTest {
ResultSet rs;
java.sql.Date d;
- rs = st.executeQuery(TestUtil.selectSQL("testdate", "dt"));
+ rs = st.executeQuery(TestUtil.selectSQL("test", "dt"));
assertNotNull(rs);
assertTrue(rs.next());
@@ -246,6 +302,21 @@ public class DateTest {
assertNotNull(d);
assertEquals(makeDate(-100, 1, 1), d);
+ assertTrue(rs.next());
+ d = rs.getDate(1);
+ assertNotNull(d);
+ assertEquals(makeDate(1, 1, 1), d);
+
+ assertTrue(rs.next());
+ d = rs.getDate(1);
+ assertNotNull(d);
+ assertEquals(makeDate(0, 1, 1), d);
+
+ assertTrue(rs.next());
+ d = rs.getDate(1);
+ assertNotNull(d);
+ assertEquals(makeDate(0, 12, 31), d);
+
assertTrue(!rs.next());
rs.close();
=====================================
src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
=====================================
@@ -304,6 +304,16 @@ public class IntervalTest {
assertEquals(-1, pgi.getYears());
assertEquals(-2, pgi.getMonths());
assertEquals(-4, pgi.getHours());
+
+ pgi = new PGInterval("PT6.123456S");
+ assertEquals(6.123456, pgi.getSeconds(), .0);
+ assertEquals(6, pgi.getWholeSeconds());
+ assertEquals(123456, pgi.getMicroSeconds());
+
+ pgi = new PGInterval("PT-6.123456S");
+ assertEquals(-6.123456, pgi.getSeconds(), .0);
+ assertEquals(-6, pgi.getWholeSeconds());
+ assertEquals(-123456, pgi.getMicroSeconds());
}
@Test
=====================================
src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java
=====================================
@@ -8,6 +8,7 @@ package org.postgresql.test.jdbc42;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -30,17 +31,17 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
+import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
+import java.time.chrono.IsoChronology;
import java.time.chrono.IsoEra;
-import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.TimeZone;
-import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -54,6 +55,8 @@ public class GetObject310Test extends BaseTest4 {
private static final ZoneOffset GMT05 = ZoneOffset.of("-05:00"); // -0500 always
private static final ZoneOffset GMT13 = ZoneOffset.of("+13:00"); // +1300 always
+ private static final IsoChronology ISO = IsoChronology.INSTANCE;
+
public GetObject310Test(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}
@@ -90,17 +93,101 @@ public class GetObject310Test extends BaseTest4 {
*/
@Test
public void testGetLocalDate() throws SQLException {
- Statement stmt = con.createStatement();
- stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '1999-01-08'"));
+ assumeTrue(TestUtil.haveIntegerDateTimes(con));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column"));
- try {
- assertTrue(rs.next());
- LocalDate localDate = LocalDate.of(1999, 1, 8);
- assertEquals(localDate, rs.getObject("date_column", LocalDate.class));
- assertEquals(localDate, rs.getObject(1, LocalDate.class));
- } finally {
- rs.close();
+ List<String> zoneIdsToTest = new ArrayList<String>();
+ zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1
+ zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9
+ zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0
+ zoneIdsToTest.add("Europe/Berlin"); // It is something like GMT+1..GMT+2
+ zoneIdsToTest.add("Europe/Moscow"); // It is something like GMT+3..GMT+4 for 2000s
+ zoneIdsToTest.add("Pacific/Apia"); // It is something like GMT+13..GMT+14
+ zoneIdsToTest.add("Pacific/Niue"); // It is something like GMT-11..GMT-11
+ for (int i = -12; i <= 13; i++) {
+ zoneIdsToTest.add(String.format("GMT%+02d", i));
+ }
+
+ List<String> datesToTest = Arrays.asList("1998-01-08",
+ // Some random dates
+ "1981-12-11", "2022-02-22",
+ "2015-09-03", "2015-06-30",
+ "1997-06-30", "1997-07-01", "2012-06-30", "2012-07-01",
+ "2015-06-30", "2015-07-01", "2005-12-31", "2006-01-01",
+ "2008-12-31", "2009-01-01", "2015-06-30", "2015-07-31",
+ "2015-07-31",
+
+ // On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
+ "2003-03-25", "2000-03-26", "2000-03-27",
+
+ // This is a pre-1970 date, so check if it is rounded properly
+ "1950-07-20",
+
+ // Ensure the calendar is proleptic
+ "1582-01-01", "1582-12-31",
+ "1582-09-30", "1582-10-16",
+
+ // https://github.com/pgjdbc/pgjdbc/issues/2221
+ "0001-01-01",
+ "1000-01-01", "1000-06-01", "0999-12-31",
+
+ // On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
+ "2000-10-28", "2000-10-29", "2000-10-30");
+
+ for (String zoneId : zoneIdsToTest) {
+ ZoneId zone = ZoneId.of(zoneId);
+ for (String date : datesToTest) {
+ localDate(zone, date);
+ }
+ }
+ }
+
+ public void localDate(ZoneId zoneId, String date) throws SQLException {
+ TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+ try (Statement stmt = con.createStatement(); ) {
+ stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '" + date + "'"));
+
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column")); ) {
+ assertTrue(rs.next());
+ LocalDate localDate = LocalDate.parse(date);
+ assertEquals(localDate, rs.getObject("date_column", LocalDate.class));
+ assertEquals(localDate, rs.getObject(1, LocalDate.class));
+ }
+ stmt.executeUpdate("DELETE FROM table1");
+ }
+ }
+
+ /**
+ * Test the behavior getObject for timetz columns.
+ */
+ @Test
+ public void testGetOffsetTime() throws SQLException {
+ List<String> timesToTest = Arrays.asList("00:00:00+00:00", "00:00:00+00:30",
+ "01:02:03.333444+02:00", "23:59:59.999999-12:00",
+ "11:22:59.4711-08:00", "23:59:59.0-12:00",
+ "11:22:59.4711+15:59:12", "23:59:59.0-15:59:12"
+ );
+
+ for (String time : timesToTest) {
+ try (Statement stmt = con.createStatement(); ) {
+ stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column","time with time zone '" + time + "'"));
+
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column")); ) {
+ assertTrue(rs.next());
+ OffsetTime offsetTime = OffsetTime.parse(time);
+ assertEquals(offsetTime, rs.getObject("time_with_time_zone_column", OffsetTime.class));
+ assertEquals(offsetTime, rs.getObject(1, OffsetTime.class));
+
+ //Also test that we get the correct values when retrieving the data as OffsetDateTime objects on EPOCH (required by JDBC)
+ OffsetDateTime offsetDT = offsetTime.atDate(LocalDate.of(1970, 1, 1));
+ assertEquals(offsetDT, rs.getObject("time_with_time_zone_column", OffsetDateTime.class));
+ assertEquals(offsetDT, rs.getObject(1, OffsetDateTime.class));
+
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class);
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class);
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class);
+ }
+ stmt.executeUpdate("DELETE FROM table1");
+ }
}
}
@@ -109,17 +196,21 @@ public class GetObject310Test extends BaseTest4 {
*/
@Test
public void testGetLocalTime() throws SQLException {
- Statement stmt = con.createStatement();
- stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06.123456'"));
+ try (Statement stmt = con.createStatement(); ) {
+ stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06.123456'"));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
- try {
- assertTrue(rs.next());
- LocalTime localTime = LocalTime.of(4, 5, 6, 123456000);
- assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class));
- assertEquals(localTime, rs.getObject(1, LocalTime.class));
- } finally {
- rs.close();
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"))) {
+ assertTrue(rs.next());
+ LocalTime localTime = LocalTime.of(4, 5, 6, 123456000);
+ assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class));
+ assertEquals(localTime, rs.getObject(1, LocalTime.class));
+
+ assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetTime.class);
+ assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetDateTime.class);
+ assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDate.class);
+ assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDateTime.class);
+ }
+ stmt.executeUpdate("DELETE FROM table1");
}
}
@@ -128,44 +219,33 @@ public class GetObject310Test extends BaseTest4 {
*/
@Test
public void testGetLocalTimeNull() throws SQLException {
- Statement stmt = con.createStatement();
- stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL"));
+ try (Statement stmt = con.createStatement(); ) {
+ stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL"));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
- try {
- assertTrue(rs.next());
- assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class));
- assertNull(rs.getObject(1, LocalTime.class));
- } finally {
- rs.close();
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"))) {
+ assertTrue(rs.next());
+ assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class));
+ assertNull(rs.getObject(1, LocalTime.class));
+ }
+ stmt.executeUpdate("DELETE FROM table1");
}
}
/**
- * Test the behavior getObject for time columns with null.
+ * Test the behavior getObject for time columns with invalid type.
*/
@Test
public void testGetLocalTimeInvalidType() throws SQLException {
- Statement stmt = con.createStatement();
- stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column", "TIME '04:05:06.123456-08:00'"));
+ try (Statement stmt = con.createStatement(); ) {
+ stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column", "TIME '04:05:06.123456-08:00'"));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column"));
- try {
- assertTrue(rs.next());
- try {
- assertNull(rs.getObject("time_with_time_zone_column", LocalTime.class));
- } catch (PSQLException e) {
- assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
- || e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
- }
- try {
- assertNull(rs.getObject(1, LocalTime.class));
- } catch (PSQLException e) {
- assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
- || e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column"))) {
+ assertTrue(rs.next());
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class);
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class);
+ assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class);
}
- } finally {
- rs.close();
+ stmt.executeUpdate("DELETE FROM table1");
}
}
@@ -204,6 +284,11 @@ public class GetObject310Test extends BaseTest4 {
// Ensure the calendar is proleptic
"1582-09-30T00:00:00", "1582-10-16T00:00:00",
+ // https://github.com/pgjdbc/pgjdbc/issues/2221
+ "0001-01-01T00:00:00",
+ "1000-01-01T00:00:00",
+ "1000-01-01T23:59:59", "1000-06-01T01:00:00", "0999-12-31T23:59:59",
+
// On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
"2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59",
"2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00",
@@ -219,12 +304,10 @@ public class GetObject310Test extends BaseTest4 {
public void localTimestamps(ZoneId zoneId, String timestamp) throws SQLException {
TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
- Statement stmt = con.createStatement();
- try {
+ try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_without_time_zone_column","TIMESTAMP '" + timestamp + "'"));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column"));
- try {
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column"))) {
assertTrue(rs.next());
LocalDateTime localDateTime = LocalDateTime.parse(timestamp);
assertEquals(localDateTime, rs.getObject("timestamp_without_time_zone_column", LocalDateTime.class));
@@ -233,12 +316,12 @@ public class GetObject310Test extends BaseTest4 {
//Also test that we get the correct values when retrieving the data as LocalDate objects
assertEquals(localDateTime.toLocalDate(), rs.getObject("timestamp_without_time_zone_column", LocalDate.class));
assertEquals(localDateTime.toLocalDate(), rs.getObject(1, LocalDate.class));
- } finally {
- rs.close();
+
+ assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetTime.class);
+ // TODO: this should also not work, but that's an open discussion (see https://github.com/pgjdbc/pgjdbc/pull/2467):
+ // assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetDateTime.class);
}
stmt.executeUpdate("DELETE FROM table1");
- } finally {
- stmt.close();
}
}
@@ -254,60 +337,55 @@ public class GetObject310Test extends BaseTest4 {
}
private void runGetOffsetDateTime(ZoneOffset offset) throws SQLException {
- Statement stmt = con.createStatement();
- try {
+ try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54.123456" + offset.toString() + "'"));
- ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column"));
- try {
+ try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column"))) {
assertTrue(rs.next());
LocalDateTime localDateTime = LocalDateTime.of(2004, 10, 19, 10, 23, 54, 123456000);
OffsetDateTime offsetDateTime = localDateTime.atOffset(offset).withOffsetSameInstant(ZoneOffset.UTC);
assertEquals(offsetDateTime, rs.getObject("timestamp_with_time_zone_column", OffsetDateTime.class));
assertEquals(offsetDateTime, rs.getObject(1, OffsetDateTime.class));
- } finally {
- rs.close();
+
+ assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalTime.class);
+ assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalDateTime.class);
}
stmt.executeUpdate("DELETE FROM table1");
- } finally {
- stmt.close();
}
}
@Test
- public void testBcTimestamp() throws SQLException {
+ public void testBcDate() throws SQLException {
+ try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 BC'::date")) {
+ assertTrue(rs.next());
+ LocalDate expected = ISO.date(IsoEra.BCE, 1582, 9, 30);
+ LocalDate actual = rs.getObject(1, LocalDate.class);
+ assertEquals(expected, actual);
+ assertFalse(rs.next());
+ }
+ }
- Statement stmt = con.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp");
- try {
+ @Test
+ public void testBcTimestamp() throws SQLException {
+ try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp")) {
assertTrue(rs.next());
- LocalDateTime expected = LocalDateTime.of(1582, 9, 30, 12, 34, 56)
- .with(ChronoField.ERA, IsoEra.BCE.getValue());
+ LocalDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(12, 34, 56);
LocalDateTime actual = rs.getObject(1, LocalDateTime.class);
assertEquals(expected, actual);
assertFalse(rs.next());
- } finally {
- rs.close();
- stmt.close();
}
}
@Test
public void testBcTimestamptz() throws SQLException {
-
- Statement stmt = con.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp");
- try {
+ try (Statement stmt = con.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp")) {
assertTrue(rs.next());
- OffsetDateTime expected = OffsetDateTime.of(1582, 9, 30, 12, 34, 56, 0, UTC)
- .with(ChronoField.ERA, IsoEra.BCE.getValue());
+ OffsetDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(OffsetTime.of(12, 34, 56, 0, UTC));
OffsetDateTime actual = rs.getObject(1, OffsetDateTime.class);
assertEquals(expected, actual);
assertFalse(rs.next());
- } finally {
- rs.close();
- stmt.close();
}
}
@@ -318,7 +396,7 @@ public class GetObject310Test extends BaseTest4 {
LocalDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay();
LocalDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay();
long numberOfDays = Duration.between(start, end).toDays() + 1L;
- List<LocalDateTime> range = Stream.iterate(start, new LocalDateTimePlusOneDay())
+ List<LocalDateTime> range = Stream.iterate(start, x -> x.plusDays(1))
.limit(numberOfDays)
.collect(Collectors.toList());
@@ -332,7 +410,7 @@ public class GetObject310Test extends BaseTest4 {
OffsetDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay().atOffset(UTC);
OffsetDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay().atOffset(UTC);
long numberOfDays = Duration.between(start, end).toDays() + 1L;
- List<OffsetDateTime> range = Stream.iterate(start, new OffsetDateTimePlusOneDay())
+ List<OffsetDateTime> range = Stream.iterate(start, x -> x.plusDays(1))
.limit(numberOfDays)
.collect(Collectors.toList());
@@ -342,34 +420,23 @@ public class GetObject310Test extends BaseTest4 {
private <T extends Temporal> void runProlepticTests(Class<T> clazz, String selectRange, List<T> range) throws SQLException {
List<T> temporals = new ArrayList<>(range.size());
- PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');");
- ResultSet rs = stmt.executeQuery();
- try {
+ try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');");
+ ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
T temporal = rs.getObject(1, clazz);
temporals.add(temporal);
}
assertEquals(range, temporals);
- } finally {
- rs.close();
- stmt.close();
}
}
- private static class LocalDateTimePlusOneDay implements UnaryOperator<LocalDateTime> {
+ /** checks if getObject with given column name or index 1 throws an exception with DATA_TYPE_MISMATCH as SQLState */
+ private static void assertDataTypeMismatch(ResultSet rs, String columnName, Class<?> typeToGet) {
+ PSQLException ex = assertThrows(PSQLException.class, () -> rs.getObject(columnName, typeToGet));
+ assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState());
- @Override
- public LocalDateTime apply(LocalDateTime x) {
- return x.plusDays(1);
- }
- }
-
- private static class OffsetDateTimePlusOneDay implements UnaryOperator<OffsetDateTime> {
-
- @Override
- public OffsetDateTime apply(OffsetDateTime x) {
- return x.plusDays(1);
- }
+ ex = assertThrows(PSQLException.class, () -> rs.getObject(1, typeToGet));
+ assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState());
}
}
=====================================
src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java
=====================================
@@ -29,6 +29,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
+import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.IsoChronology;
@@ -145,6 +146,10 @@ public class SetObject310Test extends BaseTest4 {
}
private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType) throws SQLException {
+ return insertThenReadWithoutType(data, columnName, expectedType, true);
+ }
+
+ private <T> T insertThenReadWithoutType(Object data, String columnName, Class<T> expectedType, boolean checkRoundtrip) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
ps.setObject(1, data);
@@ -160,6 +165,10 @@ public class SetObject310Test extends BaseTest4 {
assertNotNull(rs);
assertTrue(rs.next());
+ if (checkRoundtrip) {
+ assertEquals("Roundtrip set/getObject with type should return same result",
+ data, rs.getObject(1, data.getClass()));
+ }
return expectedType.cast(rs.getObject(1));
} finally {
rs.close();
@@ -170,6 +179,10 @@ public class SetObject310Test extends BaseTest4 {
}
private <T> T insertThenReadWithType(Object data, int sqlType, String columnName, Class<T> expectedType) throws SQLException {
+ return insertThenReadWithType(data, sqlType, columnName, expectedType, true);
+ }
+
+ private <T> T insertThenReadWithType(Object data, int sqlType, String columnName, Class<T> expectedType, boolean checkRoundtrip) throws SQLException {
PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?"));
try {
ps.setObject(1, data, sqlType);
@@ -185,6 +198,10 @@ public class SetObject310Test extends BaseTest4 {
assertNotNull(rs);
assertTrue(rs.next());
+ if (checkRoundtrip) {
+ assertEquals("Roundtrip set/getObject with type should return same result",
+ data, rs.getObject(1, data.getClass()));
+ }
return expectedType.cast(rs.getObject(1));
} finally {
rs.close();
@@ -336,7 +353,7 @@ public class SetObject310Test extends BaseTest4 {
// TODO: fix for binary
assumeBinaryModeRegular();
LocalTime time = LocalTime.parse("23:59:59.999999500");
- Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class);
+ Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class, false/*no roundtrip*/);
assertEquals(Time.valueOf("24:00:00"), actual);
}
@@ -346,7 +363,7 @@ public class SetObject310Test extends BaseTest4 {
assumeBinaryModeRegular();
LocalTime time = LocalTime.parse("23:59:59.999999500");
Time actual =
- insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class);
+ insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class, false/*no roundtrip*/);
assertEquals(Time.valueOf("24:00:00"), actual);
}
@@ -428,4 +445,22 @@ public class SetObject310Test extends BaseTest4 {
assertEquals(expected, actual);
}
+ /**
+ * Test the behavior setObject for time columns.
+ */
+ @Test
+ public void testSetOffsetTimeWithType() throws SQLException {
+ OffsetTime data = OffsetTime.parse("16:21:51+12:34");
+ insertThenReadWithType(data, Types.TIME, "time_with_time_zone_column", Time.class);
+ }
+
+ /**
+ * Test the behavior setObject for time columns.
+ */
+ @Test
+ public void testSetOffsetTimeWithoutType() throws SQLException {
+ OffsetTime data = OffsetTime.parse("16:21:51+12:34");
+ insertThenReadWithoutType(data, "time_with_time_zone_column", Time.class);
+ }
+
}
=====================================
src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java
=====================================
@@ -7,62 +7,124 @@ package org.postgresql.test.jdbc42;
import static org.junit.Assert.assertEquals;
-import org.postgresql.core.Provider;
import org.postgresql.jdbc.TimestampUtils;
+import org.junit.Before;
import org.junit.Test;
import java.sql.SQLException;
import java.time.LocalTime;
+import java.time.OffsetTime;
import java.util.TimeZone;
public class TimestampUtilsTest {
+ private TimestampUtils timestampUtils;
+
+ @Before
+ public void setUp() {
+ timestampUtils = new TimestampUtils(true, TimeZone::getDefault);
+ }
+
@Test
public void testToStringOfLocalTime() {
- TimestampUtils timestampUtils = createTimestampUtils();
-
- assertEquals("00:00:00", timestampUtils.toString(LocalTime.parse("00:00:00")));
- assertEquals("00:00:00.1", timestampUtils.toString(LocalTime.parse("00:00:00.1")));
- assertEquals("00:00:00.12", timestampUtils.toString(LocalTime.parse("00:00:00.12")));
- assertEquals("00:00:00.123", timestampUtils.toString(LocalTime.parse("00:00:00.123")));
- assertEquals("00:00:00.1234", timestampUtils.toString(LocalTime.parse("00:00:00.1234")));
- assertEquals("00:00:00.12345", timestampUtils.toString(LocalTime.parse("00:00:00.12345")));
- assertEquals("00:00:00.123456", timestampUtils.toString(LocalTime.parse("00:00:00.123456")));
-
- assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999")));
- assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999499"))); // 499 NanoSeconds
- assertEquals("00:00:01", timestampUtils.toString(LocalTime.parse("00:00:00.999999500"))); // 500 NanoSeconds
-
- assertEquals("23:59:59", timestampUtils.toString(LocalTime.parse("23:59:59")));
- assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999"))); // 0 NanoSeconds
- assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999499"))); // 499 NanoSeconds
- assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999500")));// 500 NanoSeconds
- assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999999")));// 999 NanoSeconds
+ assertToStringOfLocalTime("00:00:00", "00:00:00");
+ assertToStringOfLocalTime("00:00:00.1", "00:00:00.1");
+ assertToStringOfLocalTime("00:00:00.12", "00:00:00.12");
+ assertToStringOfLocalTime("00:00:00.123", "00:00:00.123");
+ assertToStringOfLocalTime("00:00:00.1234", "00:00:00.1234");
+ assertToStringOfLocalTime("00:00:00.12345", "00:00:00.12345");
+ assertToStringOfLocalTime("00:00:00.123456", "00:00:00.123456");
+
+ assertToStringOfLocalTime("00:00:00.999999", "00:00:00.999999");
+ assertToStringOfLocalTime("00:00:00.999999", "00:00:00.999999499"); // 499 NanoSeconds
+ assertToStringOfLocalTime("00:00:01", "00:00:00.999999500"); // 500 NanoSeconds
+
+ assertToStringOfLocalTime("23:59:59", "23:59:59");
+
+ assertToStringOfLocalTime("23:59:59.999999", "23:59:59.999999");
+ assertToStringOfLocalTime("23:59:59.999999", "23:59:59.999999499"); // 499 NanoSeconds
+ assertToStringOfLocalTime("24:00:00", "23:59:59.999999500"); // 500 NanoSeconds
+ assertToStringOfLocalTime("24:00:00", "23:59:59.999999999"); // 999 NanoSeconds
+ }
+
+ private void assertToStringOfLocalTime(String expectedOutput, String inputTime) {
+ assertEquals("timestampUtils.toString(LocalTime.parse(" + inputTime + "))",
+ expectedOutput,
+ timestampUtils.toString(LocalTime.parse(inputTime)));
}
@Test
public void testToLocalTime() throws SQLException {
- TimestampUtils timestampUtils = createTimestampUtils();
-
- assertEquals(LocalTime.parse("00:00:00"), timestampUtils.toLocalTime("00:00:00"));
-
- assertEquals(LocalTime.parse("00:00:00.1"), timestampUtils.toLocalTime("00:00:00.1"));
- assertEquals(LocalTime.parse("00:00:00.12"), timestampUtils.toLocalTime("00:00:00.12"));
- assertEquals(LocalTime.parse("00:00:00.123"), timestampUtils.toLocalTime("00:00:00.123"));
- assertEquals(LocalTime.parse("00:00:00.1234"), timestampUtils.toLocalTime("00:00:00.1234"));
- assertEquals(LocalTime.parse("00:00:00.12345"), timestampUtils.toLocalTime("00:00:00.12345"));
- assertEquals(LocalTime.parse("00:00:00.123456"), timestampUtils.toLocalTime("00:00:00.123456"));
- assertEquals(LocalTime.parse("00:00:00.999999"), timestampUtils.toLocalTime("00:00:00.999999"));
-
- assertEquals(LocalTime.parse("23:59:59"), timestampUtils.toLocalTime("23:59:59"));
- assertEquals(LocalTime.parse("23:59:59.999999"), timestampUtils.toLocalTime("23:59:59.999999")); // 0 NanoSeconds
- assertEquals(LocalTime.parse("23:59:59.9999999"), timestampUtils.toLocalTime("23:59:59.9999999")); // 900 NanoSeconds
- assertEquals(LocalTime.parse("23:59:59.99999999"), timestampUtils.toLocalTime("23:59:59.99999999")); // 990 NanoSeconds
- assertEquals(LocalTime.parse("23:59:59.999999998"), timestampUtils.toLocalTime("23:59:59.999999998")); // 998 NanoSeconds
- assertEquals(LocalTime.parse("23:59:59.999999999"), timestampUtils.toLocalTime("24:00:00"));
+ assertToLocalTime("00:00:00", "00:00:00");
+
+ assertToLocalTime("00:00:00.1", "00:00:00.1");
+ assertToLocalTime("00:00:00.12", "00:00:00.12");
+ assertToLocalTime("00:00:00.123", "00:00:00.123");
+ assertToLocalTime("00:00:00.1234", "00:00:00.1234");
+ assertToLocalTime("00:00:00.12345", "00:00:00.12345");
+ assertToLocalTime("00:00:00.123456", "00:00:00.123456");
+ assertToLocalTime("00:00:00.999999", "00:00:00.999999");
+
+ assertToLocalTime("23:59:59", "23:59:59");
+ assertToLocalTime("23:59:59.999999", "23:59:59.999999"); // 0 NanoSeconds
+ assertToLocalTime("23:59:59.9999999", "23:59:59.9999999"); // 900 NanoSeconds
+ assertToLocalTime("23:59:59.99999999", "23:59:59.99999999"); // 990 NanoSeconds
+ assertToLocalTime("23:59:59.999999998", "23:59:59.999999998"); // 998 NanoSeconds
+ assertToLocalTime(LocalTime.MAX.toString(), "24:00:00");
+ }
+
+ private void assertToLocalTime(String expectedOutput, String inputTime) throws SQLException {
+ assertEquals("timestampUtils.toLocalTime(" + inputTime + ")",
+ LocalTime.parse(expectedOutput),
+ timestampUtils.toLocalTime(inputTime));
+ }
+
+ @Test
+ public void testToStringOfOffsetTime() {
+ assertToStringOfOffsetTime("00:00:00+00", "00:00:00+00:00");
+ assertToStringOfOffsetTime("00:00:00.1+01", "00:00:00.1+01:00");
+ assertToStringOfOffsetTime("00:00:00.12+12", "00:00:00.12+12:00");
+ assertToStringOfOffsetTime("00:00:00.123-01", "00:00:00.123-01:00");
+ assertToStringOfOffsetTime("00:00:00.1234-02", "00:00:00.1234-02:00");
+ assertToStringOfOffsetTime("00:00:00.12345-12", "00:00:00.12345-12:00");
+ assertToStringOfOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30");
+ assertToStringOfOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34");
+
+ assertToStringOfOffsetTime("23:59:59+01", "23:59:59+01:00");
+
+ assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999+01:00");
+ assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999499+01:00"); // 499 NanoSeconds
+ assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999500+01:00"); // 500 NanoSeconds
+ assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999999+01:00"); // 999 NanoSeconds
+ }
+
+ private void assertToStringOfOffsetTime(String expectedOutput, String inputTime) {
+ assertEquals("timestampUtils.toString(OffsetTime.parse(" + inputTime + "))",
+ expectedOutput,
+ timestampUtils.toString(OffsetTime.parse(inputTime)));
+ }
+
+ @Test
+ public void testToOffsetTime() throws SQLException {
+ assertToOffsetTime("00:00:00+00:00", "00:00:00+00");
+ assertToOffsetTime("00:00:00.1+01:00", "00:00:00.1+01");
+ assertToOffsetTime("00:00:00.12+12:00", "00:00:00.12+12");
+ assertToOffsetTime("00:00:00.123-01:00", "00:00:00.123-01");
+ assertToOffsetTime("00:00:00.1234-02:00", "00:00:00.1234-02");
+ assertToOffsetTime("00:00:00.12345-12:00", "00:00:00.12345-12");
+ assertToOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30");
+ assertToOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34");
+
+ assertToOffsetTime("23:59:59.999999+01:00", "23:59:59.999999+01"); // 0 NanoSeconds
+ assertToOffsetTime("23:59:59.9999999+01:00", "23:59:59.9999999+01"); // 900 NanoSeconds
+ assertToOffsetTime("23:59:59.99999999+01:00", "23:59:59.99999999+01"); // 990 NanoSeconds
+ assertToOffsetTime("23:59:59.999999998+01:00", "23:59:59.999999998+01"); // 998 NanoSeconds
+ assertToOffsetTime(OffsetTime.MAX.toString(), "24:00:00+01");
}
- private TimestampUtils createTimestampUtils() {
- return new TimestampUtils(true, (Provider<TimeZone>) TimeZone::getDefault);
+ private void assertToOffsetTime(String expectedOutput, String inputTime) throws SQLException {
+ assertEquals("timestampUtils.toOffsetTime(" + inputTime + ")",
+ OffsetTime.parse(expectedOutput),
+ timestampUtils.toOffsetTime(inputTime));
}
}
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/c28df98f1b484312a1b34bb0b7ae0c4bf6b11161...27013f2d51f4b555b9d7266e09b2ec78eda0a8b3
--
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/c28df98f1b484312a1b34bb0b7ae0c4bf6b11161...27013f2d51f4b555b9d7266e09b2ec78eda0a8b3
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20220502/9efc35b1/attachment.htm>
More information about the pkg-java-commits
mailing list