Added span relation deserialization and its examples.
Change-Id: I4dbd07ecf767f3bef9feb13812fe649c891b001c
diff --git a/Errorcodes b/Errorcodes
index 524924a..a481a51 100644
--- a/Errorcodes
+++ b/Errorcodes
@@ -30,6 +30,8 @@
714: "Span references expect a start position and a length parameter"
715: "Attribute type is not supported"
716: "Unknown relation"
+717: "Missing relation node"
+718: "Missing relation term"
740: "Key definition is missing in term or span"
741: "Match relation unknown"
742: "Term group needs operand list"
diff --git a/src/main/java/de/ids_mannheim/korap/KrillQuery.java b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
index b5390e3..45290e8 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
@@ -7,14 +7,25 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.lucene.util.automaton.RegExp;
-
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import de.ids_mannheim.korap.query.SpanWithinQuery;
import de.ids_mannheim.korap.query.QueryBuilder;
-import de.ids_mannheim.korap.query.wrap.*;
+import de.ids_mannheim.korap.query.SpanWithinQuery;
+import de.ids_mannheim.korap.query.wrap.SpanAlterQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanAttributeQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanClassQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanFocusQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanRegexQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanRelationWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanRepetitionQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSegmentQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSequenceQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSimpleQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanSubspanQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanWithAttributeQueryWrapper;
+import de.ids_mannheim.korap.query.wrap.SpanWithinQueryWrapper;
import de.ids_mannheim.korap.response.Notifications;
import de.ids_mannheim.korap.util.QueryException;
@@ -166,6 +177,9 @@
return this.fromJson(jsonN);
};
+ public SpanQueryWrapper fromJson(JsonNode json) throws QueryException {
+ return fromJson(json, null);
+ }
/**
* <p>Deserialize JSON-LD query as a {@link JsonNode} object
@@ -180,7 +194,8 @@
// TODO: Use the shortcuts implemented in the builder
// instead of the wrapper constructors
// TODO: Rename this span context!
- public SpanQueryWrapper fromJson (JsonNode json) throws QueryException {
+ public SpanQueryWrapper fromJson(JsonNode json, String direction)
+ throws QueryException {
int number = 0;
// Only accept @typed objects for the moment
@@ -281,6 +296,12 @@
// Get wrapped token
return this._segFromJson(json.get("wrap"));
+ case "koral:relation":
+ if (!json.has("wrap")) {
+ throw new QueryException(718, "Missing relation term");
+ }
+ return this._termFromJson(json.get("wrap"), direction);
+
case "koral:span":
return this._termFromJson(json);
};
@@ -386,8 +407,14 @@
return this._operationRepetitionFromJson(json, operands);
case "operation:relation":
- throw new QueryException(765,
- "Relations are currently not supported");
+ if (!json.has("relation")) {
+ throw new QueryException(717, "Missing relation node");
+ }
+
+ return _operationRelationFromJson(operands,
+ json.get("relation"));
+ /*throw new QueryException(765,
+ "Relations are currently not supported");*/
case "operation:or": // Deprecated in favor of operation:junction
return this._operationJunctionFromJson(operands);
@@ -401,6 +428,32 @@
throw new QueryException(711, "Unknown group operation");
};
+ private SpanQueryWrapper _operationRelationFromJson(JsonNode operands,
+ JsonNode relation)
+ throws QueryException {
+
+ if (operands.size() < 2) {
+ throw new QueryException(705,
+ "Number of operands is not acceptable");
+ }
+
+ String direction = null;
+ SpanQueryWrapper operand1 = fromJson(operands.get(0));
+ SpanQueryWrapper operand2 = fromJson(operands.get(1));
+
+ if (operand1.isEmpty()) {
+ direction = "<:";
+ }
+ else {// if (operand2.isEmpty()) {
+ direction = ">:";
+ }
+
+ SpanQueryWrapper relationWrapper = fromJson(relation, direction);
+
+ return new SpanRelationWrapper(relationWrapper,
+ operand1, operand2);
+
+ }
// Deserialize operation:junction
private SpanQueryWrapper _operationJunctionFromJson (JsonNode operands)
@@ -861,9 +914,13 @@
throw new QueryException(745, "Token type is not supported");
};
+ private SpanQueryWrapper _termFromJson (JsonNode json)
+ throws QueryException {
+ return _termFromJson(json, null);
+ }
// Deserialize koral:term
- private SpanQueryWrapper _termFromJson (JsonNode json)
+ private SpanQueryWrapper _termFromJson(JsonNode json, String direction)
throws QueryException {
if (!json.has("key") || json.get("key").asText().length() < 1) {
@@ -887,6 +944,10 @@
StringBuilder value = new StringBuilder();
+ if (direction != null) {
+ value.append(direction);
+ }
+
// expect orth? expect lemma?
// s:den | i:den | cnx/l:die | mate/m:mood:ind | cnx/syn:@PREMOD |
// mate/m:number:sg | opennlp/p:ART
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAttributeQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAttributeQueryWrapper.java
index 90e9a2b..968d544 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAttributeQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanAttributeQueryWrapper.java
@@ -15,10 +15,12 @@
public SpanAttributeQueryWrapper (SpanQueryWrapper sqw) {
- if (sqw == null) {
- isNull = true;
- return;
+ if (sqw != null) {
+ isNull = false;
}
+ else
+ return;
+
if (sqw.isEmpty()) {
isEmpty = true;
return;
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRelationWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRelationWrapper.java
new file mode 100644
index 0000000..9748ed3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRelationWrapper.java
@@ -0,0 +1,86 @@
+package de.ids_mannheim.korap.query.wrap;
+
+import java.util.ArrayList;
+
+import org.apache.lucene.search.spans.SpanQuery;
+
+import de.ids_mannheim.korap.query.SpanFocusQuery;
+import de.ids_mannheim.korap.query.SpanSegmentQuery;
+import de.ids_mannheim.korap.util.QueryException;
+
+public class SpanRelationWrapper extends SpanQueryWrapper {
+
+ private SpanQueryWrapper relationQuery;
+ private SpanQueryWrapper subQuery1;
+ private SpanQueryWrapper subQuery2;
+
+ public SpanRelationWrapper (SpanQueryWrapper relationWrapper,
+ SpanQueryWrapper operand1, SpanQueryWrapper operand2) {
+
+ this.relationQuery = relationWrapper;
+ if (relationQuery != null) {
+ this.isNull = false;
+ }
+ else
+ return;
+
+ this.subQuery1 = operand1;
+ this.subQuery2 = operand2;
+ }
+
+ @Override
+ public SpanQuery toQuery() throws QueryException {
+
+ if (this.isNull()) {
+ return null;
+ }
+
+ SpanQuery sq = relationQuery.retrieveNode(this.retrieveNode).toQuery();
+ if (sq == null) return null;
+
+ ArrayList<Byte> classNumbers = new ArrayList<Byte>();
+ classNumbers.add((byte) 1);
+ classNumbers.add((byte) 2);
+
+ SpanQuery subq1, subq2;
+ if (subQuery1.isEmpty) {
+ if (!subQuery2.isEmpty) {
+ // match subquery2
+ subq2 = subQuery2.retrieveNode(this.retrieveNode).toQuery();
+ if (subq2 != null) {
+ return new SpanFocusQuery(
+ new SpanSegmentQuery(sq, subq2, true), classNumbers);
+ }
+ }
+ }
+ else if (subQuery2.isEmpty) {
+ if (!subQuery1.isEmpty) {
+ // match subquery1
+ subq1 = subQuery1.retrieveNode(this.retrieveNode).toQuery();
+ if (subq1 != null) {
+ return new SpanFocusQuery(
+ new SpanSegmentQuery(sq, subq1, true),
+ classNumbers);
+ }
+ }
+ }
+ else{
+ // match both
+ subq1 = subQuery1.retrieveNode(this.retrieveNode).toQuery();
+ if (subq1 != null) {
+ SpanFocusQuery fq = new SpanFocusQuery(new SpanSegmentQuery(sq, subq1,
+ true), (byte) 2);
+ fq.setSorted(false);
+ sq = fq;
+ }
+
+ subq2 = subQuery2.retrieveNode(this.retrieveNode).toQuery();
+ if (subq2 != null){
+ return new SpanFocusQuery(new SpanSegmentQuery(sq, subq2, true),
+ classNumbers);
+ }
+ }
+
+ return new SpanFocusQuery(sq, classNumbers);
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithAttributeQueryWrapper.java b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithAttributeQueryWrapper.java
index ebbc219..b51557d 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithAttributeQueryWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanWithAttributeQueryWrapper.java
@@ -171,6 +171,8 @@
SpanQueryWrapper attrQueryWrapper) throws QueryException {
SpanQuery sq = attrQueryWrapper.toQuery();
if (sq != null) {
+ if (sq instanceof SpanAttributeQuery)
+ return (SpanAttributeQuery) sq;
if (sq instanceof SpanTermQuery) {
return new SpanAttributeQuery((SpanTermQuery) sq,
attrQueryWrapper.isNegative, true);
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
new file mode 100644
index 0000000..9179301
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
@@ -0,0 +1,85 @@
+package de.ids_mannheim.korap.query;
+
+import static de.ids_mannheim.korap.TestSimple.getJSONQuery;
+import static org.junit.Assert.assertEquals;
+
+import org.apache.lucene.search.spans.SpanQuery;
+import org.junit.Test;
+
+import de.ids_mannheim.korap.query.wrap.SpanQueryWrapper;
+import de.ids_mannheim.korap.util.QueryException;
+
+public class TestSpanRelationQueryJSON {
+
+ @Test
+ public void testMatchRelationSource() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/match-source.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "focus([1,2]spanSegment(tokens:>:mate/d:HEAD, <tokens:c:s />))",
+ sq.toString());
+ }
+
+ @Test
+ public void testMatchRelationTarget() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/match-target.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "focus([1,2]spanSegment(tokens:<:mate/d:HEAD, <tokens:c:vp />))",
+ sq.toString());
+ }
+
+ @Test
+ public void testMatchRelationSourceAndTarget() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/match-source-and-target.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "focus([1,2]spanSegment(focus(2: spanSegment(tokens:>:mate/d:HEAD, <tokens:c:s />)), <tokens:c:vp />))",
+ sq.toString());
+ }
+
+ @Test
+ public void testMatchOperandWithProperty() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/operand-with-property.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "focus([1,2]spanSegment(focus(2: spanSegment(tokens:>:mate/d:HEAD, "
+ + "spanElementWithAttribute(<tokens:c:s />, spanAttribute(tokens:@root)))), <tokens:c:vp />))",
+ sq.toString());
+ }
+
+ @Test
+ public void testMatchOperandWithAttribute() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/operand-with-attribute.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals(
+ "focus([1,2]spanSegment(focus(2: spanSegment(tokens:>:mate/d:HEAD, "
+ + "spanElementWithAttribute(<tokens:c:s />, spanAttribute(tokens:type:top)))), <tokens:c:vp />))",
+ sq.toString());
+ }
+
+ @Test
+ public void testMatchRelationOnly() throws QueryException {
+ //
+ String filepath = getClass().getResource(
+ "/queries/relation/relation-only.json").getFile();
+ SpanQueryWrapper sqwi = getJSONQuery(filepath);
+ SpanQuery sq = sqwi.toQuery();
+ assertEquals("focus([1,2]tokens:<:mate/d:HEAD)", sq.toString());
+ }
+}
diff --git a/src/test/resources/queries/relation/match-source-and-target.json b/src/test/resources/queries/relation/match-source-and-target.json
new file mode 100644
index 0000000..860528f
--- /dev/null
+++ b/src/test/resources/queries/relation/match-source-and-target.json
@@ -0,0 +1,28 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "s"
+ },
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "vp"
+ }
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/match-source.json b/src/test/resources/queries/relation/match-source.json
new file mode 100644
index 0000000..3ca5686
--- /dev/null
+++ b/src/test/resources/queries/relation/match-source.json
@@ -0,0 +1,26 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "s"
+ },
+ {
+ "@type": "koral:token"
+ }
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/match-target.json b/src/test/resources/queries/relation/match-target.json
new file mode 100644
index 0000000..7fe4ef0
--- /dev/null
+++ b/src/test/resources/queries/relation/match-target.json
@@ -0,0 +1,24 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {"@type": "koral:token"},
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "vp"
+ }
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/operand-with-attribute.json b/src/test/resources/queries/relation/operand-with-attribute.json
new file mode 100644
index 0000000..570031c
--- /dev/null
+++ b/src/test/resources/queries/relation/operand-with-attribute.json
@@ -0,0 +1,35 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "s",
+ "match": "match:eq",
+ "attr": {
+ "@type": "koral:term",
+ "layer": "type",
+ "key": "top",
+ "match": "match:eq"
+ }
+ },
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "vp"
+ }
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/operand-with-property.json b/src/test/resources/queries/relation/operand-with-property.json
new file mode 100644
index 0000000..407c900
--- /dev/null
+++ b/src/test/resources/queries/relation/operand-with-property.json
@@ -0,0 +1,33 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "s",
+ "match": "match:eq",
+ "attr": {
+ "@type": "koral:term",
+ "root": true
+ }
+ },
+ {
+ "@type": "koral:span",
+ "layer": "c",
+ "key": "vp"
+ }
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/relation-only.json b/src/test/resources/queries/relation/relation-only.json
new file mode 100644
index 0000000..8801184
--- /dev/null
+++ b/src/test/resources/queries/relation/relation-only.json
@@ -0,0 +1,20 @@
+{
+ "query": {
+ "@type": "koral:group",
+ "operation": "operation:relation",
+ "operands": [
+ {"@type": "koral:token"},
+ {"@type": "koral:token"}
+ ],
+ "relation": {
+ "@type": "koral:relation",
+ "wrap": {
+ "@type": "koral:term",
+ "foundry": "mate",
+ "layer": "d",
+ "key": "HEAD"
+ }
+ }
+ },
+ "meta": {}
+}
\ No newline at end of file