Skip to content

Commit d978a04

Browse files
feature: handle int and float columns
1 parent 8575478 commit d978a04

File tree

11 files changed

+470
-112
lines changed

11 files changed

+470
-112
lines changed

lapis2/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313
silo:
1414
image: ghcr.io/genspectrum/lapis-silo:${SILO_TAG}
1515
ports:
16-
- ":8081"
16+
- "8081:8081"
1717
command:
1818
- "--api"
1919
- "--preprocessingConfig=/data/preprocessingConfig.yaml"

lapis2/src/main/kotlin/org/genspectrum/lapis/config/SequenceFilterFields.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ private fun mapToSequenceFilterFields(databaseMetadata: DatabaseMetadata) = when
3232
"${databaseMetadata.name}From" to SequenceFilterFieldType.DateFrom(databaseMetadata.name),
3333
"${databaseMetadata.name}To" to SequenceFilterFieldType.DateTo(databaseMetadata.name),
3434
)
35+
"int" -> listOf(
36+
databaseMetadata.name to SequenceFilterFieldType.Int,
37+
"${databaseMetadata.name}From" to SequenceFilterFieldType.IntFrom(databaseMetadata.name),
38+
"${databaseMetadata.name}To" to SequenceFilterFieldType.IntTo(databaseMetadata.name),
39+
)
40+
"float" -> listOf(
41+
databaseMetadata.name to SequenceFilterFieldType.Float,
42+
"${databaseMetadata.name}From" to SequenceFilterFieldType.FloatFrom(databaseMetadata.name),
43+
"${databaseMetadata.name}To" to SequenceFilterFieldType.FloatTo(databaseMetadata.name),
44+
)
3545

