Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lapis-docs/src/components/FiltersTable/getFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export function getFilters(config: Config) {
{
name: metadata.name,
type: metadata.type,
description: `Filters the "${metadata.name}" column" with exact match`,
description:
`Filters the "${metadata.name}" column with exact match. ` +
`You can also supply an array of values - they will be combined with logical OR.`,
},
{
name: `${metadata.name}From`,
Expand All @@ -36,7 +38,9 @@ export function getFilters(config: Config) {
const stringDescription = {
name: metadata.name,
type: metadata.type,
description: `Filters the "${metadata.name}" column" with exact match`,
description:
`Filters the "${metadata.name}" column with exact match. ` +
`You can also supply an array of values - they will be combined with logical OR.`,
Comment thread
fengelniederhammer marked this conversation as resolved.
};

const allowRegexSearchDescription = {
Expand Down
11 changes: 11 additions & 0 deletions lapis-e2e/test/aggregatedQueries/dateEqualsMultiple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"testCaseName": "date equals with multiple values",
"lapisRequest": {
"date": ["2021-05-08", "2021-01-20"]
},
"expected": [
{
"count": 6
}
]
}
11 changes: 11 additions & 0 deletions lapis-e2e/test/aggregatedQueries/floatEqualsMultiple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"testCaseName": "float equals with multiple values",
"lapisRequest": {
"qcValue": [0.97, 0.98]
},
"expected": [
{
"count": 20
}
]
}
11 changes: 11 additions & 0 deletions lapis-e2e/test/aggregatedQueries/intEqualsMultiple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"testCaseName": "int equals with multiple values",
"lapisRequest": {
"age": [51, 52]
},
"expected": [
{
"count": 15
}
]
}
2 changes: 1 addition & 1 deletion lapis-e2e/test/aminoAcidSequence.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('The /alignedAminoAcidSequence endpoint', () => {

const errorResponse = await response.json();
expect(errorResponse.error.detail).to.match(
/Error from SILO: The table does not contain the SequenceColumn 'unknownGene'/
/Error from SILO: The table does not contain the field unknownGene/
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but SILO recently changed the error message in this case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,14 @@ class SiloFilterExpressionMapper(
}

if (exactDateFilters.isNotEmpty()) {
return when (val date = getAsDate(exactDateFilters[0])) {
null -> IsNull(column = siloColumnName)
else -> DateBetween(column = siloColumnName, from = date, to = date)
}
return Or(
exactDateFilters[0].values.map {
when (val date = parseDate(it, exactDateFilters[0].originalKey)) {
null -> IsNull(column = siloColumnName)
else -> DateBetween(column = siloColumnName, from = date, to = date)
}
},
)
}

return DateBetween(
Expand All @@ -321,7 +325,14 @@ class SiloFilterExpressionMapper(
private fun getAsDate(sequenceFilterValue: SequenceFilterValue?): LocalDate? {
val (_, values, originalKey) = sequenceFilterValue ?: return null
val value = extractSingleFilterValue(values, originalKey) ?: return null
return parseDate(value, originalKey)
}

private fun parseDate(
value: String?,
originalKey: String,
): LocalDate? {
value ?: return null
try {
return LocalDate.parse(value)
} catch (exception: DateTimeParseException) {
Expand Down Expand Up @@ -350,36 +361,44 @@ class SiloFilterExpressionMapper(
private fun mapToIntEqualsFilter(
siloColumnName: SequenceFilterFieldName,
values: List<SequenceFilterValue>,
): SiloFilterExpression {
val value = extractSingleFilterValue(values[0])
?: return IsNull(column = siloColumnName)

try {
return IntEquals(siloColumnName, value.toInt())
} catch (exception: NumberFormatException) {
throw BadRequestException(
"$siloColumnName '$value' is not a valid integer: ${exception.message}",
exception,
)
}
}
): SiloFilterExpression =
Or(
values[0].values.map {
when (it) {
null -> IsNull(column = siloColumnName)

else -> try {
IntEquals(siloColumnName, it.toInt())
} catch (exception: NumberFormatException) {
throw BadRequestException(
"$siloColumnName '$it' is not a valid integer: ${exception.message}",
exception,
)
}
}
},
)

private fun mapToFloatEqualsFilter(
siloColumnName: SequenceFilterFieldName,
values: List<SequenceFilterValue>,
): SiloFilterExpression {
val value = extractSingleFilterValue(values[0])
?: return IsNull(column = siloColumnName)

try {
return FloatEquals(siloColumnName, value.toDouble())
} catch (exception: NumberFormatException) {
throw BadRequestException(
"$siloColumnName '$value' is not a valid float: ${exception.message}",
exception,
)
}
}
): SiloFilterExpression =
Or(
values[0].values.map {
when (it) {
null -> IsNull(column = siloColumnName)

else -> try {
FloatEquals(siloColumnName, it.toDouble())
} catch (exception: NumberFormatException) {
throw BadRequestException(
"$siloColumnName '$it' is not a valid float: ${exception.message}",
exception,
)
}
}
},
)

private fun mapToIntBetweenFilter(
siloColumnName: SequenceFilterFieldName,
Expand Down
44 changes: 41 additions & 3 deletions lapis/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -508,13 +508,51 @@ private fun primitiveSequenceFilterFieldSchemas(sequenceFilterFields: SequenceFi

private fun filterFieldSchema(fieldType: SequenceFilterFieldType) =
when (fieldType) {
SequenceFilterFieldType.String ->
SequenceFilterFieldType.String -> {
val fieldSchema = stringSchema(fieldType.openApiType)
.description("A string or null")
Schema<String>().anyOf(
listOf(
stringSchema(fieldType.openApiType),
logicalOrArraySchema(stringSchema(fieldType.openApiType)),
fieldSchema,
logicalOrArraySchema(fieldSchema),
),
)
}

SequenceFilterFieldType.Int -> {
val fieldSchema = Schema<Int>()
.types(setOf(fieldType.openApiType))
.description("An integer or null")
Schema<Any>().anyOf(
listOf(
fieldSchema,
logicalOrArraySchema(fieldSchema),
),
)
Comment thread
fengelniederhammer marked this conversation as resolved.
}

SequenceFilterFieldType.Float -> {
val fieldSchema = Schema<Float>()
.types(setOf(fieldType.openApiType))
.description("A float or null")
Schema<Any>().anyOf(
listOf(
fieldSchema,
logicalOrArraySchema(fieldSchema),
),
)
}

SequenceFilterFieldType.Date -> {
val fieldSchema = stringSchema(fieldType.openApiType)
.description("A date string (YYYY-MM-DD) or null")
Schema<String>().anyOf(
listOf(
fieldSchema,
logicalOrArraySchema(fieldSchema),
),
)
}

SequenceFilterFieldType.Lineage -> {
val fieldSchema = stringSchema(fieldType.openApiType)
Expand Down
9 changes: 6 additions & 3 deletions lapis/src/main/resources/templates/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ You can use metadata fields as filter parameters in your queries. The filter syn
You can also supply an array - the fields will be combined with logical OR.
Examples: `"[(${stringField})]": "someValue"`, `"[(${stringField})].regex": "^startsWithThis*"`, `"[(${stringField})]": ["someValue", "orOtherValue"]`[/]
[# th:if="${lineageField != null}"]- **Lineage fields**: For string fields that also have a lineage index, you can filter for exact matches (`"[(${lineageField})]": "lineage"`) or including sublineages (`"[(${lineageField})]": "lineage*"`).[/]
[# th:if="${dateField != null}"]- **Date fields**: Use `From` and `To` suffixes for ranges. Example: `"[(${dateField})]From": "2023-01-01", "[(${dateField})]To": "2023-12-31"`[/]
[# th:if="${intField != null}"]- **Integer fields**: Use exact match or `From`/`To` for ranges. Example: `"[(${intField})]": 42` or `"[(${intField})]From": 10, "[(${intField})]To": 50`[/]
[# th:if="${floatField != null}"]- **Float fields**: Use exact match or `From`/`To` for ranges. Example: `"[(${floatField})]": 0.95` or `"[(${floatField})]From": 0.8, "[(${floatField})]To": 1.0`[/]
[# th:if="${dateField != null}"]- **Date fields**: Use exact match, or `From`/`To` suffixes for ranges. You can also supply an array for exact match - the values will be combined with logical OR, and may include `null`.
Examples: `"[(${dateField})]": "2023-01-15"`, `"[(${dateField})]From": "2023-01-01", "[(${dateField})]To": "2023-12-31"`, `"[(${dateField})]": ["2023-01-15", "2023-02-20"]`[/]
[# th:if="${intField != null}"]- **Integer fields**: Use exact match or `From`/`To` for ranges. You can also supply an array for exact match - the values will be combined with logical OR, and may include `null`.
Examples: `"[(${intField})]": 42`, `"[(${intField})]From": 10, "[(${intField})]To": 50`, `"[(${intField})]": [42, 43]`[/]
[# th:if="${floatField != null}"]- **Float fields**: Use exact match or `From`/`To` for ranges. You can also supply an array for exact match - the values will be combined with logical OR, and may include `null`.
Examples: `"[(${floatField})]": 0.95`, `"[(${floatField})]From": 0.8, "[(${floatField})]To": 1.0`, `"[(${floatField})]": [0.95, 0.97]`[/]
[# th:if="${booleanField != null}"]- **Boolean fields**: Use `true` or `false`. Example: `"[(${booleanField})]": true`[/]

You can combine multiple filters in a single query. All filters are combined with AND logic.
Expand Down
Loading