Skip to content

Commit

Permalink
feat: add NOf query
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasKellerer committed Apr 27, 2023
1 parent c159ace commit 217704e
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ expr:
single:
nucleotide_mutation
| pangolineage_query
| n_of_query
;

nucleotide_mutation : nucleotide_symbol? position ambigous_nucleotide_symbol?;
Expand All @@ -28,6 +29,10 @@ pangolineage_character: A | B | C | D | E | F | G | H | I | J | K | L | M | N |
pangolineage_number_component: '.' NUMBER NUMBER? NUMBER?;
pangolineage_include_sublineages: DOT? ASTERISK;

n_of_query: '[' n_of_match_exactly? n_of_number_of_matchers '-of:' n_of_exprs ']';
n_of_match_exactly: 'EXACTLY-';
n_of_number_of_matchers: NUMBER+;
n_of_exprs: expr (',' expr)*;

// lexer rules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package org.genspectrum.lapis.model
import VariantQueryBaseListener
import VariantQueryParser.AndContext
import VariantQueryParser.MaybeContext
import VariantQueryParser.N_of_queryContext
import VariantQueryParser.NotContext
import VariantQueryParser.Nucleotide_mutationContext
import VariantQueryParser.OrContext
import VariantQueryParser.Pangolineage_queryContext
import org.antlr.v4.runtime.tree.ParseTreeListener
import org.genspectrum.lapis.silo.And
import org.genspectrum.lapis.silo.Maybe
import org.genspectrum.lapis.silo.NOf
import org.genspectrum.lapis.silo.Not
import org.genspectrum.lapis.silo.NucleotideSymbolEquals
import org.genspectrum.lapis.silo.Or
Expand Down Expand Up @@ -64,4 +66,20 @@ class VariantQueryCustomListener : VariantQueryBaseListener(), ParseTreeListener
val child = expressionStack.removeLast()
expressionStack.addLast(Maybe(child))
}

