Repository: firebolt-db/jdbc
Pull request #532: fix: FIR-44784 Fix boolean in prepared statements
Author: login: bogdantruta-firebolt
Created: 2025-03-31T15:13:13Z | Merged: 2025-04-08T07:17:44Z

Added a specialized class to handle all sorts of conversions from String, Numbers and Characters to Boolean.
Created tests for this method but in the current configuration of our transformer they do not work. 
We will need to update it to align ourself with the JDBC spec.
For the moment, setObject for a boolean outcome only work from a boolean object.


Diff:
diff --git a/build.gradle b/build.gradle
index 8f7b5db27f..e1fb0e63b9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -88,6 +88,7 @@ dependencies {
     testImplementation 'io.zonky.test:embedded-postgres:2.1.0'
     testCompileOnly 'org.slf4j:slf4j-api:2.0.17'
     testCommonImplementation 'org.junit.jupiter:junit-jupiter-api:5.12.1'
+    testCommonImplementation 'org.junit.jupiter:junit-jupiter-params:5.12.1'
     testImplementation sourceSets.testCommon.output
     compileTestJava.dependsOn processTestResources
     jar.dependsOn processTestResources
diff --git a/src/integrationTest/java/integration/tests/PreparedStatementTest.java b/src/integrationTest/java/integration/tests/PreparedStatementTest.java
index 2b0c9b3cd7..65f8345fca 100644
--- a/src/integrationTest/java/integration/tests/PreparedStatementTest.java
+++ b/src/integrationTest/java/integration/tests/PreparedStatementTest.java
@@ -14,6 +14,7 @@
 import lombok.Value;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -38,8 +39,6 @@
 import java.sql.Types;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -589,13 +588,59 @@ void shouldFetchSpecialCharacters() throws SQLException, MalformedURLException {
 		}
 	}
 
