Skip to content

Commit

Permalink
fix: strict handling of getBoolean and setObject with postgres accept…
Browse files Browse the repository at this point in the history
…ed values (#732)

Strict support of accepted values in getBoolean and setObject(BOOLEAN)

Discussion: https://www.postgresql.org/message-id/flat/990BE54A-5803-4B5A-A5C8-BD6DBE75B72C%40me.com
  • Loading branch information
jorsol authored and vlsi committed Jan 27, 2017
1 parent 55e2cd1 commit 4942f7d
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 206 deletions.
105 changes: 105 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2017, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/

package org.postgresql.jdbc;

import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Helper class to handle boolean type of PostgreSQL.
* <p>
* Based on values accepted by the PostgreSQL server:
* https://www.postgresql.org/docs/current/static/datatype-boolean.html
*/
class BooleanTypeUtil {

private static final Logger LOGGER = Logger.getLogger(BooleanTypeUtil.class.getName());

private BooleanTypeUtil() {
}

/**
* Cast an Object value to the corresponding boolean value.
*
* @param in Object to cast into boolean
* @return boolean value corresponding to the cast of the object
* @throws PSQLException PSQLState.CANNOT_COERCE
*/
static boolean castToBoolean(final Object in) throws PSQLException {
LOGGER.log(Level.FINE, "Object to cast: {0}", in);
if (in instanceof Boolean) {
return (Boolean) in;
}
if (in instanceof String) {
return from((String) in);
}
if (in instanceof Character) {
return from((Character) in);
}
if (in instanceof Number) {
return from((Number) in, in.getClass().getName());
}
throw cannotCoerceException(in.getClass().getName());
}

private static boolean from(final String strval) throws PSQLException {
// Leading or trailing whitespace is ignored, and case does not matter.
final String val = strval.trim();
if ("1".equals(val) || "true".equalsIgnoreCase(val)
|| "t".equalsIgnoreCase(val) || "yes".equalsIgnoreCase(val)
|| "y".equalsIgnoreCase(val) || "on".equalsIgnoreCase(val)) {
return true;
}
if ("0".equals(val) || "false".equalsIgnoreCase(val)
|| "f".equalsIgnoreCase(val) || "no".equalsIgnoreCase(val)
|| "n".equalsIgnoreCase(val) || "off".equalsIgnoreCase(val)) {
return false;
}
throw cannotCoerceException("String", val);
}

private static boolean from(final Character charval) throws PSQLException {
if ('1' == charval || 't' == charval || 'T' == charval
|| 'y' == charval || 'Y' == charval) {
return true;
}
if ('0' == charval || 'f' == charval || 'F' == charval
|| 'n' == charval || 'N' == charval) {
return false;
}
throw cannotCoerceException("Character", charval.toString());
}

private static boolean from(final Number numval, final String className) throws PSQLException {
// Handles BigDecimal, Byte, Short, Integer, Long Float, Double
// based on the widening primitive conversions.
double value = numval.doubleValue();
if (value == 1.0d) {
return true;
}
if (value == 0.0d) {
return false;
}
throw cannotCoerceException(className, String.valueOf(numval));
}

private static PSQLException cannotCoerceException(final String fromType) {
LOGGER.log(Level.FINE, "Cannot cast type {0} to boolean", fromType);
return new PSQLException(GT.tr("Cannot cast type {0} to boolean", fromType),
PSQLState.CANNOT_COERCE);
}

private static PSQLException cannotCoerceException(final String fromType, final String value) {
LOGGER.log(Level.FINE, "Cannot cast type {0} to boolean: \"{1}\"", new Object[]{fromType, value});
return new PSQLException(GT.tr("Cannot cast type {0} to boolean: \"{1}\"", fromType, value),
PSQLState.CANNOT_COERCE);
}

}
4 changes: 2 additions & 2 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgArray.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -577,9 +577,9 @@ private Object buildArray(PgArrayList input, int index, int count) throws SQLExc


if (dims > 1 || useObjects) { if (dims > 1 || useObjects) {
oa[length++] = o == null ? null oa[length++] = o == null ? null
: (dims > 1 ? buildArray((PgArrayList) o, 0, -1) : PgResultSet.toBoolean((String) o)); : (dims > 1 ? buildArray((PgArrayList) o, 0, -1) : BooleanTypeUtil.castToBoolean((String) o));
} else { } else {
pa[length++] = o == null ? false : PgResultSet.toBoolean((String) o); pa[length++] = o == null ? false : BooleanTypeUtil.castToBoolean((String) o);
} }
} }
} else if (type == Types.SMALLINT || type == Types.INTEGER) { } else if (type == Types.SMALLINT || type == Types.INTEGER) {
Expand Down
79 changes: 3 additions & 76 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -206,9 +206,6 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException {
checkClosed(); checkClosed();


int oid; int oid;
if (sqlType == Types.BOOLEAN) {
sqlType = Types.BIT;
}
switch (sqlType) { switch (sqlType) {
case Types.SQLXML: case Types.SQLXML:
oid = Oid.XML; oid = Oid.XML;
Expand Down Expand Up @@ -252,6 +249,7 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException {
case Types.TIMESTAMP: case Types.TIMESTAMP:
oid = Oid.UNSPECIFIED; oid = Oid.UNSPECIFIED;
break; break;
case Types.BOOLEAN:
case Types.BIT: case Types.BIT:
oid = Oid.BOOL; oid = Oid.BOOL;
break; break;
Expand Down Expand Up @@ -527,10 +525,6 @@ public void setObject(int parameterIndex, Object in, int targetSqlType, int scal
return; return;
} }


if (targetSqlType == Types.BOOLEAN) {
targetSqlType = Types.BIT;
}

if (targetSqlType == Types.OTHER && in instanceof UUID if (targetSqlType == Types.OTHER && in instanceof UUID
&& connection.haveMinimumServerVersion(ServerVersion.v8_3)) { && connection.haveMinimumServerVersion(ServerVersion.v8_3)) {
setUuid(parameterIndex, (UUID) in); setUuid(parameterIndex, (UUID) in);
Expand Down Expand Up @@ -649,8 +643,9 @@ public void setObject(int parameterIndex, Object in, int targetSqlType, int scal
} }
break; break;
//#endif //#endif
case Types.BOOLEAN:
case Types.BIT: case Types.BIT:
setBoolean(parameterIndex, castToBoolean(in)); setBoolean(parameterIndex, BooleanTypeUtil.castToBoolean(in));
break; break;
case Types.BINARY: case Types.BINARY:
case Types.VARBINARY: case Types.VARBINARY:
Expand Down Expand Up @@ -877,74 +872,6 @@ private static BigDecimal castToBigDecimal(final Object in, final int scale) thr
throw cannotCastException(in.getClass().getName(), "BigDecimal"); throw cannotCastException(in.getClass().getName(), "BigDecimal");
} }


private static boolean castToBoolean(final Object in) throws SQLException {
if (in instanceof Boolean) {
return (Boolean) in;
}
if (in instanceof String) {
// Leading or trailing whitespace is ignored, and case does not matter.
final String strval = ((String) in).trim();
Boolean value = booleanTypeFromString(strval);
if (value == null) {
throw cannotCastException("String", "boolean");
}
return value;
}
if (in instanceof Character) {
Boolean value = null;
final Character charval = (Character) in;
if ('1' == charval || 't' == charval || 'T' == charval
|| 'y' == charval || 'Y' == charval) {
value = Boolean.TRUE;
} else if ('0' == charval || 'f' == charval || 'F' == charval
|| 'n' == charval || 'N' == charval) {
value = Boolean.FALSE;
} else if (value == null) {
throw cannotCastException("Character", "boolean");
}
return value;
}
if (in instanceof BigDecimal) {
return ((BigDecimal) in).signum() != 0;
}
if (in instanceof Number) {
return ((Number) in).longValue() != 0L;
}
if (in instanceof java.util.Date) {
return ((java.util.Date) in).getTime() != 0L;
}
if (in instanceof Clob) {
final String strval = asString((Clob) in).trim();
Boolean value = booleanTypeFromString(strval);
if (value == null) {
throw cannotCastException("Clob", "boolean");
}
return value;
}
throw cannotCastException(in.getClass().getName(), "boolean");
}

/**
* Based on values accepted by the PostgreSQL server:
* https://www.postgresql.org/docs/current/static/datatype-boolean.html
*
* @param val String with the value to "parse"
* @return Boolean if any valid value match, null otherwise.
*/
private static Boolean booleanTypeFromString(final String val) {
if ("1".equals(val) || "true".equalsIgnoreCase(val)
|| "t".equalsIgnoreCase(val) || "yes".equalsIgnoreCase(val)
|| "y".equalsIgnoreCase(val) || "on".equalsIgnoreCase(val)) {
return Boolean.TRUE;
}
if ("0".equals(val) || "false".equalsIgnoreCase(val)
|| "f".equalsIgnoreCase(val) || "no".equalsIgnoreCase(val)
|| "n".equalsIgnoreCase(val) || "off".equalsIgnoreCase(val)) {
return Boolean.FALSE;
}
return null;
}

private static String castToString(final Object in) throws SQLException { private static String castToString(final Object in) throws SQLException {
try { try {
if (in instanceof String) { if (in instanceof String) {
Expand Down
54 changes: 27 additions & 27 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -169,12 +169,10 @@ public java.net.URL getURL(String columnName) throws SQLException {
protected Object internalGetObject(int columnIndex, Field field) throws SQLException { protected Object internalGetObject(int columnIndex, Field field) throws SQLException {
switch (getSQLType(columnIndex)) { switch (getSQLType(columnIndex)) {
case Types.BOOLEAN: case Types.BOOLEAN:
case Types.BIT:
return getBoolean(columnIndex); return getBoolean(columnIndex);
case Types.SQLXML: case Types.SQLXML:
return getSQLXML(columnIndex); return getSQLXML(columnIndex);
case Types.BIT:
// Also Types.BOOLEAN in JDBC3
return getBoolean(columnIndex) ? Boolean.TRUE : Boolean.FALSE;
case Types.TINYINT: case Types.TINYINT:
case Types.SMALLINT: case Types.SMALLINT:
case Types.INTEGER: case Types.INTEGER:
Expand Down Expand Up @@ -1918,6 +1916,29 @@ public String getString(int columnIndex) throws SQLException {
} }
} }


/**
* Retrieves the value of the designated column in the current row of this <code>ResultSet</code>
* object as a <code>boolean</code> in the Java programming language.
* <p>
* If the designated column has a Character datatype and is one of the following values: "1",
* "true", "t", "yes", "y" or "on", a value of <code>true</code> is returned. If the designated
* column has a Character datatype and is one of the following values: "0", "false", "f", "no",
* "n" or "off", a value of <code>false</code> is returned. Leading or trailing whitespace is
* ignored, and case does not matter.
* <p>
* If the designated column has a Numeric datatype and is a 1, a value of <code>true</code> is
* returned. If the designated column has a Numeric datatype and is a 0, a value of
* <code>false</code> is returned.
*
* @param columnIndex the first column is 1, the second is 2, ...
* @return the column value; if the value is SQL <code>NULL</code>, the value returned is
* <code>false</code>
* @exception SQLException if the columnIndex is not valid; if a database access error occurs or
* this method is called on a closed result set
* @see <a href="https://www.postgresql.org/docs/current/static/datatype-boolean.html">PostgreSQL
* Boolean Type</a>
*/
@Override
public boolean getBoolean(int columnIndex) throws SQLException { public boolean getBoolean(int columnIndex) throws SQLException {
checkResultSet(columnIndex); checkResultSet(columnIndex);
if (wasNullFlag) { if (wasNullFlag) {
Expand All @@ -1926,10 +1947,10 @@ public boolean getBoolean(int columnIndex) throws SQLException {


if (isBinary(columnIndex)) { if (isBinary(columnIndex)) {
int col = columnIndex - 1; int col = columnIndex - 1;
return readDoubleValue(this_row[col], fields[col].getOID(), "boolean") == 1; return BooleanTypeUtil.castToBoolean(readDoubleValue(this_row[col], fields[col].getOID(), "boolean"));
} }


return toBoolean(getString(columnIndex)); return BooleanTypeUtil.castToBoolean(getString(columnIndex));
} }


private static final BigInteger BYTEMAX = new BigInteger(Byte.toString(Byte.MAX_VALUE)); private static final BigInteger BYTEMAX = new BigInteger(Byte.toString(Byte.MAX_VALUE));
Expand Down Expand Up @@ -2440,6 +2461,7 @@ public String getString(String columnName) throws SQLException {
return getString(findColumn(columnName)); return getString(findColumn(columnName));
} }


@Override
public boolean getBoolean(String columnName) throws SQLException { public boolean getBoolean(String columnName) throws SQLException {
return getBoolean(findColumn(columnName)); return getBoolean(findColumn(columnName));
} }
Expand Down Expand Up @@ -2759,28 +2781,6 @@ protected boolean isBinary(int column) {


// ----------------- Formatting Methods ------------------- // ----------------- Formatting Methods -------------------


public static boolean toBoolean(String s) {
if (s != null) {
s = s.trim();

if (s.equalsIgnoreCase("t") || s.equalsIgnoreCase("true") || s.equals("1")) {
return true;
}

if (s.equalsIgnoreCase("f") || s.equalsIgnoreCase("false") || s.equals("0")) {
return false;
}

try {
if (Double.parseDouble(s) == 1) {
return true;
}
} catch (NumberFormatException e) {
}
}
return false; // SQL NULL
}

private static final BigInteger INTMAX = new BigInteger(Integer.toString(Integer.MAX_VALUE)); private static final BigInteger INTMAX = new BigInteger(Integer.toString(Integer.MAX_VALUE));
private static final BigInteger INTMIN = new BigInteger(Integer.toString(Integer.MIN_VALUE)); private static final BigInteger INTMIN = new BigInteger(Integer.toString(Integer.MIN_VALUE));


Expand Down
1 change: 1 addition & 0 deletions pgjdbc/src/main/java/org/postgresql/util/PSQLState.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public int hashCode() {
public final static PSQLState DATA_TYPE_MISMATCH = new PSQLState("42821"); public final static PSQLState DATA_TYPE_MISMATCH = new PSQLState("42821");
public final static PSQLState UNDEFINED_FUNCTION = new PSQLState("42883"); public final static PSQLState UNDEFINED_FUNCTION = new PSQLState("42883");
public final static PSQLState INVALID_NAME = new PSQLState("42602"); public final static PSQLState INVALID_NAME = new PSQLState("42602");
public final static PSQLState CANNOT_COERCE = new PSQLState("42846");


public final static PSQLState OUT_OF_MEMORY = new PSQLState("53200"); public final static PSQLState OUT_OF_MEMORY = new PSQLState("53200");
public final static PSQLState OBJECT_NOT_IN_STATE = new PSQLState("55000"); public final static PSQLState OBJECT_NOT_IN_STATE = new PSQLState("55000");
Expand Down
Loading

0 comments on commit 4942f7d

Please sign in to comment.