From 0e56cb4e8c60275575c5246a7daa6de49fe987db Mon Sep 17 00:00:00 2001 From: Alexander Taepper Date: Thu, 21 May 2026 11:45:38 +0200 Subject: [PATCH 1/3] feat(lapis): also handle SILO returning arrow date values --- .../main/kotlin/org/genspectrum/lapis/silo/ArrowRowConverter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lapis/src/main/kotlin/org/genspectrum/lapis/silo/ArrowRowConverter.kt b/lapis/src/main/kotlin/org/genspectrum/lapis/silo/ArrowRowConverter.kt index 94c068fbd..d68241b56 100644 --- a/lapis/src/main/kotlin/org/genspectrum/lapis/silo/ArrowRowConverter.kt +++ b/lapis/src/main/kotlin/org/genspectrum/lapis/silo/ArrowRowConverter.kt @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.TextNode import org.apache.arrow.vector.BigIntVector import org.apache.arrow.vector.BitVector +import org.apache.arrow.vector.DateDayVector import org.apache.arrow.vector.Float4Vector import org.apache.arrow.vector.Float8Vector import org.apache.arrow.vector.IntVector @@ -188,6 +189,7 @@ private fun VectorSchemaRoot.fieldValueAsJsonNode( is Float8Vector -> DoubleNode(vector.get(rowIndex)) is Float4Vector -> FloatNode(vector.get(rowIndex)) is BitVector -> BooleanNode.valueOf(vector.get(rowIndex) != 0) + is DateDayVector -> TextNode(java.time.LocalDate.ofEpochDay(vector.get(rowIndex).toLong()).toString()) else -> TextNode(vector.getObject(rowIndex)?.toString() ?: "") } } From 23b1f480c58cf51db2a1966082f4f6e47cb16b03 Mon Sep 17 00:00:00 2001 From: Alexander Taepper Date: Tue, 26 May 2026 13:16:11 +0200 Subject: [PATCH 2/3] fixup! feat(lapis): also handle SILO returning arrow date values --- .../genspectrum/lapis/silo/ArrowTestHelper.kt | 13 ++++++++ .../genspectrum/lapis/silo/SiloClientTest.kt | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/ArrowTestHelper.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/ArrowTestHelper.kt index 09cea8d8e..8434993dc 100644 --- a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/ArrowTestHelper.kt +++ b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/ArrowTestHelper.kt @@ -3,17 +3,20 @@ package org.genspectrum.lapis.silo import org.apache.arrow.memory.RootAllocator import org.apache.arrow.vector.BigIntVector import org.apache.arrow.vector.BitVector +import org.apache.arrow.vector.DateDayVector import org.apache.arrow.vector.Float8Vector import org.apache.arrow.vector.IntVector import org.apache.arrow.vector.VarCharVector import org.apache.arrow.vector.VectorSchemaRoot import org.apache.arrow.vector.ipc.ArrowStreamWriter +import org.apache.arrow.vector.types.DateUnit import org.apache.arrow.vector.types.pojo.ArrowType import org.apache.arrow.vector.types.pojo.Field import org.apache.arrow.vector.types.pojo.FieldType import org.apache.arrow.vector.types.pojo.Schema import java.io.ByteArrayOutputStream import java.nio.channels.Channels +import java.time.LocalDate /** * Builds an Arrow IPC stream byte array from [rows] using the given builder. @@ -51,6 +54,8 @@ fun buildArrowIpcStream(rows: List>): ByteArray { is Boolean -> Field(name, FieldType.nullable(ArrowType.Bool()), null) + is LocalDate -> Field(name, FieldType.nullable(ArrowType.Date(DateUnit.DAY)), null) + // Note: null values (and any unrecognized types) are typed as Utf8. // If the first row has null for a column that later rows fill with a typed value (e.g. Int, Long), // the column will be inferred as Utf8 and the converter will throw a cast error at runtime. @@ -101,6 +106,14 @@ fun buildArrowIpcStream(rows: List>): ByteArray { } } + is DateDayVector -> { + if (value == null) { + vector.setNull(rowIdx) + } else { + vector.set(rowIdx, (value as LocalDate).toEpochDay().toInt()) + } + } + is VarCharVector -> { if (value == null) { vector.setNull(rowIdx) diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt index 0d50475ca..2f96eaafd 100644 --- a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt +++ b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.node.DoubleNode import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.TextNode +import java.time.LocalDate import org.genspectrum.lapis.config.SiloVersion import org.genspectrum.lapis.logging.RequestIdContext import org.genspectrum.lapis.request.Order @@ -295,6 +296,36 @@ class SiloClientTest( ) } + @Test + fun `GIVEN server returns details with date32 column THEN date values are converted to ISO-8601 strings and nulls remain null`() { + expectQueryRequestAndRespondWith( + response() + .withContentType(parse(ARROW_STREAM_MEDIA_TYPE)) + .withBody( + buildArrowIpcStream( + listOf( + mapOf("date" to LocalDate.of(2021, 2, 23), "label" to "A"), + mapOf("date" to LocalDate.of(2021, 3, 19), "label" to "B"), + mapOf("date" to null, "label" to "C"), + ), + ), + ), + ) + + val query = SiloQuery(SiloAction.details(), StringEquals("theColumn", "theValue")) + val result = sendQuery(query) + + assertThat(result, hasSize(3)) + assertThat( + result, + containsInAnyOrder( + DetailsData(mapOf("date" to TextNode("2021-02-23"), "label" to TextNode("A"))), + DetailsData(mapOf("date" to TextNode("2021-03-19"), "label" to TextNode("B"))), + DetailsData(mapOf("date" to NullNode.instance, "label" to TextNode("C"))), + ), + ) + } + @Test fun `GIVEN server returns most recent common ancestor response THEN response can be deserialized`() { expectQueryRequestAndRespondWith( From a45a82bf526f6bb9076da8f6b75bb88283bd2688 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 27 May 2026 08:34:11 +0200 Subject: [PATCH 3/3] fix lints --- .../test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt index 2f96eaafd..f36c8e9a1 100644 --- a/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt +++ b/lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.node.DoubleNode import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.TextNode -import java.time.LocalDate import org.genspectrum.lapis.config.SiloVersion import org.genspectrum.lapis.logging.RequestIdContext import org.genspectrum.lapis.request.Order @@ -42,6 +41,7 @@ import org.mockserver.model.MediaType import org.mockserver.model.MediaType.parse import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import java.time.LocalDate private const val MOCK_SERVER_PORT = 1080 @@ -297,7 +297,7 @@ class SiloClientTest( } @Test - fun `GIVEN server returns details with date32 column THEN date values are converted to ISO-8601 strings and nulls remain null`() { + fun `GIVEN server returns date32 column THEN dates are converted to ISO-8601 strings and nulls remain null`() { expectQueryRequestAndRespondWith( response() .withContentType(parse(ARROW_STREAM_MEDIA_TYPE))