+	@Test
+	void shouldFetchBoolean() throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b, ? as c")) {
+				statement.setBoolean(1, true);
+				statement.setObject(2, false);
+				statement.setObject(3, true, Types.BOOLEAN);
+				statement.execute();
+				ResultSet rs = statement.getResultSet();
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(1).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(2).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(3).toLowerCase());
+				assertTrue(rs.next());
+				assertTrue(rs.getBoolean(1));
+				assertFalse(rs.getBoolean(2));
+				assertTrue(rs.getBoolean(3));
+			}
+		}
+	}
+
+	@Disabled
+	@ParameterizedTest
+	@MethodSource("com.firebolt.jdbc.testutils.TestFixtures#booleanTypes")
+	void shouldFetchBooleanFromVariousObjects(Object objectTrue, Object objectFalse) throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b")) {
+				statement.setObject(1, objectTrue, Types.BOOLEAN);
+				statement.setObject(2, objectFalse, Types.BOOLEAN);
+				statement.execute();
+				ResultSet rs = statement.getResultSet();
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(1).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(2).toLowerCase());
+				assertTrue(rs.next());
+				assertTrue(rs.getBoolean(1));
+				assertFalse(rs.getBoolean(2));
+			}
+		}
+	}
+
 	Stream<Arguments> dateTypes() {
 		return Stream.of(
-				Arguments.of(LocalDateTime.of(2019, 7, 31, 14, 15, 13),
+				Arguments.of(new Timestamp(1564571713000L).toLocalDateTime(),
 						LocalDate.of(2019, 7, 31), true),
 				Arguments.of(new Timestamp(1564571713000L),
 						new Date(1564527600000L), true),
-				Arguments.of(LocalDateTime.of(2019, 7, 31, 14, 15, 13),
+				Arguments.of(new Timestamp(1564571713000L).toLocalDateTime(),
 						LocalDate.of(2019, 7, 31), false),
 				Arguments.of(new Timestamp(1564571713000L),
 						new Date(1564527600000L), false)
diff --git a/src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java b/src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
new file mode 100644
index 0000000000..0340c4eae2
--- /dev/null
+++ b/src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
@@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201") // Pattern Matching for "instanceof" was introduced in java 16 while we still try to be compliant with java 11
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // 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 cannotCastException(strval);
+    }
+
+    private static boolean fromCharacter(final Character charval) throws FireboltException {
+        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 cannotCastException(charval);
+    }
+
+    private static boolean fromNumber(final Number numval) throws FireboltException {
+        // Handles BigDecimal, Byte, Short, Integer, Long, Float, Double
+        // based on the widening primitive conversions.
+        final double value = numval.doubleValue();
+        if (value == 1.0d) {
+            return true;
+        }
+        if (value == 0.0d) {
+            return false;
+        }
+        throw cannotCastException(numval);
+    }
+
+    private static FireboltException cannotCastException(final Object value) {
+        return new FireboltException(format("Cannot cast to boolean: \"%s\"", value));
+    }
+
+}
diff --git a/src/main/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLString.java b/src/main/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLString.java
index 4a8d06135b..608e9ba001 100644
--- a/src/main/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLString.java
+++ b/src/main/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLString.java
@@ -34,7 +34,7 @@
 import static java.util.stream.Collectors.toMap;
 
 public enum JavaTypeToFireboltSQLString {
-	BOOLEAN(Boolean.class, value -> Boolean.TRUE.equals(value) ? "1" : "0"),
+	BOOLEAN(Boolean.class, BooleanTypeUtil::castToFireboltBoolean),
 	UUID(java.util.UUID.class, Object::toString),
 	BYTE(Byte.class, value -> Byte.toString(((Number) value).byteValue())),
 	SHORT(Short.class, value -> Short.toString(((Number) value).shortValue())),
diff --git a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
index 4f51973a4b..0368af7bc0 100644
--- a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
+++ b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
@@ -407,7 +407,7 @@ void shouldSetBoolean() throws SQLException {
 		statement.execute();
 
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
-		assertEquals("INSERT INTO cars(available) VALUES (1)", queryInfoWrapperArgumentCaptor.getValue().getSql());
+		assertEquals("INSERT INTO cars(available) VALUES (true)", queryInfoWrapperArgumentCaptor.getValue().getSql());
 	}
 
 	@ParameterizedTest
@@ -547,7 +547,7 @@ void shouldSetAllObjects(Object timestampOrLocalDateTime, Object dateOrLocalDate
 
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
 		assertEquals(
-				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,1,5)",
+				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,true,5)",
 				queryInfoWrapperArgumentCaptor.getValue().getSql());
 	}
 
@@ -573,7 +573,7 @@ void shouldSetAllObjectsWithCorrectSqlType(Object timestampOrLocalDateTime, Obje
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
 
 		assertEquals(
-				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,1,5)",
+				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,true,5)",
 				queryInfoWrapperArgumentCaptor.getValue().getSql());
 	}
 
diff --git a/src/test/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLStringTest.java b/src/test/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLStringTest.java
index 7e7b24ff18..320881740f 100644
--- a/src/test/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLStringTest.java
+++ b/src/test/java/com/firebolt/jdbc/type/JavaTypeToFireboltSQLStringTest.java
@@ -5,8 +5,10 @@
 import com.firebolt.jdbc.type.array.FireboltArray;
 import com.firebolt.jdbc.type.array.SqlArrayUtil;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.junitpioneer.jupiter.DefaultTimeZone;
 
 import java.sql.Date;
