diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java index 5f2e216..2e56833 100644 --- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java +++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java @@ -128,6 +128,12 @@ public enum PGProperty { DEFAULT_ROW_FETCH_SIZE("defaultRowFetchSize", "0", "Positive number of rows that should be fetched from the database when more rows are needed for ResultSet by each fetch iteration"), + /** + * Use binary format for sending and receiving data if possible. + */ + BIT_TO_STRING("bitToString", "false", + "Auto Convert bit or bit(n) to String type in ResultSet.getObject"), + /** * Use binary format for sending and receiving data if possible. */ diff --git a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java index df672cd..29b43f1 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java +++ b/pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java @@ -29,6 +29,12 @@ public interface BaseConnection extends PGConnection, Connection { */ ClientLogic getClientLogic(); + /** + * True if bit convert to string type else to boolean type, default is false. + * @return + */ + boolean getBitToString(); + /** * Cancel the current query executing on this connection. * diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java b/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java index 87cfbbf..7e56025 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java @@ -11,6 +11,10 @@ import org.postgresql.util.PSQLState; import org.postgresql.log.Logger; import org.postgresql.log.Log; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + /** *

Helper class to handle boolean type of PostgreSQL.

@@ -21,6 +25,8 @@ import org.postgresql.log.Log; class BooleanTypeUtil { private static Log LOGGER = Logger.getLogger(BooleanTypeUtil.class.getName()); + private static final Set TRUE_SETS = Arrays.stream(new String[]{"1", "true", "t", "yes", "y", "on"}).collect(Collectors.toSet()); + private static final Set FALSE_SETS = Arrays.stream(new String[]{"0", "false", "f", "no", "n", "off"}).collect(Collectors.toSet()); private BooleanTypeUtil() { } @@ -54,19 +60,24 @@ class BooleanTypeUtil { private static boolean fromString(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)) { + if (isTrueStr(val)) { return true; } - if ("0".equals(val) || "false".equalsIgnoreCase(val) - || "f".equalsIgnoreCase(val) || "no".equalsIgnoreCase(val) - || "n".equalsIgnoreCase(val) || "off".equalsIgnoreCase(val)) { + if (isFalseStr(val)) { return false; } throw cannotCoerceException(strval); } + private static boolean isTrueStr(final String strval) { + return TRUE_SETS.contains(strval.toLowerCase()); + } + + private static boolean isFalseStr(final String strval) { + return FALSE_SETS.contains(strval.toLowerCase()); + } + + private static boolean fromCharacter(final Character charval) throws PSQLException { if ('1' == charval || 't' == charval || 'T' == charval || 'y' == charval || 'Y' == charval) { diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java index e2f77a6..7056343 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java @@ -77,10 +77,7 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Executor; import java.io.File; -import java.io.InputStream; import java.net.URISyntaxException; -import java.util.jar.Attributes; -import java.util.jar.Manifest; import org.postgresql.core.types.PGClob; import org.postgresql.core.types.PGBlob; @@ -166,6 +163,9 @@ public class PgConnection implements BaseConnection { // Current warnings; there might be more on queryExecutor too. private SQLWarning firstWarning = null; + // True if bit to string else bit to boolean. + private boolean bitToString = false; + // Timer for scheduling TimerTasks for this connection. // Only instantiated if a task is actually scheduled. private volatile Timer cancelTimer = null; @@ -244,6 +244,7 @@ public class PgConnection implements BaseConnection { this.creatingURL = url; + bitToString = PGProperty.BIT_TO_STRING.getBoolean(info); setDefaultFetchSize(PGProperty.DEFAULT_ROW_FETCH_SIZE.getInt(info)); setPrepareThreshold(PGProperty.PREPARE_THRESHOLD.getInt(info)); @@ -1276,6 +1277,11 @@ public class PgConnection implements BaseConnection { } } + @Override + public boolean getBitToString() { + return bitToString; + } + public int getPrepareThreshold() { return prepareThreshold; } diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java index 62b4ca7..ec22500 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java @@ -234,6 +234,11 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement { } } + public void setBit(int parameterIndex, boolean x) throws SQLException { + checkClosed(); + bindString(parameterIndex, x ? "1" : "0", Oid.BIT); + } + public void setByte(int parameterIndex, byte x) throws SQLException { setShort(parameterIndex, x); } @@ -632,9 +637,11 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement { } break; case Types.BOOLEAN: - case Types.BIT: setBoolean(parameterIndex, BooleanTypeUtil.castToBoolean(in)); break; + case Types.BIT: + setBit(parameterIndex, BooleanTypeUtil.castToBoolean(in)); + break; case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java index 5990176..382dd8c 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java @@ -27,8 +27,6 @@ import org.postgresql.util.PGobject; import org.postgresql.util.PGtokenizer; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.postgresql.log.Logger; -import org.postgresql.log.Log; import java.io.ByteArrayInputStream; import java.io.CharArrayReader; @@ -241,8 +239,9 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS protected Object internalGetObject(int columnIndex, Field field) throws SQLException { switch (getSQLType(columnIndex)) { case Types.BOOLEAN: - case Types.BIT: return getBoolean(columnIndex); + case Types.BIT: + return getBit(columnIndex); case Types.SQLXML: return getSQLXML(columnIndex); case Types.TINYINT: @@ -2093,6 +2092,20 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS return BooleanTypeUtil.castToBoolean(getString(columnIndex)); } + public Object getBit(int columnIndex) throws SQLException { + connection.getLogger().trace("[" + connection.getSocketAddress() + "] " + " getBit columnIndex: " + columnIndex); + checkResultSet(columnIndex); + if (wasNullFlag) { + return null; // SQL NULL + } + + String val = getString(columnIndex); + if (connection.getBitToString()) { + return val; + } + return BooleanTypeUtil.castToBoolean(val); + } + private static final BigInteger BYTEMAX = new BigInteger(Byte.toString(Byte.MAX_VALUE)); private static final BigInteger BYTEMIN = new BigInteger(Byte.toString(Byte.MIN_VALUE)); diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetBitNTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetBitNTest.java new file mode 100644 index 0000000..5ec8158 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetBitNTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import org.junit.Test; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PGobject; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/* + * ResultSet tests. + */ +public class ResultSetBitNTest extends BaseTest4 { + @Override + public void setUp() throws Exception { + super.setUp(); + TestUtil.createTable(con, "test_bit", "id bit, id1 bit(1), id2 bit(10)"); + String insertOne = "insert into test_bit (id, id1, id2) values ('1', b'0', b'1111100000')"; + try (Statement stmt = con.createStatement()) { + stmt.executeUpdate(insertOne); + } + } + + @Override + public void tearDown() throws SQLException { + TestUtil.dropTable(con, "test_bit"); + super.tearDown(); + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + props.setProperty("loggerLevel", "TRACE"); + props.setProperty("bitToString", "true"); + } + + @Test + public void testInsertDirectly() throws SQLException { + String insertSql = "insert into test_bit (id, id1, id2) values ('1', b'0', b'1111100000')"; + try (Statement st = con.createStatement()) { + int number = st.executeUpdate(insertSql); + assertEquals(1, number); + } + } + + @Test + public void testInsertByPreparedStatementObj() throws SQLException { + String insertSql = "insert into test_bit (id, id1, id2) values (?::bit, ?::bit, ?)"; + try (PreparedStatement ps = con.prepareStatement(insertSql)) { + ps.setObject(1, "1"); + ps.setObject(2, "0"); + PGobject pObj = new PGobject(); + pObj.setType("bit"); + pObj.setValue("1010111111"); + ps.setObject(3, pObj); + boolean success = ps.execute(); + assertFalse(success); + int num = ps.getUpdateCount(); + assertEquals(1, num); + } + } + + @Test + public void testInsertByPreparedStatementString() throws SQLException { + String insertSql = "insert into test_bit (id, id1, id2) values (?::bit,?::bit,?)"; + try (PreparedStatement ps = con.prepareStatement(insertSql)) { + ps.setString(1, "1"); + ps.setString(2, "0"); + PGobject pObj = new PGobject(); + pObj.setType("bit"); + pObj.setValue("1010111111"); + ps.setObject(3, pObj); + boolean success = ps.execute(); + assertFalse(success); + int num = ps.getUpdateCount(); + assertEquals(1, num); + } + } + + @Test + public void testSelectDirectly() throws SQLException { + String sql = "select id, id1, id2 from test_bit"; + try (Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery(sql)) { + assertTrue(rs.next()); + Object obj = rs.getObject(1); + assertEquals("1", obj); + Object obj1 = rs.getObject(2); + assertEquals("0", obj1); + Object obj2 = rs.getObject(3); + assertEquals("1111100000", obj2); + } + } + } +}