Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

correct mapping for postgres timestamptz type to sql type TIMESTAMP_W… #2715

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Note that the most detailed log levels, "`FINEST`", may include sensitive inform
In addition to the standard connection parameters the driver supports a number of additional properties which can be used to specify additional driver behaviour specific to PostgreSQL™. These properties may be specified in either the connection URL or an additional Properties object parameter to DriverManager.getConnection.

| Property | Type | Default | Description |
|-------------------------------| -- |:-----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|-------------------------------| |:-----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| user | String | null | The database user on whose behalf the connection is being made. |
| password | String | null | The database user's password. |
| options | String | null | Specify 'options' connection initialization parameter. |
Expand Down Expand Up @@ -123,7 +123,7 @@ In addition to the standard connection parameters the driver supports a number o
| sslResponseTimeout | Integer | 5000 | Socket timeout in milliseconds waiting for a response from a request for SSL upgrade from the server. |
| tcpKeepAlive | Boolean | false | Enable or disable TCP keep-alive. |
| tcpNoDelay | Boolean | true | Enable or disable TCP no delay. |
| ApplicationName | String | PostgreSQL JDBC Driver | The application name (require server version >= 9.0). If assumeMinServerVersion is set to >= 9.0 this will be sent in the startup packets, otherwise after the connection is made |
| ApplicationName | String | PostgreSQL JDBC Driver | The application name (require server version >= 9.0). If assumeMinServerVersion is set to >= 9.0 this will be sent in the startup packets, otherwise after the connection is made |
| readOnly | Boolean | false | Puts this connection in read-only mode |
| readOnlyMode | String | transaction | Specifies the behavior when a connection is set to be read only, possible values: ignore, transaction, always |
| disableColumnSanitiser | Boolean | false | Enable optimization that disables column name sanitiser |
Expand Down Expand Up @@ -152,6 +152,7 @@ In addition to the standard connection parameters the driver supports a number o
| authenticationPluginClassName | String | null | Fully qualified class name of the class implementing the AuthenticationPlugin interface. If this is null, the password value in the connection properties will be used. |
| unknownLength | Integer | Integer.MAX_LENGTH | Specifies the length to return for types of unknown length |
| stringtype | String | null | Specify the type to use when binding `PreparedStatement` parameters set via `setString()` |
| useWithTimezone | Boolean | false | Map time with timezone and timestamp with timezone to `TIME_WITH_TIMEZONE` and `TIMESTAMP_WITH_TIMEZONE` |

#### System Properties
| Property | Type | Default | Description |
Expand Down
3 changes: 3 additions & 0 deletions docs/content/documentation/use.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,9 @@ If we quote them, then we end up sending ""colname"" to the backend instead of "
Fully qualified class name of the class implementing the AuthenticationPlugin interface. If this is null, the password
value in the connection properties will be used.

* **`useWithTimezone (`*boolean*`)`** *Default `false`*\
Map time with timezone and timestamp with timezone to `TIME_WITH_TIMEZONE` and `TIMESTAMP_WITH_TIMEZONE`

### Unix sockets

By adding junixsocket you can obtain a socket factory that works with the driver.
Expand Down
11 changes: 11 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/PGProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,17 @@ public enum PGProperty {
"false",
"Use SPNEGO in SSPI authentication requests"),

/**
* Enable mapping of `WITH TIMEZONE` to {@see java.sql.Types.TIME_WITH_TIMEZONE}
* and {@see java.sql.Types.TIMESTAMP_WITH_TIMEZONE} .
* The default is {@code false}
*/
USE_WITH_TIMEZONE(
"useWithTimezone",
"false",
"Map time with timezone and timestamp with timezone to (@code TIME_WITH_TIMEZONE) and (@code TIMESTAMP_WITH_TIMEZONE)"
+ "The default is (@code false)"),

/**
* Factory class to instantiate factories for XML processing.
* The default factory disables external entity processing.
Expand Down
16 changes: 16 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,22 @@ public int getSocketTimeout() {
return PGProperty.SOCKET_TIMEOUT.getIntNoCheck(properties);
}

/**
* @param enabled if PostgreSQL types with TIMEZONE should map into SQL types with TIMEZONE
* @see PGProperty#USE_WITH_TIMEZONE
*/
public void setUseWithTimezone(boolean enabled) {
PGProperty.USE_WITH_TIMEZONE.set(properties, enabled);
}