@@ -24,6 +26,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class JavaTypeToFireboltSQLStringTest {
 
 	@Test
@@ -39,9 +42,9 @@ void shouldTransformNull(JavaTypeToFireboltSQLString type) throws SQLException {
 
 	@Test
 	void shouldTransformBooleanToString() throws SQLException {
-		assertEquals("1", JavaTypeToFireboltSQLString.BOOLEAN.transform(true));
+		assertEquals("true", JavaTypeToFireboltSQLString.BOOLEAN.transform(true));
 
-		assertEquals("0", JavaTypeToFireboltSQLString.BOOLEAN.transform(false));
+		assertEquals("false", JavaTypeToFireboltSQLString.BOOLEAN.transform(false));
 
 		assertEquals("NULL", JavaTypeToFireboltSQLString.BOOLEAN.transform(null));
 	}
@@ -232,4 +235,11 @@ void shouldThrowExceptionWhenObjectCouldNotBeTransformed() {
 	void shouldTransformNullValue(JavaTypeToFireboltSQLString type) throws SQLException {
 		assertEquals(NULL_VALUE, type.transform(null));
 	}
+
+	@ParameterizedTest
+	@MethodSource("com.firebolt.jdbc.testutils.TestFixtures#booleanTypes")
+	void shouldTransformBooleanTypes(Object trueValue, Object falseValue) throws SQLException {
+		assertEquals("true", JavaTypeToFireboltSQLString.BOOLEAN.transform(trueValue));
+		assertEquals("false", JavaTypeToFireboltSQLString.BOOLEAN.transform(falseValue));
+	}
 }
diff --git a/src/testCommon/java/com/firebolt/jdbc/testutils/TestFixtures.java b/src/testCommon/java/com/firebolt/jdbc/testutils/TestFixtures.java
new file mode 100644
index 0000000000..b5caa2ba35
--- /dev/null
+++ b/src/testCommon/java/com/firebolt/jdbc/testutils/TestFixtures.java
@@ -0,0 +1,27 @@
+package com.firebolt.jdbc.testutils;
+
+import org.junit.jupiter.params.provider.Arguments;
+
+import java.util.stream.Stream;
+
+public class TestFixtures {
+    public static Stream<Arguments> booleanTypes() {
+        return Stream.of(
+                Arguments.of("1", "0"),
+                Arguments.of("true", "false"),
+                Arguments.of("t", "f"),
+                Arguments.of("yes", "no"),
+                Arguments.of("y", "n"),
+                Arguments.of("on", "off"),
+                Arguments.of('1', '0'),
+                Arguments.of('t', 'f'),
+                Arguments.of('T', 'F'),
+                Arguments.of('y', 'n'),
+                Arguments.of('Y', 'N'),
+                Arguments.of(1, 0),
+                Arguments.of(1.0, 0.0),
+                Arguments.of(1F, 0F),
+                Arguments.of(1L, 0L)
+        );
+    }
+}


Reviews:
user: login: goprean
body: I see that in some tests you have changed from 1 to true (boolean). But wasn't the actual problem that 1 was not treated as a boolean?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T10:29:22Z
user: login: bogdantruta-firebolt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T11:59:42Z
user: login: ptiurin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T15:22:33Z
user: login: bogdantruta-firebolt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T07:59:16Z
user: login: bogdantruta-firebolt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:16:10Z
user: login: bogdantruta-firebolt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:18:10Z
user: login: ptiurin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:40:19Z
user: login: ptiurin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T08:43:05Z
user: login: ptiurin
body: LGTM, but make sure to resolve any open conversations.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T08:50:24Z
user: login: goprean
body: I think the spec is very clear about how the transforming should be done. 
If setObject(index, object) is used without the sql type, then a default mapping is done (table B4 from the spec: https://download.oracle.com/otn-pub/jcp/jdbc-4_3-mrel3-eval-spec/jdbc4.3-fr-spec.pdf?AuthParam=1744031434_3e1d01e62d033e97eb5e7c88f5039beb). For example if the object is Boolean.class then we transform it to a boolean value to be sent to firebolt. 

if setObject(index, object, sqlType) is used, then the spec is also clear that only certain java classes with sql types should match. (the full list is B5 table - https://download.oracle.com/otn-pub/jcp/jdbc-4_3-mrel3-eval-spec/jdbc4.3-fr-spec.pdf?AuthParam=1744031434_3e1d01e62d033e97eb5e7c88f5039beb).

So while this is fixing this case, I can see other cases where the setObject method is not going to work. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:46:03Z

Review comments:
diff_hunk: @@ -555,6 +556,72 @@ void shouldFetchSpecialCharacters() throws SQLException, MalformedURLException {
 		}
 	}
 
+	@Test
+	void shouldFetchBoolean() throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b, ? as c")) {
+				statement.setBoolean(1, true);
+				statement.setObject(2, false);
path: src/integrationTest/java/integration/tests/PreparedStatementTest.java
user: login: goprean
body: the bug mentions that this does not work: statement.setObject(paramIndex, 1,  SqlTypes.BOOLEAN). 
Can we also set it in tests to make sure it works?
created_at: 2025-04-01T10:25:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: goprean
body: I think we need to check for nulls. If you convert a null to a boolean it will throw a NPE (null pointer exception)
created_at: 2025-04-01T10:26:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -403,7 +403,7 @@ void shouldSetBoolean() throws SQLException {
 		statement.execute();
 
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
-		assertEquals("INSERT INTO cars(available) VALUES (1)", queryInfoWrapperArgumentCaptor.getValue().getSql());
path: src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
user: login: goprean
body: why are we changing this? the issue was with boolean as a number of (in this case 1). We should probably add another insert with VALUES(true) but as a separate line.
created_at: 2025-04-01T10:27:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -542,7 +542,7 @@ void shouldSetAllObjects() throws SQLException {
 
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
 		assertEquals(
-				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,1,5)",
+				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,true,5)",
path: src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
user: login: goprean
body: same here. Why are we changing it from 1 to true. The problem was with 1. 
created_at: 2025-04-01T10:28:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -567,7 +567,7 @@ void shouldSetAllObjectsWithCorrectSqlType() throws SQLException {
 		verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(properties), any());
 
 		assertEquals(
-				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,1,5)",
+				"INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,true,5)",
path: src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java
user: login: goprean
body: and here
created_at: 2025-04-01T10:28:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -555,6 +556,72 @@ void shouldFetchSpecialCharacters() throws SQLException, MalformedURLException {
 		}
 	}
 
+	@Test
+	void shouldFetchBoolean() throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b, ? as c")) {
+				statement.setBoolean(1, true);
+				statement.setObject(2, false);
path: src/integrationTest/java/integration/tests/PreparedStatementTest.java
user: login: bogdantruta-firebolt
body: I test that in the following 2 lines, both the paramIndex, object method, and the one with the sqlTargetType, but I could make a test where all values are both true and false to test both cases for all methods
created_at: 2025-04-01T11:59:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -555,6 +556,72 @@ void shouldFetchSpecialCharacters() throws SQLException, MalformedURLException {
 		}
 	}
 
+	@Test
+	void shouldFetchBoolean() throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b, ? as c")) {
+				statement.setBoolean(1, true);
+				statement.setObject(2, false);
+				statement.setObject(3, true, Types.BOOLEAN);
+				statement.execute();
+				ResultSet rs = statement.getResultSet();
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(1).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(2).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(3).toLowerCase());
+				assertTrue(rs.next());
+				assertTrue(rs.getBoolean(1));
+				assertFalse(rs.getBoolean(2));
+				assertTrue(rs.getBoolean(3));
+			}
+		}
+	}
+
+	@Ignore
+	@ParameterizedTest
+	@MethodSource("booleanTypes")
+	void shouldFetchBooleanFromVariousObjects(Object objectTrue, Object objectFalse) throws SQLException {
+		try (Connection connection = createConnection()) {
+			try (PreparedStatement statement = connection
+					.prepareStatement("SELECT ? as a, ? as b")) {
+				statement.setObject(1, objectTrue, Types.BOOLEAN);
+				statement.setObject(2, objectFalse, Types.BOOLEAN);
+				statement.execute();
+				ResultSet rs = statement.getResultSet();
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(1).toLowerCase());
+				assertEquals(FireboltDataType.BOOLEAN.name().toLowerCase(),
+						rs.getMetaData().getColumnTypeName(2).toLowerCase());
+				assertTrue(rs.next());
+				assertTrue(rs.getBoolean(1));
+				assertFalse(rs.getBoolean(2));
+			}
+		}
+	}
+
+	Stream<Arguments> booleanTypes() {
path: src/integrationTest/java/integration/tests/PreparedStatementTest.java
user: login: ptiurin
body: We're duplicating this method. Is there a way to reuse it in different tests? Can we add it to the parent class?
created_at: 2025-04-03T15:06:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: ptiurin
body: What are we suppressing here? 
created_at: 2025-04-03T15:12:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // Leading or trailing whitespace is ignored, and case does not matter.
+        final String val = strval.trim();
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: ptiurin
body: I'm not sure if we should do this. If there's whitespace something clearly is wrong and we should not assume the value is boolean. Instead, make the user sanitize the values.
created_at: 2025-04-03T15:20:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // 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)) {
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: ptiurin
body: I'm not on board with allowing on/off and y/n. Is there a convention on which values are castable to boolean?
created_at: 2025-04-03T15:21:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: bogdantruta-firebolt
body: It was an issue from Sonar that said the object casting should be done in the instanceof statement, but that is a Java 16 feature whilst we support Java 11, so I had to surpress the warning. Will leave a comment next to the annotation.
created_at: 2025-04-04T07:59:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // Leading or trailing whitespace is ignored, and case does not matter.
+        final String val = strval.trim();
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: bogdantruta-firebolt
body: I believe that since we are casting other objects to a boolean value, we might as well trim whitespaces from a string. This behavior is taken directly from the Postgres JDBC driver
created_at: 2025-04-04T08:16:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // 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)) {
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: bogdantruta-firebolt
body: I am not sure of any convention, but after trying these values directly in the Firebolt console, they work in the same way as inputing the related boolean value, so I am guessing changing them client side would not do us any harm. Then again, we have the option of just setting the number/text value, but that would not work in the case of a select statement,
created_at: 2025-04-04T08:18:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // Leading or trailing whitespace is ignored, and case does not matter.
+        final String val = strval.trim();
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: ptiurin
body: Ok, looks like PG also thinks this is fine so I guess we'll do the same.
created_at: 2025-04-04T08:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+package com.firebolt.jdbc.type;
+
+import com.firebolt.jdbc.exception.FireboltException;
+import lombok.CustomLog;
+
+import static java.lang.String.format;
+
+@CustomLog
+class BooleanTypeUtil {
+
+    private BooleanTypeUtil() {
+    }
+
+    static String castToFireboltBoolean(Object value) throws FireboltException {
+        return castToBoolean(value) ? "true" : "false";
+    }
+
+    /**
+     * 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 FireboltException cannot
+     */
+    @SuppressWarnings("java:S6201")
+    static boolean castToBoolean(final Object in) throws FireboltException {
+        log.debug("Cast to boolean: \"{0}\"", String.valueOf(in));
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        }
+        if (in instanceof String) {
+            return fromString((String) in);
+        }
+        if (in instanceof Character) {
+            return fromCharacter((Character) in);
+        }
+        if (in instanceof Number) {
+            return fromNumber((Number) in);
+        }
+        throw new FireboltException(format("Cannot cast %s to boolean", in));
+    }
+
+    static boolean fromString(final String strval) throws FireboltException {
+        // 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)) {
path: src/main/java/com/firebolt/jdbc/type/BooleanTypeUtil.java
user: login: ptiurin
body: Looks like there's none, and a lengthy discussion in PG mailing list. However, Postgres seems to [support](https://www.postgresql.org/docs/current/datatype-boolean.html) these values too so since we want to be compatible with PG as much as possible this is fine...
created_at: 2025-04-04T08:43:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-07T17:30:42Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=firebolt-db_jdbc&pullRequest=532) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=firebolt-db_jdbc&pullRequest=532&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=firebolt-db_jdbc&pullRequest=532&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=firebolt-db_jdbc&pullRequest=532&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [90.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_jdbc&pullRequest=532&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=firebolt-db_jdbc&pullRequest=532&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=firebolt-db_jdbc&pullRequest=532)


author_association: NONE