Implemented relation queries with regex.

Change-Id: I1dafc835f6034d90a350d9688acdcf54bac28124
diff --git a/Changes b/Changes
index f39c756..a8f1cbf 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.55.9 2017-11-16
+0.55.9 2017-12-19
         - [bugfix] Serialize token identifier correctly for
           new corpora with text siglen (diewald)
         - [bugfix] Extend bytebuffer for relation payloads (diewald)
@@ -6,6 +6,8 @@
           repositioning can result in exceeding the string (diewald)
         - [bugfix] Set correct start position of token-token-relations
           in snippet generation (diewald)
+        - [bugfix] Token cannot contain another token or element (margaretha)
+        - [feature] Enabled searching relation query using regex (margaretha)
 
 0.55.8 2017-09-05
         - [feature] Retrieve and display pagebreaks (diewald)
diff --git a/src/main/java/de/ids_mannheim/korap/KrillQuery.java b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
index 4f5901f..8e30b7a 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillQuery.java
@@ -5,6 +5,7 @@
 import java.util.List;
 import java.util.Iterator;
 
+import org.apache.lucene.search.RegexpQuery;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -13,6 +14,7 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.QueryBuilder;
 import de.ids_mannheim.korap.query.SpanWithinQuery;
 import de.ids_mannheim.korap.query.wrap.SpanAlterQueryWrapper;
@@ -323,7 +325,7 @@
                     return this._termFromJson(json);
 
                 // This is an ugly hack
-                return this._termFromJson(json.get("wrap"), "<>:");
+                return this._termFromJson(json.get("wrap"), true);
         };
 
         // Unknown query type
@@ -566,9 +568,12 @@
         SpanQueryWrapper operand1 = this._fromKoral(operands.get(0), true);
         SpanQueryWrapper operand2 = this._fromKoral(operands.get(1), true);
 
-        String direction = ">:";
+        RelationDirection direction;
         if (operand1.isEmpty() && !operand2.isEmpty()) {
-            direction = "<:";
+            direction = RelationDirection.LEFT; // "<:";
+        }
+        else{
+            direction = RelationDirection.RIGHT; // ">:"
         }
 
         if (!relation.has("@type")){
@@ -576,15 +581,20 @@
                     "JSON-LD group has no @type attribute");
         }
         
-        SpanRelationWrapper spanRelationWrapper;
         if (relation.get("@type").asText().equals("koral:relation")) {
+            SpanRelationWrapper spanRelationWrapper;
+            SpanQueryWrapper relationTermWrapper;
             if (!relation.has("wrap")) {
-                throw new QueryException(718, "Missing relation term");
+                throw new QueryException(718, "Missing relation term.");
+            }
+            else{
+                relationTermWrapper =
+                        _termFromJson(relation.get("wrap"), false, direction);
+                spanRelationWrapper = new SpanRelationWrapper(relationTermWrapper, operand1, operand2);
             }
             
-            SpanQueryWrapper relationWrapper =
-                    _termFromJson(relation.get("wrap"), direction);
-            return new SpanRelationWrapper(relationWrapper, operand1, operand2);
+            spanRelationWrapper.setDirection(direction);    
+            return spanRelationWrapper;
         }
         else {
             throw new QueryException(713, "Query type is not supported");
@@ -999,13 +1009,8 @@
         return sseqqw;
     };
 
-    private SpanQueryWrapper _segFromJson (JsonNode json)
-            throws QueryException {
-        return _segFromJson(json, null);
-    }
-    
     // Deserialize koral:token