/**
* @return true if PostgreSQL types with TIMEZONE map to java time types with TIMEZON
* @see PGProperty#USE_WITH_TIMEZONE
*/
public boolean getUseWithTimezone() {
return PGProperty.USE_WITH_TIMEZONE.getBoolean(properties);
}

/**
* @param seconds timeout that is used for sending cancel command
* @see PGProperty#CANCEL_SIGNAL_TIMEOUT
Expand Down
7 changes: 4 additions & 3 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,11 @@ public PgConnection(HostSpec[] hostSpecs,
rollbackQuery = createQuery("ROLLBACK", false, true).query;

int unknownLength = PGProperty.UNKNOWN_LENGTH.getInt(info);
boolean sqlTypesWithTimezone = PGProperty.USE_WITH_TIMEZONE.getBoolean(info);

// Initialize object handling
@SuppressWarnings("argument")
TypeInfo typeCache = createTypeInfo(this, unknownLength);
TypeInfo typeCache = createTypeInfo(this, unknownLength,sqlTypesWithTimezone);
this.typeCache = typeCache;
initObjectTypes(info);

Expand Down Expand Up @@ -777,8 +778,8 @@ public Object getObject(String type, @Nullable String value, byte @Nullable [] b
}
}

protected TypeInfo createTypeInfo(BaseConnection conn, int unknownLength) {
return new TypeInfoCache(conn, unknownLength);
protected TypeInfo createTypeInfo(BaseConnection conn, int unknownLength,boolean sqlTypesWithTimezone) {
return new TypeInfoCache(conn, unknownLength,sqlTypesWithTimezone);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ public URL getURL(String columnName) throws SQLException {
return getString(columnIndex);
case Types.DATE:
return getDate(columnIndex);
case Types.TIME_WITH_TIMEZONE:
case Types.TIME:
return getTime(columnIndex);
case Types.TIMESTAMP_WITH_TIMEZONE:
case Types.TIMESTAMP:
return getTimestamp(columnIndex, null);
case Types.BINARY:
Expand Down
49 changes: 36 additions & 13 deletions pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,26 @@ public class TypeInfoCache implements TypeInfo {
{"bit", Oid.BIT, Types.BIT, "java.lang.Boolean", Oid.BIT_ARRAY},
{"date", Oid.DATE, Types.DATE, "java.sql.Date", Oid.DATE_ARRAY},
{"time", Oid.TIME, Types.TIME, "java.sql.Time", Oid.TIME_ARRAY},
{"timetz", Oid.TIMETZ, Types.TIME, "java.sql.Time", Oid.TIMETZ_ARRAY},
{"timestamp", Oid.TIMESTAMP, Types.TIMESTAMP, "java.sql.Timestamp", Oid.TIMESTAMP_ARRAY},
{"timestamptz", Oid.TIMESTAMPTZ, Types.TIMESTAMP, "java.sql.Timestamp",
Oid.TIMESTAMPTZ_ARRAY},
{"refcursor", Oid.REF_CURSOR, Types.REF_CURSOR, "java.sql.ResultSet", Oid.REF_CURSOR_ARRAY},
{"json", Oid.JSON, Types.OTHER, "org.postgresql.util.PGobject", Oid.JSON_ARRAY},
{"point", Oid.POINT, Types.OTHER, "org.postgresql.geometric.PGpoint", Oid.POINT_ARRAY},
{"box", Oid.BOX, Types.OTHER, "org.postgresql.geometric.PGBox", Oid.BOX_ARRAY}
};

// whether withTimeZone or noTimeZone types are used is controlled by the PGProperty SQL_TYPES_WITH_TIMEZONE
private static final Object[][] sqlTypesWithTimezone = {
{"timetz", Oid.TIMETZ, Types.TIME_WITH_TIMEZONE, "java.sql.Time", Oid.TIMETZ_ARRAY},
{"timestamptz", Oid.TIMESTAMPTZ, Types.TIMESTAMP_WITH_TIMEZONE, "java.sql.Timestamp",
Oid.TIMESTAMPTZ_ARRAY}
};

private static final Object[][] sqlTypesNoTimezone = {
{"timetz", Oid.TIMETZ, Types.TIME, "java.sql.Time", Oid.TIMETZ_ARRAY},
{"timestamptz", Oid.TIMESTAMPTZ, Types.TIMESTAMP, "java.sql.Timestamp",
Oid.TIMESTAMPTZ_ARRAY}
};

/**
* PG maps several alias to real type names. When we do queries against pg_catalog, we must use
* the real type, not an alias, so use this mapping.
Expand Down Expand Up @@ -152,21 +162,23 @@ public class TypeInfoCache implements TypeInfo {
}

@SuppressWarnings("method.invocation")
public TypeInfoCache(BaseConnection conn, int unknownLength) {
public TypeInfoCache(BaseConnection conn, int unknownLength,boolean sqlTypeWithTimezone) {
this.conn = conn;
this.unknownLength = unknownLength;
oidToPgName = new HashMap<>((int) Math.round(types.length * 1.5));
pgNameToOid = new HashMap<>((int) Math.round(types.length * 1.5));
javaArrayTypeToOid = new HashMap<>((int) Math.round(types.length * 1.5));
pgNameToJavaClass = new HashMap<>((int) Math.round(types.length * 1.5));
pgNameToPgObject = new HashMap<>((int) Math.round(types.length * 1.5));
pgArrayToPgType = new HashMap<>((int) Math.round(types.length * 1.5));
arrayOidToDelimiter = new HashMap<>((int) Math.round(types.length * 2.5));
final Object[][] timezoneTypes = sqlTypeWithTimezone ? sqlTypesWithTimezone : sqlTypesNoTimezone;
final int typesLength = types.length + timezoneTypes.length;
oidToPgName = new HashMap<Integer, String>((int) Math.round(typesLength * 1.5));
pgNameToOid = new HashMap<String, Integer>((int) Math.round(typesLength * 1.5));
javaArrayTypeToOid = new HashMap<String, Integer>((int) Math.round(typesLength * 1.5));
pgNameToJavaClass = new HashMap<String, String>((int) Math.round(typesLength * 1.5));
pgNameToPgObject = new HashMap<String, Class<? extends PGobject>>((int) Math.round(typesLength * 1.5));
pgArrayToPgType = new HashMap<Integer, Integer>((int) Math.round(typesLength * 1.5));
arrayOidToDelimiter = new HashMap<Integer, Character>((int) Math.round(typesLength * 2.5));

// needs to be synchronized because the iterator is returned
// from getPGTypeNamesWithSQLTypes()
pgNameToSQLType = Collections.synchronizedMap(new HashMap<String, Integer>((int) Math.round(types.length * 1.5)));
oidToSQLType = Collections.synchronizedMap(new HashMap<Integer, Integer>((int) Math.round(types.length * 1.5)));
pgNameToSQLType = Collections.synchronizedMap(new HashMap<String, Integer>((int) Math.round(typesLength * 1.5)));
oidToSQLType = Collections.synchronizedMap(new HashMap<Integer, Integer>((int) Math.round(typesLength * 1.5)));

for (Object[] type : types) {
String pgTypeName = (String) type[0];
Expand All @@ -178,6 +190,17 @@ public TypeInfoCache(BaseConnection conn, int unknownLength) {
addCoreType(pgTypeName, oid, sqlType, javaClass, arrayOid);
}

// add timezone types
for (Object[] type : timezoneTypes) {
String pgTypeName = (String) type[0];
Integer oid = (Integer) type[1];
Integer sqlType = (Integer) type[2];
String javaClass = (String) type[3];
Integer arrayOid = (Integer) type[4];

addCoreType(pgTypeName, oid, sqlType, javaClass, arrayOid);
}

pgNameToJavaClass.put("hstore", Map.class.getName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public void object3dArrayCopy() throws Exception {

private static final class EncodingConnection implements BaseConnection {
private final Encoding encoding;
private final TypeInfo typeInfo = new TypeInfoCache(this, -1);
private final TypeInfo typeInfo = new TypeInfoCache(this, -1,false);

EncodingConnection(Encoding encoding) {
this.encoding = encoding;
Expand Down