3646
else -> throw IllegalArgumentException(
3747
"Unknown field type '${databaseMetadata.type}' for field '${databaseMetadata.name}'",
@@ -53,4 +63,10 @@ sealed class SequenceFilterFieldType(val openApiType: kotlin.String) {
5363
object VariantQuery : SequenceFilterFieldType("string")
5464
data class DateFrom(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("string")
5565
data class DateTo(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("string")
66+
object Int : SequenceFilterFieldType("integer")
67+
data class IntFrom(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("integer")
68+
data class IntTo(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("integer")
69+
object Float : SequenceFilterFieldType("number")
70+
data class FloatFrom(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("number")
71+
data class FloatTo(val associatedField: SequenceFilterFieldName) : SequenceFilterFieldType("number")
5672
}

lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloFilterExpressionMapper.kt

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import org.genspectrum.lapis.config.SequenceFilterFieldType
44
import org.genspectrum.lapis.config.SequenceFilterFields
55
import org.genspectrum.lapis.silo.And
66
import org.genspectrum.lapis.silo.DateBetween
7+
import org.genspectrum.lapis.silo.FloatBetween
8+
import org.genspectrum.lapis.silo.FloatEquals
9+
import org.genspectrum.lapis.silo.IntBetween
10+
import org.genspectrum.lapis.silo.IntEquals
711
import org.genspectrum.lapis.silo.NucleotideSymbolEquals
812
import org.genspectrum.lapis.silo.PangoLineageEquals
913
import org.genspectrum.lapis.silo.SiloFilterExpression
@@ -35,14 +39,7 @@ class SiloFilterExpressionMapper(
3539
}
3640
.groupBy({ it.first }, { it.second })
3741

38-
if (allowedSequenceFiltersWithType.keys.any { it.second == Filter.VariantQuery } &&
39-
allowedSequenceFiltersWithType.keys.any { it.second in variantQueryTypes }
40-
) {
41-
throw IllegalArgumentException(
42-
"variantQuery filter cannot be used with other variant filters such as: " +
43-
"${variantQueryTypes.joinToString(", ")}",
44-
)
45-
}
42+
crossValidateFilters(allowedSequenceFiltersWithType)
4643

4744
val filterExpressions = allowedSequenceFiltersWithType.map { (key, values) ->
4845
val (siloColumnName, filter) = key
@@ -52,6 +49,10 @@ class SiloFilterExpressionMapper(
5249
Filter.DateBetween -> mapToDateBetweenFilter(siloColumnName, values)
5350
Filter.NucleotideSymbolEquals -> mapToNucleotideFilter(values[0].value)
5451
Filter.VariantQuery -> mapToVariantQueryFilter(values[0].value)
52+
Filter.IntEquals -> mapToIntEqualsFilter(siloColumnName, values)
53+
Filter.IntBetween -> mapToIntBetweenFilter(siloColumnName, values)
54+
Filter.FloatEquals -> mapToFloatEqualsFilter(siloColumnName, values)
55+
Filter.FloatBetween -> mapToFloatBetweenFilter(siloColumnName, values)
5556
}
5657
}
5758

@@ -70,6 +71,12 @@ class SiloFilterExpressionMapper(
7071
SequenceFilterFieldType.String -> Pair(key, Filter.StringEquals)
7172
SequenceFilterFieldType.MutationsList -> Pair(key, Filter.NucleotideSymbolEquals)
7273
SequenceFilterFieldType.VariantQuery -> Pair(key, Filter.VariantQuery)
74+
SequenceFilterFieldType.Int -> Pair(key, Filter.IntEquals)
75+
is SequenceFilterFieldType.IntFrom -> Pair(type.associatedField, Filter.IntBetween)
76+
is SequenceFilterFieldType.IntTo -> Pair(type.associatedField, Filter.IntBetween)
77+
SequenceFilterFieldType.Float -> Pair(key, Filter.FloatEquals)
78+
is SequenceFilterFieldType.FloatFrom -> Pair(type.associatedField, Filter.FloatBetween)
79+
is SequenceFilterFieldType.FloatTo -> Pair(type.associatedField, Filter.FloatBetween)
7380

7481
null -> throw IllegalArgumentException(
7582
"'$key' is not a valid sequence filter key. Valid keys are: " +
@@ -79,6 +86,47 @@ class SiloFilterExpressionMapper(
7986
return Pair(filterExpressionId, type)
8087
}
8188

89+
private fun crossValidateFilters(
90+
allowedSequenceFiltersWithType: Map<Pair<SequenceFilterFieldName, Filter>, List<SequenceFilterValue>>,
91+
) {
92+
if (allowedSequenceFiltersWithType.keys.any { it.second == Filter.VariantQuery } &&
93+
allowedSequenceFiltersWithType.keys.any { it.second in variantQueryTypes }
94+
) {
95+
throw IllegalArgumentException(
96+
"variantQuery filter cannot be used with other variant filters such as: " +
97+
variantQueryTypes.joinToString(", "),
98+
)
99+
}
100+
101+
val intEqualFilters = allowedSequenceFiltersWithType.keys.filter { it.second == Filter.IntEquals }
102+
for ((intEqualsColumnName, _) in intEqualFilters) {
103+
val intBetweenFilterForSameColumn = allowedSequenceFiltersWithType[
104+
Pair(intEqualsColumnName, Filter.IntBetween),
105+
]
106+
107+
if (intBetweenFilterForSameColumn != null) {
108+
throw IllegalArgumentException(
109+
"Cannot filter by exact int field '$intEqualsColumnName' " +
110+
"and by int range field '${intBetweenFilterForSameColumn[0].originalKey}'.",
111+
)
112+
}
113+
}
114+
115+
val floatEqualFilters = allowedSequenceFiltersWithType.keys.filter { it.second == Filter.FloatEquals }
116+
for ((floatEqualsColumnName, _) in floatEqualFilters) {
117+
val floatBetweenFilterForSameColumn = allowedSequenceFiltersWithType[
118+
Pair(floatEqualsColumnName, Filter.FloatBetween),
119+
]
120+
121+
if (floatBetweenFilterForSameColumn != null) {
122+
throw IllegalArgumentException(
123+
"Cannot filter by exact float field '$floatEqualsColumnName' " +
124+
"and by float range field '${floatBetweenFilterForSameColumn[0].originalKey}'.",
125+
)
126+
}
127+
}
128+
}
129+
82130
private fun mapToVariantQueryFilter(variantQuery: String): SiloFilterExpression {
83131
if (variantQuery.isBlank()) {
84132
throw IllegalArgumentException(
@@ -123,8 +171,8 @@ class SiloFilterExpressionMapper(
123171
private inline fun <reified T : SequenceFilterFieldType> findDateOfFilterType(
124172
dateRangeFilters: List<SequenceFilterValue>,
125173
): LocalDate? {
126-
val fromFilter = dateRangeFilters.find { (type, _, _) -> type is T }
127-
return getAsDate(fromFilter)
174+
val filter = dateRangeFilters.find { (type, _, _) -> type is T }
175+
return getAsDate(filter)
128176
}
129177

130178
private fun getAsDate(sequenceFilterValue: SequenceFilterValue?): LocalDate? {
@@ -162,6 +210,88 @@ class SiloFilterExpressionMapper(
162210
)
163211
}
164212

213+
private fun mapToIntEqualsFilter(
214+
siloColumnName: SequenceFilterFieldName,
215+
values: List<SequenceFilterValue>,
216+
): SiloFilterExpression {
217+
val value = values[0].value
218+
try {
219+
return IntEquals(siloColumnName, value.toInt())
220+
} catch (exception: NumberFormatException) {
221+
throw IllegalArgumentException(
222+
"$siloColumnName '$value' is not a valid integer: ${exception.message}",
223+
exception,
224+
)
225+
}
226+
}
227+
228+
private fun mapToFloatEqualsFilter(
229+
siloColumnName: SequenceFilterFieldName,
230+
values: List<SequenceFilterValue>,
231+
): SiloFilterExpression {
232+
val value = values[0].value
233+
try {
234+
return FloatEquals(siloColumnName, value.toDouble())
235+
} catch (exception: NumberFormatException) {
236+
throw IllegalArgumentException(
237+
"$siloColumnName '$value' is not a valid float: ${exception.message}",
238+
exception,
239+
)
240+
}
241+
}
242+
243+
private fun mapToIntBetweenFilter(
244+
siloColumnName: SequenceFilterFieldName,
245+
values: List<SequenceFilterValue>,
246+
): SiloFilterExpression {
247+
return IntBetween(
248+
siloColumnName,
249+
from = findIntOfFilterType<SequenceFilterFieldType.IntFrom>(values),
250+
to = findIntOfFilterType<SequenceFilterFieldType.IntTo>(values),
251+
)
252+
}
253+
254+
private inline fun <reified T : SequenceFilterFieldType> findIntOfFilterType(
255+
dateRangeFilters: List<SequenceFilterValue>,
256+
): Int? {
257+
val (_, value, originalKey) = dateRangeFilters.find { (type, _, _) -> type is T } ?: return null
258+
259+
try {
260+
return value.toInt()
261+
} catch (exception: NumberFormatException) {
262+
throw IllegalArgumentException(
263+
"$originalKey '$value' is not a valid integer: ${exception.message}",
264+
exception,
265+
)
266+
}
267+
}
268+
269+
private fun mapToFloatBetweenFilter(
270+
siloColumnName: SequenceFilterFieldName,
271+
values: List<SequenceFilterValue>,
272+
): SiloFilterExpression {
273+
return FloatBetween(
274+
siloColumnName,
275+
from = findFloatOfFilterType<SequenceFilterFieldType.FloatFrom>(values),
276+
to = findFloatOfFilterType<SequenceFilterFieldType.FloatTo>(values),
277+
)
278+
}
279+
280+
private inline fun <reified T : SequenceFilterFieldType> findFloatOfFilterType(
281+
dateRangeFilters: List<SequenceFilterValue>,
282+
): Double? {
283+
val (_, value, originalKey) = dateRangeFilters.find { (type, _, _) -> type is T } ?: return null
284+
285+
try {
286+
return value.toDouble()
287+
} catch (exception: NumberFormatException) {
288+
throw IllegalArgumentException(
289+
"$originalKey '$value' is not a valid float: ${exception.message}",
290+
exception,
291+
)
292+
}
293+
}
294+
165295
companion object {
166296
private var NUCLEOTIDE_MUTATION_REGEX: Regex
167297

@@ -188,6 +318,10 @@ class SiloFilterExpressionMapper(
188318
DateBetween,
189319
NucleotideSymbolEquals,
190320
VariantQuery,
321+
IntEquals,
322+
IntBetween,
323+
FloatEquals,
324+
FloatBetween,
191325
}
192326

193327
private val variantQueryTypes = listOf(Filter.PangoLineage, Filter.NucleotideSymbolEquals)

lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@ data class Maybe(val child: SiloFilterExpression) : SiloFilterExpression("Maybe"
4949

5050
data class NOf(val numberOfMatchers: Int, val matchExactly: Boolean, val children: List<SiloFilterExpression>) :
5151
SiloFilterExpression("N-Of")
52+
53+
data class IntEquals(val column: String, val value: Int) : SiloFilterExpression("IntEquals")
54+
55+
data class IntBetween(val column: String, val from: Int?, val to: Int?) : SiloFilterExpression("IntBetween")
56+
57+
data class FloatEquals(val column: String, val value: Double) : SiloFilterExpression("FloatEquals")
58+
59+
data class FloatBetween(val column: String, val from: Double?, val to: Double?) : SiloFilterExpression("FloatBetween")

0 commit comments

Comments
 (0)