From 0f19a087573a3b7351c6d32e31cd4f7f95c89251 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Sat, 7 Oct 2023 16:55:20 -0400
Subject: [PATCH] Add long-to-boolean conversion methods to KiwiJdbc

* Add overloaded booleanFromLong methods. Both accept a ResultSet
  and a String columnName, but one accepts a BooleanConversionOption
  argument while the other doesn't. The one that does not accept
  a BooleanConversionOption uses the ZERO_OR_ONE option.

Closes #1045
---
 .../java/org/kiwiproject/jdbc/KiwiJdbc.java   | 32 ++++++++++
 .../org/kiwiproject/jdbc/KiwiJdbcTest.java    | 62 +++++++++++++++++++
 2 files changed, 94 insertions(+)

diff --git a/src/main/java/org/kiwiproject/jdbc/KiwiJdbc.java b/src/main/java/org/kiwiproject/jdbc/KiwiJdbc.java
index f947e0af..049bbeae 100644
--- a/src/main/java/org/kiwiproject/jdbc/KiwiJdbc.java
+++ b/src/main/java/org/kiwiproject/jdbc/KiwiJdbc.java
@@ -5,6 +5,8 @@
 
 import lombok.experimental.UtilityClass;
 import org.checkerframework.checker.nullness.qual.Nullable;
+import org.kiwiproject.base.KiwiPrimitives;
+import org.kiwiproject.base.KiwiPrimitives.BooleanConversionOption;
 
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -301,6 +303,36 @@ public static <T extends Enum<T>> Optional<T> enumValueOrEmpty(ResultSet rs, Str
         return isNull(enumName) ? Optional.empty() : Optional.of(Enum.valueOf(enumType, enumName));
     }
 
+    /**
+     * Converts a long value in the specified column to a boolean. The database value must be zero, one
+     * or NULL.
+     *
+     * @param rs         the ResultSet
+     * @param columnName the column name
+     * @return true if the database value is one, or false if it is zero or NULL
+     * @throws IllegalArgumentException if the value in the column is not zero, one, or NULL
+     * @throws SQLException             if there is any error getting the value from the database
+     * @see #booleanFromLong(ResultSet, String, BooleanConversionOption)
+     */
+    public static boolean booleanFromLong(ResultSet rs, String columnName) throws SQLException {
+        return booleanFromLong(rs, columnName, BooleanConversionOption.ZERO_OR_ONE);
+    }
+
+    /**
+     * Converts a long value in the specified column to a boolean using the given {@link BooleanConversionOption}.
+     *
+     * @param rs         the ResultSet
+     * @param columnName the column name
+     * @param option     how to convert the long value into a boolean
+     * @return the converted value, determined using the conversion option
+     * @throws SQLException if there is any error getting the value from the database
+     */
+    public static boolean booleanFromLong(ResultSet rs, String columnName, BooleanConversionOption option)
+            throws SQLException {
+
+        return KiwiPrimitives.booleanFromLong(rs.getLong(columnName), option);
+    }
+
     /**
      * Sets the {@link Timestamp} value in a null-safe manner by using the {@link PreparedStatement#setNull(int, int)}
      * method for {@code null} values. Uses {@link Types#TIMESTAMP} as the SQL type.
diff --git a/src/test/java/org/kiwiproject/jdbc/KiwiJdbcTest.java b/src/test/java/org/kiwiproject/jdbc/KiwiJdbcTest.java
index 4e03ca8b..7568b1e8 100644
--- a/src/test/java/org/kiwiproject/jdbc/KiwiJdbcTest.java
+++ b/src/test/java/org/kiwiproject/jdbc/KiwiJdbcTest.java
@@ -12,7 +12,10 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.kiwiproject.base.KiwiPrimitives.BooleanConversionOption;
 
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -526,6 +529,65 @@ enum Season {
         WINTER, SPRING, SUMMER, FALL
     }
 
+    @Nested
+    class BooleanFromLong {
+
+        @ParameterizedTest
+        @CsvSource(textBlock = """
+                1, true,
+                0, false
+                """)
+        void shouldConvert_WithZeroOrOneConversionOption(long value, boolean expectedResult) throws SQLException {
+            var resultSet = newMockResultSet();
+            when(resultSet.getLong(anyString())).thenReturn(value);
+
+            assertThat(KiwiJdbc.booleanFromLong(resultSet, "is_admin"))
+                    .isEqualTo(expectedResult);
+
+            verify(resultSet).getLong("is_admin");
+            verifyNoMoreInteractions(resultSet);
+        }
+
+        @ParameterizedTest
+        @ValueSource(longs = { -1, 2, 4, 42 })
+        void shouldThrowIllegalArgument_WhenValueFromResultSet_IsNotZeroOrOne_WithZeroOrOneConversionOption(long value)
+                throws SQLException {
+
+            var resultSet = newMockResultSet();
+            when(resultSet.getLong(anyString())).thenReturn(value);
+
+            assertThatIllegalArgumentException()
+                    .isThrownBy(() -> KiwiJdbc.booleanFromLong(resultSet, "is_active"))
+                    .withMessage("value must be 0 or 1, but found %d", value);
+
+            verify(resultSet).getLong("is_active");
+            verifyNoMoreInteractions(resultSet);
+        }
+
+        @ParameterizedTest
+        @CsvSource(textBlock = """
+                1, ZERO_OR_ONE, true,
+                1, NON_ZERO_AS_TRUE, true,
+                -1, NON_ZERO_AS_TRUE, true,
+                2, NON_ZERO_AS_TRUE, true,
+                1000, NON_ZERO_AS_TRUE, true,
+                0, ZERO_OR_ONE, false,
+                0, NON_ZERO_AS_TRUE, false
+                """)
+        void shouldConvert_WithBooleanConversionOption(long value,
+                                                       BooleanConversionOption option,
+                                                       boolean expectedResult) throws SQLException {
+            var resultSet = newMockResultSet();
+            when(resultSet.getLong(anyString())).thenReturn(value);
+
+            assertThat(KiwiJdbc.booleanFromLong(resultSet, "is_admin", option))
+                    .isEqualTo(expectedResult);
+
+            verify(resultSet).getLong("is_admin");
+            verifyNoMoreInteractions(resultSet);
+        }
+    }
+
     @Nested
     class NullSafeSetInt {