From ecd5c45448c81facabc6e688b0ae435ebef58f4d Mon Sep 17 00:00:00 2001 From: Andrei Shishkin <70378753+andrey-shishkin@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:33:42 +0400 Subject: [PATCH] Fix reading null values from db (#192) --- .../src/main/scala/zio/jdbc/JdbcDecoder.scala | 109 ++++++++---------- 1 file changed, 45 insertions(+), 64 deletions(-) diff --git a/core/src/main/scala/zio/jdbc/JdbcDecoder.scala b/core/src/main/scala/zio/jdbc/JdbcDecoder.scala index 53bd8458..89c5ad86 100644 --- a/core/src/main/scala/zio/jdbc/JdbcDecoder.scala +++ b/core/src/main/scala/zio/jdbc/JdbcDecoder.scala @@ -616,163 +616,141 @@ trait JdbcDecoderLowPriorityImplicits { meta.getColumnType(columnIndex) match { case SqlTypes.ARRAY => val array = resultSet.getArray(columnIndex) - - createDynamicDecoder(schema, array.getResultSet().getMetaData())(array.getResultSet()) + valueOrNone(array, createDynamicDecoder(schema, array.getResultSet().getMetaData)(array.getResultSet())) case SqlTypes.BIGINT => val bigInt = resultSet.getBigDecimal(columnIndex).toBigInteger() - - DynamicValue.Primitive(bigInt, StandardType.BigIntegerType) + valueOrNone(bigInt, DynamicValue.Primitive(bigInt, StandardType.BigIntegerType)) case SqlTypes.BINARY => val chunk = getBinary(resultSet.getBinaryStream(columnIndex)) - - DynamicValue.Primitive(chunk, StandardType.BinaryType) + valueOrNone(chunk, DynamicValue.Primitive(chunk, StandardType.BinaryType)) case SqlTypes.BIT => - val bit = resultSet.getInt(columnIndex) == 1 - - DynamicValue.Primitive(bit, StandardType.BoolType) + val bit = resultSet.getBoolean(columnIndex) + valueOrNone(bit, DynamicValue.Primitive(bit, StandardType.BoolType)) case SqlTypes.BLOB => val blob = resultSet.getBlob(columnIndex) - - DynamicValue.Primitive(Chunk.fromArray(blob.getBytes(0, blob.length().toInt)), StandardType.BinaryType) + valueOrNone( + blob, + DynamicValue.Primitive(Chunk.fromArray(blob.getBytes(0, blob.length().toInt)), StandardType.BinaryType) + ) case SqlTypes.BOOLEAN => val bool = resultSet.getBoolean(columnIndex) - - DynamicValue.Primitive(bool, StandardType.BoolType) + valueOrNone(bool, DynamicValue.Primitive(bool, StandardType.BoolType)) case SqlTypes.CHAR => - val char: Char = resultSet.getString(columnIndex)(0) - - DynamicValue.Primitive(char, StandardType.CharType) + val char = resultSet.getString(columnIndex)(0) + valueOrNone(char, DynamicValue.Primitive(char, StandardType.CharType)) case SqlTypes.CLOB => val clob = resultSet.getClob(columnIndex) - - DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType) + valueOrNone( + clob, + DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType) + ) case SqlTypes.DATE => val date = resultSet.getDate(columnIndex) - - DynamicValue.Primitive(date.toLocalDate(), StandardType.LocalDateType) + valueOrNone(date, DynamicValue.Primitive(date.toLocalDate(), StandardType.LocalDateType)) case SqlTypes.DECIMAL => val bigDecimal = resultSet.getBigDecimal(columnIndex) - - DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType) + valueOrNone(bigDecimal, DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)) case SqlTypes.DOUBLE => val double = resultSet.getDouble(columnIndex) - - DynamicValue.Primitive(double, StandardType.DoubleType) + valueOrNone(double, DynamicValue.Primitive(double, StandardType.DoubleType)) case SqlTypes.FLOAT => val float = resultSet.getFloat(columnIndex) - - DynamicValue.Primitive(float, StandardType.FloatType) + valueOrNone(float, DynamicValue.Primitive(float, StandardType.FloatType)) case SqlTypes.INTEGER => val int = resultSet.getInt(columnIndex) - - DynamicValue.Primitive(int, StandardType.IntType) + valueOrNone(int, DynamicValue.Primitive(int, StandardType.IntType)) case SqlTypes.LONGNVARCHAR => val string = resultSet.getString(columnIndex) - - DynamicValue.Primitive(string, StandardType.StringType) + valueOrNone(string, DynamicValue.Primitive(string, StandardType.StringType)) case SqlTypes.LONGVARBINARY => val chunk = getBinary(resultSet.getBinaryStream(columnIndex)) - - DynamicValue.Primitive(chunk, StandardType.BinaryType) + valueOrNone(chunk, DynamicValue.Primitive(chunk, StandardType.BinaryType)) case SqlTypes.LONGVARCHAR => val string = resultSet.getString(columnIndex) - - DynamicValue.Primitive(string, StandardType.StringType) + valueOrNone(string, DynamicValue.Primitive(string, StandardType.StringType)) case SqlTypes.NCHAR => val string = resultSet.getNString(columnIndex) - - DynamicValue.Primitive(string, StandardType.StringType) + valueOrNone(string, DynamicValue.Primitive(string, StandardType.StringType)) case SqlTypes.NCLOB => val clob = resultSet.getNClob(columnIndex) - - DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType) + valueOrNone( + clob, + DynamicValue.Primitive(clob.getSubString(0L, clob.length().toInt), StandardType.StringType) + ) case SqlTypes.NULL => DynamicValue.Primitive((), StandardType.UnitType) case SqlTypes.NUMERIC => val bigDecimal = resultSet.getBigDecimal(columnIndex) - - DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType) + valueOrNone(bigDecimal, DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)) case SqlTypes.NVARCHAR => val string = resultSet.getString(columnIndex) - - DynamicValue.Primitive(string, StandardType.StringType) + valueOrNone(string, DynamicValue.Primitive(string, StandardType.StringType)) case SqlTypes.REAL => val bigDecimal = resultSet.getBigDecimal(columnIndex) - - DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType) + valueOrNone(bigDecimal, DynamicValue.Primitive(bigDecimal, StandardType.BigDecimalType)) case SqlTypes.ROWID => val long = resultSet.getLong(columnIndex) - - DynamicValue.Primitive(long, StandardType.LongType) + valueOrNone(long, DynamicValue.Primitive(long, StandardType.LongType)) case SqlTypes.SMALLINT => val short = resultSet.getShort(columnIndex) - - DynamicValue.Primitive(short, StandardType.ShortType) + valueOrNone(short, DynamicValue.Primitive(short, StandardType.ShortType)) case SqlTypes.SQLXML => val xml = resultSet.getSQLXML(columnIndex) - - DynamicValue.Primitive(xml.getString(), StandardType.StringType) + valueOrNone(xml, DynamicValue.Primitive(xml.getString(), StandardType.StringType)) case SqlTypes.TIME => val time = resultSet.getTime(columnIndex) - - DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType) + valueOrNone(time, DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType)) case SqlTypes.TIMESTAMP => val timestamp = resultSet.getTimestamp(columnIndex) - - DynamicValue.Primitive(timestamp.toInstant(), StandardType.InstantType) + valueOrNone(timestamp, DynamicValue.Primitive(timestamp.toLocalDateTime, StandardType.LocalDateTimeType)) case SqlTypes.TIMESTAMP_WITH_TIMEZONE => // TODO: Timezone val timestamp = resultSet.getTimestamp(columnIndex) - - DynamicValue.Primitive(timestamp.toInstant(), StandardType.InstantType) + valueOrNone(timestamp, DynamicValue.Primitive(timestamp.toInstant(), StandardType.InstantType)) case SqlTypes.TIME_WITH_TIMEZONE => // TODO: Timezone val time = resultSet.getTime(columnIndex) - - DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType) + valueOrNone(time, DynamicValue.Primitive(time.toLocalTime(), StandardType.LocalTimeType)) case SqlTypes.TINYINT => val short = resultSet.getShort(columnIndex) - - DynamicValue.Primitive(short, StandardType.ShortType) + valueOrNone(short, DynamicValue.Primitive(short, StandardType.ShortType)) case SqlTypes.VARBINARY => val chunk = getBinary(resultSet.getBinaryStream(columnIndex)) - - DynamicValue.Primitive(chunk, StandardType.BinaryType) + valueOrNone(chunk, DynamicValue.Primitive(chunk, StandardType.BinaryType)) case SqlTypes.VARCHAR => val string = resultSet.getString(columnIndex) - - DynamicValue.Primitive(string, StandardType.StringType) + valueOrNone(string, DynamicValue.Primitive(string, StandardType.StringType)) case other => throw new SQLException( @@ -788,6 +766,9 @@ trait JdbcDecoderLowPriorityImplicits { DynamicValue.Record(TypeId.Structural, listMap) } + protected def valueOrNone(value: Any, dyn: DynamicValue): DynamicValue = + if (value != null) dyn else DynamicValue.NoneValue + def fromSchema[A](implicit schema: Schema[A]): JdbcDecoder[A] = (columnIndex: Int, resultSet: ResultSet) => { val dynamicDecoder = createDynamicDecoder(schema, resultSet.getMetaData())