-    private SpanQueryWrapper _segFromJson (JsonNode json, String direction)
+    private SpanQueryWrapper _segFromJson (JsonNode json)
             throws QueryException {
 
         if (!json.has("@type"))
@@ -1038,7 +1043,7 @@
                 //                return this.seg().without(ssqw);
                 //
                 //            case "match:eq":
-                return this._termFromJson(json, direction);
+                return this._termFromJson(json);
             //            };
             //
             //            throw new QueryException(741, "Match relation unknown");
@@ -1095,13 +1100,16 @@
 
     private SpanQueryWrapper _termFromJson (JsonNode json)
             throws QueryException {
-        return this._termFromJson(json, null);
+        return this._termFromJson(json, false, null);
     }
-
+    private SpanQueryWrapper _termFromJson (JsonNode json, boolean isSpan)
+            throws QueryException {
+        return this._termFromJson(json, isSpan, null);
+    }
 
     // Deserialize koral:term
     // TODO: Not optimal as it does not respect non-term
-    private SpanQueryWrapper _termFromJson (JsonNode json, String direction)
+    private SpanQueryWrapper _termFromJson (JsonNode json, boolean isSpan, RelationDirection direction)
             throws QueryException {
 
         if (!json.has("@type")) {
@@ -1124,10 +1132,9 @@
             }
         };
         
-        // Ugly direction hack
-        if (direction != null && direction.equals("<>:")) {
+        // Empty koral:span hack
+        if (isSpan) {
             isTerm = false;
-            direction = null;
         };
 
         // <legacy>
@@ -1154,7 +1161,7 @@
         StringBuilder value = new StringBuilder();
 
         if (direction != null)
-            value.append(direction);
+            value.append(direction.value());
 
         // expect orth? expect lemma? 
         // s:den | i:den | cnx/l:die | mate/m:mood:ind | cnx/syn:@PREMOD |
diff --git a/src/main/java/de/ids_mannheim/korap/constants/RelationDirection.java b/src/main/java/de/ids_mannheim/korap/constants/RelationDirection.java
new file mode 100644
index 0000000..e20055d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/constants/RelationDirection.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.constants;
+
+public enum RelationDirection {
+    LEFT("<:"), RIGHT(">:");
+
+    private String value;
+
+    RelationDirection (String value) {
+        this.value = value;
+    }
+
+    public String value () {
+        return value;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/SpanRelationMatchQuery.java b/src/main/java/de/ids_mannheim/korap/query/SpanRelationMatchQuery.java
index de77ef4..866dbc7 100644
--- a/src/main/java/de/ids_mannheim/korap/query/SpanRelationMatchQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/query/SpanRelationMatchQuery.java
@@ -10,6 +10,7 @@
 import org.apache.lucene.search.spans.Spans;
 import org.apache.lucene.util.Bits;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.spans.FocusSpans;
 
 /**
@@ -81,7 +82,7 @@
         SpanFocusQuery sq = null;
         SpanFocusQuery sq2 = null;
         // match source and then target
-        if (relationQuery.getDirection() == 0) {
+        if (relationQuery.getDirection().equals(RelationDirection.RIGHT)) {
             sq = new SpanFocusQuery(
                     new SpanSegmentQuery(relationQuery, operandQuery, true),
                     relation.getTempTargetNum());
diff --git a/src/main/java/de/ids_mannheim/korap/query/SpanRelationQuery.java b/src/main/java/de/ids_mannheim/korap/query/SpanRelationQuery.java
index ea71d63..fe9d3b3 100644
--- a/src/main/java/de/ids_mannheim/korap/query/SpanRelationQuery.java
+++ b/src/main/java/de/ids_mannheim/korap/query/SpanRelationQuery.java
@@ -9,11 +9,11 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermContext;
 import org.apache.lucene.search.spans.SpanQuery;
-import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.search.spans.Spans;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.ToStringUtils;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.spans.RelationSpans;
 
 /**
@@ -54,7 +54,7 @@
  */
 public class SpanRelationQuery extends SimpleSpanQuery {
 
-    private int direction = 0; // >
+    private RelationDirection direction;
     private byte tempSourceNum = 1;
     private byte tempTargetNum = 2;
     private byte sourceClass;
@@ -75,29 +75,32 @@
      *            payloads are to be collected, otherwise
      *            <code>false</code>.
      */
-    public SpanRelationQuery (SpanQuery firstClause, boolean collectPayloads) {
+    public SpanRelationQuery (SpanQuery firstClause, boolean collectPayloads, 
+            RelationDirection direction) {
         super(firstClause, collectPayloads);
-        SpanTermQuery st = (SpanTermQuery) firstClause;
-        String direction = st.getTerm().text().substring(0, 1);
-        if (direction.equals("<")) {
-            this.direction = 1;
-        }
+        this.direction = direction;
+//        SpanTermQuery st = (SpanTermQuery) firstClause;
+//        String direction = st.getTerm().text().substring(0, 1);
+//        if (direction.equals("<")) {
+//            this.direction = 1;
+//        }
     }
 
 
-    public SpanRelationQuery (SpanQuery firstClause, List<Byte> classNumbers,
-                              boolean collectPayloads) {
-        this(firstClause, collectPayloads);
-        this.tempClassNumbers = classNumbers;
-        this.tempSourceNum = classNumbers.get(0);
-        this.tempTargetNum = classNumbers.get(1);
-    }
+//    public SpanRelationQuery (SpanQuery firstClause, List<Byte> classNumbers,
+//                              boolean collectPayloads) {
+//        this(firstClause, collectPayloads);
+//        this.tempClassNumbers = classNumbers;
+//        this.tempSourceNum = classNumbers.get(0);
+//        this.tempTargetNum = classNumbers.get(1);
+//    }
 
 
     @Override
     public SimpleSpanQuery clone () {
         SimpleSpanQuery sq = new SpanRelationQuery(
-                (SpanQuery) this.firstClause.clone(), this.collectPayloads);
+                (SpanQuery) this.firstClause.clone(), this.collectPayloads, 
+                this.direction);
         return sq;
     }
 
@@ -136,12 +139,12 @@
     }
 
 
-    public int getDirection () {
+    public RelationDirection getDirection () {
         return direction;
     }
 
 
-    public void setDirection (int direction) {
+    public void setDirection (RelationDirection direction) {
         this.direction = direction;
     }
 
diff --git a/src/main/java/de/ids_mannheim/korap/query/spans/FocusSpans.java b/src/main/java/de/ids_mannheim/korap/query/spans/FocusSpans.java
index 51bf64f..c5e9487 100644
--- a/src/main/java/de/ids_mannheim/korap/query/spans/FocusSpans.java
+++ b/src/main/java/de/ids_mannheim/korap/query/spans/FocusSpans.java
@@ -14,6 +14,7 @@
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.util.Bits;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanFocusQuery;
 
 
@@ -157,10 +158,10 @@
 
         if (firstSpans instanceof RelationSpans && classNumbers.size() == 1) {
             RelationSpans relationSpans = (RelationSpans) firstSpans;
-            int direction = relationSpans.getDirection();
+            RelationDirection direction = relationSpans.getDirection();
 
             if (classNumbers.get(0) == relationSpans.getTempSourceNum()) {
-                if (direction == 0) {
+                if (direction.equals(RelationDirection.RIGHT)) {
                     setSpanId(relationSpans.getLeftId());
                 }
                 else {
@@ -168,7 +169,7 @@
                 }
             }
             else if (classNumbers.get(0) == relationSpans.getTempTargetNum()) {
-                if (direction == 0) {
+                if (direction.equals(RelationDirection.RIGHT)) {
                     setSpanId(relationSpans.getRightId());
                 }
                 else {
diff --git a/src/main/java/de/ids_mannheim/korap/query/spans/RelationSpans.java b/src/main/java/de/ids_mannheim/korap/query/spans/RelationSpans.java
index ca467fc..a10e770 100644
--- a/src/main/java/de/ids_mannheim/korap/query/spans/RelationSpans.java
+++ b/src/main/java/de/ids_mannheim/korap/query/spans/RelationSpans.java
@@ -10,11 +10,13 @@
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermContext;
+import org.apache.lucene.search.spans.Spans;
 import org.apache.lucene.search.spans.TermSpans;
 import org.apache.lucene.util.Bits;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanRelationQuery;
 
 /**
@@ -52,8 +54,8 @@
 public class RelationSpans extends RelationBaseSpans {
 
     private int currentDoc, currentPosition;
-    private int direction;
-    private TermSpans relationTermSpan;
+    private RelationDirection direction;
+    private Spans relationTermSpan;
 
     protected Logger logger = LoggerFactory.getLogger(RelationSpans.class);
     private List<CandidateSpan> candidateList;
@@ -96,7 +98,7 @@
         targetClass = relationSpanQuery.getTargetClass();
 
         candidateList = new ArrayList<>();
-        relationTermSpan = (TermSpans) firstSpans;
+        relationTermSpan = firstSpans;
         hasMoreSpans = relationTermSpan.next();
     }
 
@@ -138,8 +140,10 @@
             }
             else {
                 setCandidateList();
-                currentDoc = relationTermSpan.doc();
-                currentPosition = relationTermSpan.start();
+                if(hasMoreSpans){
+                    currentDoc = relationTermSpan.doc();
+                    currentPosition = relationTermSpan.start();
+                }
             }
         }
         return false;
@@ -155,6 +159,7 @@
      * @throws IOException
      */
     private void setCandidateList () throws IOException {
+        logger.debug("hasMoreSpans "+hasMoreSpans+" "+relationTermSpan.doc());
         while (hasMoreSpans && relationTermSpan.doc() == currentDoc
                 && relationTermSpan.start() == currentPosition) {
 
@@ -257,7 +262,7 @@
         if (relationTermSpan.isPayloadAvailable()) {
             payload.addAll(relationTermSpan.getPayload());
         }
-        if (direction == 0) {
+        if (direction.equals(RelationDirection.RIGHT)) {
             payload.add(createClassPayload(cs.getLeftStart(), cs.getLeftEnd(),
                     tempSourceNum, false));
             payload.add(createClassPayload(cs.getRightStart(), cs.getRightEnd(),
@@ -393,12 +398,12 @@
     }
 
 
-    public int getDirection () {
+    public RelationDirection getDirection () {
         return direction;
     }
 
 
-    public void setDirection (int direction) {
+    public void setDirection (RelationDirection direction) {
         this.direction = direction;
     }
 
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
index d7b0f6d..fe980c9 100644
--- a/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRelationWrapper.java
+++ b/src/main/java/de/ids_mannheim/korap/query/wrap/SpanRelationWrapper.java
@@ -1,8 +1,10 @@
 package de.ids_mannheim.korap.query.wrap;
 
+import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanFocusQuery;
 import de.ids_mannheim.korap.query.SpanRelationMatchQuery;
 import de.ids_mannheim.korap.query.SpanRelationQuery;
@@ -13,7 +15,7 @@
     private SpanQueryWrapper relationQuery;
     private SpanQueryWrapper subQuery1;
     private SpanQueryWrapper subQuery2;
-
+    private RelationDirection direction;
 
     public SpanRelationWrapper (SpanQueryWrapper relationWrapper,
                                 SpanQueryWrapper operand1,
@@ -43,12 +45,15 @@
             return null;
         }
 
-        SpanTermQuery relationTermQuery = (SpanTermQuery) relationQuery
+        SpanQuery relationTermQuery = relationQuery
                 .retrieveNode(this.retrieveNode).toFragmentQuery();
+        
+//        SpanTermQuery relationTermQuery = (SpanTermQuery) relationQuery
+//                .retrieveNode(this.retrieveNode).toFragmentQuery();
         if (relationTermQuery == null)
             return null;
 
-        SpanRelationQuery rq = new SpanRelationQuery(relationTermQuery, true);
+        SpanRelationQuery rq = new SpanRelationQuery(relationTermQuery, true, direction);
         SpanQuery subq1, subq2;
 
         if (subQuery1.isEmpty) {
@@ -94,4 +99,11 @@
         fq.setSorted(false);
         return fq;
     }
+    
+    public void setDirection (RelationDirection direction) {
+        this.direction = direction;
+    }
+    public RelationDirection getDirection () {
+        return direction;
+    }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
index 9228c78..1ea89d8 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestFocusIndex.java
@@ -10,6 +10,7 @@
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanFocusQuery;
 import de.ids_mannheim.korap.query.SpanNextQuery;
 import de.ids_mannheim.korap.query.SpanRelationQuery;
@@ -36,7 +37,7 @@
         ki.commit();
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", ">:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.RIGHT);
         sq.setSourceClass((byte) 1);
 
         SpanFocusQuery sfq = new SpanFocusQuery(sq, (byte) 1);
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
index 177dbc7..b5d3412 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestReferenceIndex.java
@@ -11,6 +11,7 @@
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.DistanceConstraint;
 import de.ids_mannheim.korap.query.SpanClassQuery;
 import de.ids_mannheim.korap.query.SpanDistanceQuery;
@@ -44,7 +45,8 @@
         SpanFocusQuery sfq1 = new SpanFocusQuery(snq1, (byte) 2);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), true, 
+                RelationDirection.LEFT);
 
         SpanElementQuery seq2 = new SpanElementQuery("base", "pp");
         SpanClassQuery scq3 = new SpanClassQuery(seq2, (byte) 3);
@@ -126,7 +128,7 @@
         // nn -> prp
         SpanRelationQuery srq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("tokens", ">:stanford/d:tag")),
-                true);
+                true, RelationDirection.RIGHT);
         SpanRelationMatchQuery rq = new SpanRelationMatchQuery(srq, sfq2, scq2,
                 true);
 
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
index 505d2bf..4d2ba30 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestRelationIndex.java
@@ -5,11 +5,14 @@
 import java.io.IOException;
 
 import org.apache.lucene.index.Term;
+import org.apache.lucene.sandbox.queries.regex.RegexQuery;
+import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.junit.Test;
 
 import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.constants.RelationDirection;
 import de.ids_mannheim.korap.query.SpanAttributeQuery;
 import de.ids_mannheim.korap.query.SpanClassQuery;
 import de.ids_mannheim.korap.query.SpanElementQuery;
@@ -19,6 +22,7 @@
 import de.ids_mannheim.korap.query.SpanSegmentQuery;
 import de.ids_mannheim.korap.query.SpanTermWithIdQuery;
 import de.ids_mannheim.korap.query.SpanWithAttributeQuery;
+import de.ids_mannheim.korap.response.Match;
 import de.ids_mannheim.korap.response.Result;
 
 /*
@@ -114,6 +118,30 @@
                         + ":xip/syntax-dep_rel$<b>33<i>6<i>7<i>6<i>9<s>2<s>1<s>0]");
         return fd;
     }
+    
+    public static FieldDocument createFieldDoc3 () {
+        FieldDocument fd = new FieldDocument();
+        fd.addString("ID", "doc-1");
+        fd.addTV("base", "ceccecdeed",
+                "[(0-1)s:c$<s>2|<>:p$<b>64<i>0<i>3<i>3<b>0<s>1|_0$<i>0<i>1|"
+                        + ">:xip/dep_rel$<b>35<i>0<i>1<i>1<i>2<i>3<i>6<i>9<s>1<s>1<s>2|"
+                        + ">:xip/dep_rel$<b>33<i>1<i>2<i>6<i>9<s>2<s>1<s>0|"
+                        + "@:func=subj$<b>18<s>2]"
+                        + "[(1-2)s:e|_1$<i>1<i>2|<>:p$<b>64<i>1<i>3<i>3<b>0<s>1]"
+                        + "[(2-3)s:c|_2$<i>2<i>3]"
+                        + "[(3-4)s:c|s:b|_3$<i>3<i>4]"
+                        + "[(4-5)s:e|s:d|_4$<i>4<i>5]"
+                        + "[(5-6)s:c|_5$<i>5<i>6]"
+                        + "[(6-7)s:d$<s>2|<>:p$<b>64<i>6<i>9<i>9<b>0<s>1|_6$<i>6<i>7|"
+                        + ">:xip/dep_rel$<b>34<i>3<i>4<i>9<i>9<s>1<s>1<s>0|"
+                        + "<:xip/dep_rel$<b>35<i>3<i>4<i>4<i>5<i>9<i>1<i>3<s>1<s>1<s>2|"
+                        + "<:xip/dep_rel$<b>34<i>5<i>6<i>9<i>1<s>1<s>2<s>0|"
+                        + "@:func=obj$<b>18<s>2]" + "[(7-8)s:e|_7$<i>7<i>8]"
+                        + "[(8-9)s:e|s:b|_8$<i>8<i>9]"
+                        + "[(9-10)s:d$<s>1|_9$<i>9<i>10|<"
+                        + ":xip/dep_rel$<b>33<i>6<i>7<i>6<i>9<s>2<s>1<s>0]");
+        return fd;
+    }
 
 
     public static FieldDocument createFieldDoc2 () {
@@ -215,6 +243,29 @@
 //        }
 //
 //    }
+    @Test
+    public void testRelationWithRegex () throws IOException {
+        ki.addDoc(createFieldDoc0());
+        ki.addDoc(createFieldDoc3());
+        ki.commit();
+
+        SpanQuery sq; 
+        sq = new SpanRelationQuery(
+                new SpanMultiTermQueryWrapper<RegexQuery>(
+                        new RegexQuery(new Term("base", ">:xip/.*"))),
+                true, RelationDirection.RIGHT);
+        kr = ki.search(sq, (short) 10);
+        
+        assertEquals((long) 7, kr.getTotalResults());
+        
+        sq = new SpanRelationQuery(
+                new SpanMultiTermQueryWrapper<RegexQuery>(
+                        new RegexQuery(new Term("base", "<:xip/.*"))),
+                true, RelationDirection.LEFT);
+        kr = ki.search(sq, (short) 10);
+        
+        assertEquals((long) 7, kr.getTotalResults());
+    }
     
     /**
      * Relations: token to token, token to span, span to span
@@ -227,7 +278,7 @@
 
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", ">:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.RIGHT);
         kr = ki.search(sq, (short) 10);
 
         assertEquals((long) 7, kr.getTotalResults());
@@ -269,7 +320,7 @@
 
         SpanRelationQuery sq = new SpanRelationQuery(
                 new SpanTermQuery(new Term("base", "<:xip/syntax-dep_rel")),
-                true);
+                true, RelationDirection.LEFT);
         kr = ki.search(sq, (short) 10);
 
         assertEquals((long) 7, kr.getTotalResults());
@@ -306,7 +357,8 @@
 
         // child-of relations
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         kr = ki.search(srq, (short) 20);
 
         assertEquals((long) 13, kr.getTotalResults());
@@ -334,7 +386,8 @@
         ki.commit();
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
 
         SpanFocusQuery fq = new SpanFocusQuery(srq, srq.getTempClassNumbers());
         fq.setMatchTemporaryClass(true);
@@ -382,7 +435,8 @@
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
 
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
         SpanFocusQuery rv = new SpanFocusQuery(rm, (byte) 1);
@@ -406,7 +460,8 @@
 
         // return all parents that are NP
         srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), 
+                true, RelationDirection.LEFT);
         rm = new SpanRelationMatchQuery(srq, scq1, true);
         rv = new SpanFocusQuery(rm, (byte) 1);
         kr = ki.search(rv, (short) 10);
@@ -439,7 +494,8 @@
 
         // target of a dependency relation
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:dep")), true);
+                new SpanTermQuery(new Term("base", "<:dep")), true, 
+                RelationDirection.LEFT);
         kr = ki.search(srq, (short) 10);
         assertEquals((long) 6, kr.getTotalResults());
 
@@ -482,7 +538,8 @@
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), true, 
+                RelationDirection.RIGHT);
         srq.setTargetClass((byte) 2);
 
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
@@ -528,7 +585,8 @@
         SpanTermWithIdQuery tq = new SpanTermWithIdQuery(
                 new Term("base", "pos:NN"), true);
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:dep")), true);
+                new SpanTermQuery(new Term("base", "<:dep")), true, 
+                RelationDirection.LEFT);
         srq.setSourceClass((byte) 1);
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, tq, true);
         SpanQuery rv = new SpanFocusQuery(rm, (byte) 1);
@@ -544,7 +602,8 @@
 
         // return target of dep relations from pos:NN
         srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:dep")), true);
+                new SpanTermQuery(new Term("base", ">:dep")), true, 
+                RelationDirection.RIGHT);
         srq.setTargetClass((byte) 1);
         rm = new SpanRelationMatchQuery(srq, tq, true);
         rv = new SpanFocusQuery(rm, (byte) 1);
@@ -578,7 +637,8 @@
         SpanElementQuery seq1 = new SpanElementQuery("base", "np");
         SpanClassQuery scq1 = new SpanClassQuery(seq1, (byte) 1);
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", "<:child-of")), true);
+                new SpanTermQuery(new Term("base", "<:child-of")), 
+                true, RelationDirection.LEFT);
         srq.setSourceClass((byte) 2);
         SpanRelationMatchQuery rm = new SpanRelationMatchQuery(srq, scq1, true);
         SpanFocusQuery rv = new SpanFocusQuery(rm, (byte) 2);
@@ -620,7 +680,8 @@
         SpanClassQuery scq2 = new SpanClassQuery(seq, (byte) 2);
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
 
@@ -661,7 +722,8 @@
         assertEquals(6, kr.getMatch(1).getEndPos());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
 
@@ -752,7 +814,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
@@ -834,7 +897,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
@@ -908,7 +972,8 @@
         assertEquals((long) 3, kr.getTotalResults());
 
         SpanRelationQuery srq = new SpanRelationQuery(
-                new SpanTermQuery(new Term("base", ">:child-of")), true);
+                new SpanTermQuery(new Term("base", ">:child-of")), 
+                true, RelationDirection.RIGHT);
         srq.setSourceClass((byte) 1);
         srq.setTargetClass((byte) 2);
         kr = ki.search(srq, (short) 20);
diff --git a/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
index 2423ae6..67acf96 100644
--- a/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/query/TestSpanRelationQueryJSON.java
@@ -105,12 +105,13 @@
                 "focus(#[1,2]spanSegment(spanRelation(tokens:>:mate/d:HEAD), <tokens:c:s />))",
                 sq.toString());
     }
-    
+
     @Test
     public void testMatchRelationSourceToken () throws QueryException {
         //
         String filepath = getClass()
-                .getResource("/queries/relation/match-source-token.json").getFile();
+                .getResource("/queries/relation/match-source-token.json")
+                .getFile();
         SpanQueryWrapper sqwi = getJSONQuery(filepath);
         SpanQuery sq = sqwi.toQuery();
         assertEquals(
@@ -118,7 +119,7 @@
                 sq.toString());
     }
 
-    
+
     @Test
     public void testMatchRelationTarget () throws QueryException {
         //
@@ -240,8 +241,21 @@
                 sq.toString());
     }
 
+    @Test
+    public void testTypedRelationWithAnnotationRegex () throws QueryException {
+        String filepath = getClass()
+                .getResource(
+                        "/queries/relation/typed-relation-with-key-regex.json")
+                .getFile();
+        SpanQueryWrapper sqwi = getJSONQuery(filepath);
+        SpanQuery sq = sqwi.toQuery();
+        assertEquals("focus(#[1,2]spanSegment(<tokens:corenlp/c:NP />, "
+                + "focus(#2: spanSegment(spanRelation(SpanMultiTermQueryWrapper(tokens:/>:malt/d:.*/)), "
+                + "<tokens:corenlp/c:VP />))))", sq.toString());
+    }
 
     // EM: should relation term allow empty key?
+    // EM: or should this be interpreted as any key, i.e. [func=/.*/]
     @Test
     public void testTypedRelationWithoutKey () throws QueryException {
 
@@ -297,11 +311,11 @@
         SpanQuery sq = sqwi.toQuery();
         assertEquals(
                 "focus(#[1,2]spanSegment(tokens:tt/p:VVINF, "
-                + "focus(#2: spanSegment("
-                + "spanRelation(tokens:>:malt/d:KONJ), tokens:tt/p:KOUI))))",
+                        + "focus(#2: spanSegment("
+                        + "spanRelation(tokens:>:malt/d:KONJ), tokens:tt/p:KOUI))))",
                 sq.toString());
 
     }
-    
+
     // EM: handle empty koral:span
 }
diff --git a/src/test/resources/queries/relation/indirect-typed-relation-without-key.json b/src/test/resources/queries/relation/indirect-typed-relation-without-key.json
new file mode 100644
index 0000000..f641b8f
--- /dev/null
+++ b/src/test/resources/queries/relation/indirect-typed-relation-without-key.json
@@ -0,0 +1,41 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt"
+            },
+            "@type": "koral:relation",
+            "boundary": {
+                "min": 0,
+                "@type": "koral:boundary"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/indirect-typed-relation.json b/src/test/resources/queries/relation/indirect-typed-relation.json
new file mode 100644
index 0000000..17a92ed
--- /dev/null
+++ b/src/test/resources/queries/relation/indirect-typed-relation.json
@@ -0,0 +1,43 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt",
+                "match": "match:eq",
+                "key": "DET"
+            },
+            "@type": "koral:relation",
+            "boundary": {
+                "min": 0,
+                "@type": "koral:boundary"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/queries/relation/typed-relation-with-key-regex.json b/src/test/resources/queries/relation/typed-relation-with-key-regex.json
new file mode 100644
index 0000000..bee6c78
--- /dev/null
+++ b/src/test/resources/queries/relation/typed-relation-with-key-regex.json
@@ -0,0 +1,40 @@
+{
+    "@context": "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+    "query": {
+        "operation": "operation:relation",
+        "operands": [
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "VP"
+                },
+                "@type": "koral:span"
+            },
+            {
+                "wrap": {
+                    "@type": "koral:term",
+                    "layer": "c",
+                    "foundry": "corenlp",
+                    "match": "match:eq",
+                    "key": "NP"
+                },
+                "@type": "koral:span"
+            }
+        ],
+        "@type": "koral:group",
+        "relType": {
+        	"@type": "koral:relation",
+            "wrap": {
+                "@type": "koral:term",
+                "layer": "d",
+                "foundry": "malt",
+                "match": "match:eq",
+                "type": "type:regex",
+                "key": ".*"
+            }
+        }
+    }
+}
\ No newline at end of file