override fun exitN_of_query(ctx: N_of_queryContext?) {
if (ctx == null) {
return
}

val n = ctx.n_of_number_of_matchers().text.toInt()
val matchExactly = ctx.n_of_match_exactly() != null

val children = mutableListOf<SiloFilterExpression>()
for (i in 1..n) {
children += expressionStack.removeLast()
}

expressionStack.addLast(NOf(n, matchExactly, children.reversed()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ data class Or(val children: List<SiloFilterExpression>) : SiloFilterExpression("
data class Not(val child: SiloFilterExpression) : SiloFilterExpression("Not")

data class Maybe(val child: SiloFilterExpression) : SiloFilterExpression("Maybe")

data class NOf(val numberOfMatchers: Int, val matchExactly: Boolean, val children: List<SiloFilterExpression>) :
SiloFilterExpression("NOf")
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.genspectrum.lapis.model

import org.genspectrum.lapis.silo.And
import org.genspectrum.lapis.silo.Maybe
import org.genspectrum.lapis.silo.NOf
import org.genspectrum.lapis.silo.Not
import org.genspectrum.lapis.silo.NucleotideSymbolEquals
import org.genspectrum.lapis.silo.Or
Expand All @@ -10,6 +11,7 @@ import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class VariantQueryFacadeTest {
private lateinit var underTest: VariantQueryFacade
Expand All @@ -20,7 +22,64 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant query with a single entry then map should return the corresponding SiloQuery`() {
fun `given a complex variant query then map should return the corresponding SiloQuery`() {
val variantQuery = "300G & (400- | 500B) & !600 & MAYBE(700B | 800-) & [3-of: 123A, 234T, 345G] & A.1.2.3*"

val result = underTest.map(variantQuery)

val expectedResult =
And(
listOf(
And(
listOf(
And(
listOf(
And(
listOf(
And(
listOf(
NucleotideSymbolEquals(300, "G"),
Or(
listOf(
NucleotideSymbolEquals(400, "-"),
NucleotideSymbolEquals(500, "B"),
),
),
),
),
Not(NucleotideSymbolEquals(600, "-")),
),
),
Maybe(
Or(
listOf(
NucleotideSymbolEquals(700, "B"),
NucleotideSymbolEquals(800, "-"),
),
),
),
),
),
NOf(
3,
matchExactly = false,
listOf(
NucleotideSymbolEquals(123, "A"),
NucleotideSymbolEquals(234, "T"),
NucleotideSymbolEquals(345, "G"),
),
),
),
),
PangoLineageEquals("pangoLineage", "A.1.2.3", true),
),
)

MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
}

@Test
fun `given a variantQuery with a single entry then map should return the corresponding SiloQuery`() {
val variantQuery = "300G"

val result = underTest.map(variantQuery)
Expand All @@ -30,7 +89,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with an 'And' expression the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with an 'And' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "300G & 400"

val result = underTest.map(variantQuery)
Expand All @@ -45,7 +104,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with two 'And' expression the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with two 'And' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "300G & 400- & 500B"

val result = underTest.map(variantQuery)
Expand All @@ -65,7 +124,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with a 'Not' expression the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with a 'Not' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "!300G"

val result = underTest.map(variantQuery)
Expand All @@ -75,7 +134,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with an 'Or' expression the map should return the corresponding SiloQuery`() {
fun `given a variant variantQuery with an 'Or' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "300G | 400"

val result = underTest.map(variantQuery)
Expand All @@ -90,7 +149,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with an bracket expression the map should return the corresponding SiloQuery`() {
fun `given a variant variantQuery with an bracket expression then map should return the corresponding SiloQuery`() {
val variantQuery = "300C & (400A | 500G)"

val result = underTest.map(variantQuery)
Expand All @@ -110,7 +169,7 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with a 'Maybe' expression the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with a 'Maybe' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "MAYBE(300G)"

val result = underTest.map(variantQuery)
Expand All @@ -120,22 +179,130 @@ class VariantQueryFacadeTest {
}

@Test
fun `given a variant variantQuery with a 'Pangolineage' expression the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with a 'Pangolineage' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "A.1.2.3"

val result = underTest.map(variantQuery)

val expectedResult = PangoLineageEquals("A.1.2.3", false)
val expectedResult = PangoLineageEquals("pangoLineage", "A.1.2.3", false)
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
}

@Test
fun `given a variant variantQuery with a 'Pangolineage' expression including sublineages the map should return the corresponding SiloQuery`() {
fun `given a variantQuery with a 'Pangolineage' expression (including sublineages) then map should return the corresponding SiloQuery`() { // ktlint-disable max-line-length
val variantQuery = "A.1.2.3*"

val result = underTest.map(variantQuery)

val expectedResult = PangoLineageEquals("A.1.2.3", true)
val expectedResult = PangoLineageEquals("pangoLineage", "A.1.2.3", true)
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
}

@Test
fun `given a variantQuery with a 'Nof' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "[3-of: 123A, 234T, 345G]"

val result = underTest.map(variantQuery)

val expectedResult = NOf(
3,
false,
listOf(
NucleotideSymbolEquals(123, "A"),
NucleotideSymbolEquals(234, "T"),
NucleotideSymbolEquals(345, "G"),
),
)
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
}

@Test
fun `given a variantQuery with a exact 'Nof' expression then map should return the corresponding SiloQuery`() {
val variantQuery = "[exactly-3-of: 123A, 234T, 345G]"

val result = underTest.map(variantQuery)

val expectedResult = NOf(
3,
true,
listOf(
NucleotideSymbolEquals(123, "A"),
NucleotideSymbolEquals(234, "T"),
NucleotideSymbolEquals(345, "G"),
),
)
MatcherAssert.assertThat(result, Matchers.equalTo(expectedResult))
}

@Test
fun `given a variantQuery with a 'Insertion' expression then map should throw an error`() {
val variantQuery = "ins_1234:GAG"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Nucleotide insertions are not supported yet."),
)
}

@Test
fun `given a variant variantQuery with a 'AA mutation' expression then map should throw an error`() {
val variantQuery = "S:N501Y"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Amino acid mutations are not supported yet."),
)
}

@Test
fun `given a valid variantQuery with a 'AA insertion' expression then map should throw an error`() {
val variantQuery = "ins_S:501:EPE"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Amino acid insertions are not supported yet."),
)
}

@Test
fun `given a valid variantQuery with a 'nextclade pango lineage' expression then map should throw an error`() {
val variantQuery = "nextcladePangoLineage:BA.5*"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Nextclade pango lineages are not supported yet."),
)
}

@Test
fun `given a valid variantQuery with a 'Nextstrain clade lineage' expression then map should throw an error`() {
val variantQuery = "nextstrainClade:22B"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Nextstrain clade lineages are not supported yet."),
)
}

@Test
fun `given a valid variantQuery with a 'Gisaid clade lineage' expression then map should throw an error`() {
val variantQuery = "gisaid:AB"

val exception = assertThrows<NotImplementedError> { underTest.map(variantQuery) }

MatcherAssert.assertThat(
exception.message,
Matchers.equalTo("Gisaid clade lineages are not supported yet."),
)
}
}
29 changes: 29 additions & 0 deletions lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,35 @@ class SiloQueryTest {
}
""",
),
Arguments.of(
NOf(
2,
true,
listOf(
StringEquals("theColumn", "theValue"),
StringEquals("theOtherColumn", "theOtherValue"),
),
),
"""
{
"type": "NOf",
"numberOfMatchers": 2,
"matchExactly": true,
"children": [
{
"type": "StringEquals",
"column": "theColumn",
"value": "theValue"
},
{
"type": "StringEquals",
"column": "theOtherColumn",
"value": "theOtherValue"
}
]
}
""",
),
)
}
}

0 comments on commit 217704e

Please sign in to comment.