Support for 'or' and nested groups in collections
diff --git a/CHANGES b/CHANGES
index ee0a2ba..d7f2e12 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+0.30.7 2014-04-09
+        - Moved JSON interpretation from BooleanFilter to KorapFilter (diewald)
+	- Added 'or' and group nesting to KorapFilter (diewald)
+
 0.30.6 2014-03-20
         - Refactoring and cleaning of ElementSpans (diewald)
 	- [bugfix] Stabilizing KorapMatch (diewald)
diff --git a/pom.xml b/pom.xml
index 789c805..cd866c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 -->
   <groupId>KorAP-modules</groupId>
   <artifactId>KorAP-lucene-index</artifactId>
-  <version>0.30.6</version>
+  <version>0.30.7</version>
   <packaging>jar</packaging>
 
   <name>KorAP-lucene-index</name>
diff --git a/src/main/java/de/ids_mannheim/korap/KorapCollection.java b/src/main/java/de/ids_mannheim/korap/KorapCollection.java
index e92c5e4..29545e7 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapCollection.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapCollection.java
@@ -80,11 +80,11 @@
 
 	if (type.equals("korap:meta-filter")) {
 	    log.trace("Add Filter");
-	    this.filter(new BooleanFilter(json.get("@value")));
+	    this.filter(new KorapFilter(json.get("@value")));
 	}
 	else if (type.equals("korap:meta-extend")) {
 	    log.trace("Add Extend");
-	    this.extend(new BooleanFilter(json.get("@value")));
+	    this.extend(new KorapFilter(json.get("@value")));
 	};
     };
 
@@ -118,6 +118,11 @@
 	return this;
     };
 
+    public KorapCollection filter (KorapFilter filter) {
+	return this.filter(filter.toBooleanFilter());
+    };
+
+
     public KorapCollection extend (BooleanFilter filter) {
 	log.trace("Added extension: {}", filter.toString());
 	this.filter.add(
@@ -130,6 +135,11 @@
 	return this;
     };
 
+    public KorapCollection extend (KorapFilter filter) {
+	return this.extend(filter.toBooleanFilter());
+    };
+
+    
     public ArrayList<FilterOperation> getFilters () {
 	return this.filter;
     };
diff --git a/src/main/java/de/ids_mannheim/korap/KorapFilter.java b/src/main/java/de/ids_mannheim/korap/KorapFilter.java
index 33ebc73..c4541d6 100644
--- a/src/main/java/de/ids_mannheim/korap/KorapFilter.java
+++ b/src/main/java/de/ids_mannheim/korap/KorapFilter.java
@@ -2,6 +2,12 @@
 
 import de.ids_mannheim.korap.filter.BooleanFilter;
 import de.ids_mannheim.korap.filter.RegexFilter;
+import de.ids_mannheim.korap.util.QueryException;
+
+import org.apache.lucene.search.Query;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonNode;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,8 +44,145 @@
     private BooleanFilter filter;
 
     // Logger
-    private final static Logger jlog = LoggerFactory.getLogger(KorapFilter.class);
+    private final static Logger log = LoggerFactory.getLogger(KorapFilter.class);
 
+    // This advices the java compiler to ignore all loggings
+    public static final boolean DEBUG = false;
+    
+    public KorapFilter () {
+	filter = new BooleanFilter();
+    };
+
+
+    public KorapFilter (JsonNode json) throws QueryException {
+	filter = this.fromJSON(json, "tokens");
+    };
+
+
+    /*
+    */
+	/*
+	String type = json.get("@type").asText();
+	String field = _getField(json);
+
+	if (type.equals("korap:term")) {
+	    this.fromJSON(json, field);
+	}
+	else if (type.equals("korap:group")) {
+	    // TODO: relation
+	    for (JsonNode operand : json.get("operands")) {
+		this.fromJSON(operand, field);
+	    };
+	};
+	*/
+    //    };
+    
+    private BooleanFilter fromJSON (JsonNode json, String field) throws QueryException {
+	BooleanFilter filter = new BooleanFilter();
+	
+	String type = json.get("@type").asText();
+
+	if (DEBUG)
+	    log.trace("@type: " + type);
+
+	if (json.has("@field"))
+	    field = _getField(json);
+
+	if (type.equals("korap:term")) {
+	    if (field != null && json.has("@value"))
+		filter.and(field, json.get("@value").asText());
+	    return filter;
+	}
+	else if (type.equals("korap:group")) {
+	    if (!json.has("relation") || !json.has("operands"))
+		return filter;
+
+	    String date, till;
+
+	    if (DEBUG)
+		log.trace("relation: " + json.get("relation").asText());
+
+	    BooleanFilter group = new BooleanFilter();
+	    
+	    switch (json.get("relation").asText())  {
+	    case "between":
+		date = _getDate(json, 0);
+		till = _getDate(json, 1);
+		if (date != null && till != null)
+		    filter.between(date, till);
+		break;
+
+	    case "until":
+		date = _getDate(json, 0);
+		if (date != null)
+		    filter.till(date);
+		break;
+
+	    case "since":
+		date = _getDate(json, 0);
+		if (date != null)
+		    filter.since(date);
+		break;
+
+	    case "equals":
+		date = _getDate(json, 0);
+		if (date != null)
+		    filter.date(date);
+		break;
+
+	    case "and":
+		for (JsonNode operand : json.get("operands")) {
+		    group.and(this.fromJSON(operand, field));
+		};
+		filter.and(group);
+		break;
+
+	    case "or":
+		for (JsonNode operand : json.get("operands")) {
+		    group.or(this.fromJSON(operand, field));
+		};
+		filter.and(group);
+		break;
+
+	    default:
+		throw new QueryException(
+		    json.get("relation").asText() + " is not a supported relation"
+	        );
+	    };
+	}
+	else {
+	    throw new QueryException(type + " is not a supported group");
+	};
+	return filter;
+    };
+    
+
+    private static String  _getField (JsonNode json)  {
+	if (!json.has("@field"))
+	    return (String) null;
+
+	String field = json.get("@field").asText();
+	return field.replaceFirst("korap:field#", "");
+    };
+
+    private static String _getDate (JsonNode json, int index) {
+	if (!json.has("operands"))
+	    return (String) null;
+
+	if (!json.get("operands").has(index))
+	    return (String) null;
+
+	JsonNode date = json.get("operands").get(index);
+	if (!date.get("@type").asText().equals("korap:date"))
+	    return (String) null;
+
+	if (!date.has("@value"))
+	    return (String) null;
+
+	return date.get("@value").asText();
+    };
+
+    
     public BooleanFilter and (String type, String ... terms) {
 	BooleanFilter bf = new BooleanFilter();
 	bf.and(type, terms);
@@ -91,4 +234,16 @@
     public RegexFilter re (String regex) {
 	return new RegexFilter(regex);
     };
+
+    public BooleanFilter toBooleanFilter()  {
+	return this.filter;
+    };
+
+    public Query toQuery () {
+	return this.filter.toQuery();
+    };
+
+    public String toString () {
+	return this.filter.toQuery().toString();
+    };
 };
diff --git a/src/main/java/de/ids_mannheim/korap/filter/BooleanFilter.java b/src/main/java/de/ids_mannheim/korap/filter/BooleanFilter.java
index a447ccd..57392bf 100644
--- a/src/main/java/de/ids_mannheim/korap/filter/BooleanFilter.java
+++ b/src/main/java/de/ids_mannheim/korap/filter/BooleanFilter.java
@@ -15,9 +15,6 @@
 import de.ids_mannheim.korap.filter.RegexFilter;
 import de.ids_mannheim.korap.KorapFilter;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.JsonNode;
-
 import de.ids_mannheim.korap.util.QueryException;
 
 import org.slf4j.Logger;
@@ -50,117 +47,6 @@
 	bool = new BooleanQuery();
     };
 
-    public BooleanFilter (JsonNode json) throws QueryException {
-	bool = new BooleanQuery();
-	this.fromJSON(json, "tokens");
-	/*
-	String type = json.get("@type").asText();
-	String field = _getField(json);
-
-	if (type.equals("korap:term")) {
-	    this.fromJSON(json, field);
-	}
-	else if (type.equals("korap:group")) {
-	    // TODO: relation
-	    for (JsonNode operand : json.get("operands")) {
-		this.fromJSON(operand, field);
-	    };
-	};
-	*/
-    };
-
-
-    private void fromJSON (JsonNode json, String field) throws QueryException {
-	String type = json.get("@type").asText();
-
-	log.trace("@type: " + type);
-
-	if (json.has("@field"))
-	    field = _getField(json);
-
-	if (type.equals("korap:term")) {
-	    if (field != null && json.has("@value"))
-		this.and(field, json.get("@value").asText());
-	    return;
-	}
-	else if (type.equals("korap:group")) {
-	    if (!json.has("relation"))
-		return;
-
-	    String date, till;
-
-	    log.trace("relation: " + json.get("relation").asText());
-
-	    switch (json.get("relation").asText())  {
-
-	    case "between":
-		date = _getDate(json, 0);
-		till = _getDate(json, 1);
-		if (date != null && till != null)
-		    this.between(date, till);
-		break;
-
-	    case "until":
-		date = _getDate(json, 0);
-		if (date != null)
-		    this.till(date);
-		break;
-
-	    case "since":
-		date = _getDate(json, 0);
-		if (date != null)
-		    this.since(date);
-		break;
-
-	    case "equals":
-		date = _getDate(json, 0);
-		if (date != null)
-		    this.date(date);
-		break;
-
-	    case "and":
-		if (!json.has("operands"))
-		    return;
-
-		for (JsonNode operand : json.get("operands")) {
-		    this.fromJSON(operand, field);
-		};
-		break;
-
-	    default:
-		throw new QueryException(json.get("relation").asText() + " is not a supported relation");
-	    };
-	}
-	else {
-	    throw new QueryException(type + " is not a supported group");
-	};
-    };
-
-    private static String  _getField (JsonNode json)  {
-	if (!json.has("@field"))
-	    return (String) null;
-
-	String field = json.get("@field").asText();
-	return field.replaceFirst("korap:field#", "");
-    };
-
-    private static String _getDate (JsonNode json, int index) {
-	if (!json.has("operands"))
-	    return (String) null;
-
-	if (!json.get("operands").has(index))
-	    return (String) null;
-
-	JsonNode date = json.get("operands").get(index);
-	if (!date.get("@type").asText().equals("korap:date"))
-	    return (String) null;
-
-	if (!date.has("@value"))
-	    return (String) null;
-
-	return date.get("@value").asText();
-    };
-
     public BooleanFilter or (String type, String ... terms) {
 	for (String term : terms) {
 	    bool.add(
@@ -180,6 +66,12 @@
     };
 
     public BooleanFilter or (BooleanFilter bf) {
+	if (bf.bool.clauses().size() == 1) {
+	    BooleanClause bc = bf.bool.getClauses()[0];
+	    bc.setOccur(BooleanClause.Occur.SHOULD);
+	    bool.add(bc);
+	    return this;
+	}
 	bool.add(
  	    bf.toQuery(),
 	    BooleanClause.Occur.SHOULD
@@ -211,6 +103,12 @@
     };
 
     public BooleanFilter and (BooleanFilter bf) {
+	if (bf.bool.clauses().size() == 1) {
+	    BooleanClause bc = bf.bool.getClauses()[0];
+	    bc.setOccur(BooleanClause.Occur.MUST);
+	    bool.add(bc);
+	    return this;
+	}
 	bool.add(
  	    bf.toQuery(),
 	    BooleanClause.Occur.MUST
@@ -325,7 +223,6 @@
 	return this;
     };
 
-    
     public Query toQuery () {
 	return this.bool;
     };
diff --git a/src/test/java/de/ids_mannheim/korap/filter/TestKorapCollectionJSON.java b/src/test/java/de/ids_mannheim/korap/filter/TestKorapCollectionJSON.java
index 2d10b6f..bc8a5bb 100644
--- a/src/test/java/de/ids_mannheim/korap/filter/TestKorapCollectionJSON.java
+++ b/src/test/java/de/ids_mannheim/korap/filter/TestKorapCollectionJSON.java
@@ -18,8 +18,8 @@
 	KorapCollection kc = new KorapCollection(metaQuery);
 
 	assertEquals("filter with QueryWrapperFilter(+textClass:wissenschaft)", kc.getFilter(0).toString());
-	assertEquals("filter with QueryWrapperFilter(+pubPlace:Erfurt +author:Hesse)", kc.getFilter(1).toString());
-	assertEquals("extend with QueryWrapperFilter(+pubDate:[20110429 TO 20131231] +textClass:freizeit)", kc.getFilter(2).toString());
+	assertEquals("filter with QueryWrapperFilter(+(+pubPlace:Erfurt +author:Hesse))", kc.getFilter(1).toString());
+	assertEquals("extend with QueryWrapperFilter(+(+pubDate:[20110429 TO 20131231] +textClass:freizeit))", kc.getFilter(2).toString());
 	assertEquals(3, kc.getCount());
     };
 
@@ -29,7 +29,7 @@
 	String metaQuery = getString(getClass().getResource("/queries/metaquery2.jsonld").getFile());
 	KorapCollection kc = new KorapCollection(metaQuery);
 	assertEquals(1,kc.getCount());
-	assertEquals("filter with QueryWrapperFilter(+author:Hesse +pubDate:[0 TO 20131205])",kc.getFilter(0).toString());
+	assertEquals("filter with QueryWrapperFilter(+(+author:Hesse +pubDate:[0 TO 20131205]))",kc.getFilter(0).toString());
     };
 
     @Test
@@ -40,6 +40,16 @@
 	assertEquals("filter with QueryWrapperFilter(+pubDate:[20000101 TO 20131231])",kc.getFilter(0).toString());
     };
 
+    @Test
+    public void metaQuery7 () {
+	String metaQuery = getString(getClass().getResource("/queries/metaquery7.jsonld").getFile());
+	KorapCollection kc = new KorapCollection(metaQuery);
+	assertEquals(2,kc.getCount());
+	assertEquals("filter with QueryWrapperFilter(+(corpusID:c-1 corpusID:c-2))",kc.getFilter(0).toString());
+	assertEquals("filter with QueryWrapperFilter(+(+corpusID:d-1 +corpusID:d-2))",kc.getFilter(1).toString());
+    };
+
+
 
     public static String getString (String path) {
 	StringBuilder contentBuilder = new StringBuilder();
diff --git a/src/test/java/de/ids_mannheim/korap/filter/TestKorapFilter.java b/src/test/java/de/ids_mannheim/korap/filter/TestKorapFilter.java
index 661e45b..4651dbf 100644
--- a/src/test/java/de/ids_mannheim/korap/filter/TestKorapFilter.java
+++ b/src/test/java/de/ids_mannheim/korap/filter/TestKorapFilter.java
@@ -25,6 +25,8 @@
 	assertEquals("+textClass:tree +textClass:sport", kf.and("textClass","tree").and("textClass","sport").toString());
 	assertEquals("+textClass:tree +textClass:sport textClass:news", kf.and("textClass","tree").and("textClass","sport").or("textClass","news").toString());
 	assertEquals("+textClass:tree +textClass:sport +textClass:news", kf.and("textClass", "tree", "sport", "news").toString());
+
+	assertEquals("corpusID:c-1 corpusID:c-2 corpusID:c-3", kf.or("corpusID", "c-1", "c-2", "c-3").toString());
     };
 
     @Test
diff --git a/src/test/java/de/ids_mannheim/korap/index/TestRealIndex.java b/src/test/java/de/ids_mannheim/korap/index/TestRealIndex.java
index 3ac5a32..6be837f 100644
--- a/src/test/java/de/ids_mannheim/korap/index/TestRealIndex.java
+++ b/src/test/java/de/ids_mannheim/korap/index/TestRealIndex.java
@@ -12,6 +12,7 @@
 import org.apache.lucene.store.MMapDirectory;
 import de.ids_mannheim.korap.filter.BooleanFilter;
 import org.apache.lucene.search.spans.SpanQuery;
+import de.ids_mannheim.korap.util.QueryException;
 
 import static org.junit.Assert.*;
 import org.junit.Test;
diff --git a/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java b/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
index 9b5be8b..ecbf426 100644
--- a/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
+++ b/src/test/java/de/ids_mannheim/korap/search/TestKorapSearch.java
@@ -279,6 +279,69 @@
 
     };
 
+    @Test
+    public void searchJSONCollection () throws IOException {
+
+	// Construct index
+	KorapIndex ki = new KorapIndex();
+	// Indexing test files
+	for (String i : new String[] {"00001", "00002", "00003", "00004", "00005", "00006", "02439"}) {
+	    ki.addDocFile(
+	      getClass().getResource("/wiki/" + i + ".json.gz").getFile(), true
+            );
+	};
+	ki.commit();
+
+	String json = getString(getClass().getResource("/queries/metaquery8-nocollection.jsonld").getFile());
+	
+	KorapSearch ks = new KorapSearch(json);
+	KorapResult kr = ks.run(ki);
+	assertEquals(276, kr.getTotalResults());
+	assertEquals(0, kr.getStartIndex());
+	assertEquals(10, kr.getItemsPerPage());
+
+	json = getString(getClass().getResource("/queries/metaquery8.jsonld").getFile());
+	
+	ks = new KorapSearch(json);
+	kr = ks.run(ki);
+
+	assertEquals(147, kr.getTotalResults());
+	assertEquals("WPD_AAA.00001", kr.getMatch(0).getDocID());
+	assertEquals(0, kr.getStartIndex());
+	assertEquals(10, kr.getItemsPerPage());
+
+	json = getString(getClass().getResource("/queries/metaquery8-filtered.jsonld").getFile());
+	
+	ks = new KorapSearch(json);
+	kr = ks.run(ki);
+
+	assertEquals(28, kr.getTotalResults());
+	assertEquals("WPD_AAA.00002", kr.getMatch(0).getDocID());
+	assertEquals(0, kr.getStartIndex());
+	assertEquals(10, kr.getItemsPerPage());
+
+	json = getString(getClass().getResource("/queries/metaquery8-filtered-further.jsonld").getFile());
+	
+	ks = new KorapSearch(json);
+	kr = ks.run(ki);
+
+	assertEquals(0, kr.getTotalResults());
+	assertEquals(0, kr.getStartIndex());
+	assertEquals(10, kr.getItemsPerPage());
+
+	json = getString(getClass().getResource("/queries/metaquery8-filtered-nested.jsonld").getFile());
+	
+	ks = new KorapSearch(json);
+	kr = ks.run(ki);
+
+	assertEquals("filter with QueryWrapperFilter(+(ID:WPD_AAA.00003 (+tokens:s:die +tokens:s:Schriftzeichen)))", ks.getCollection().getFilter(1).toString());
+
+	assertEquals(119, kr.getTotalResults());
+	assertEquals(0, kr.getStartIndex());
+	assertEquals(10, kr.getItemsPerPage());
+
+    };
+
 
     public static String getString (String path) {
 	StringBuilder contentBuilder = new StringBuilder();
diff --git a/src/test/resources/queries/metaquery7.jsonld b/src/test/resources/queries/metaquery7.jsonld
new file mode 100644
index 0000000..a7114e6
--- /dev/null
+++ b/src/test/resources/queries/metaquery7.jsonld
@@ -0,0 +1,60 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "meta":{
+	"startPage" : 1,
+	"count" : 5,
+	"context" : {
+	    "left" : [ "token", 3 ],
+	    "right" : [ "char", 6 ]
+	}
+    },
+    "query":{
+	"@type":"korap:token",
+	"wrap":{
+	    "@type":"korap:term",
+	    "foundry":"mate",
+	    "layer":"l",
+	    "key":"lediglich"
+	}
+    },
+    "collections": [
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#corpusID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "c-1"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "c-2"
+		    }
+		]
+	    }
+	},
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "and",
+		"@field": "korap:field#corpusID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "d-1"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "d-2"
+		    }
+		]
+	    }
+	}
+    ]
+}
diff --git a/src/test/resources/queries/metaquery8-filtered-further.jsonld b/src/test/resources/queries/metaquery8-filtered-further.jsonld
new file mode 100644
index 0000000..945a2fa
--- /dev/null
+++ b/src/test/resources/queries/metaquery8-filtered-further.jsonld
@@ -0,0 +1,80 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "query": {
+	"@type": "korap:token", 
+	"wrap": {
+	    "@type": "korap:term", 
+	    "foundry": "mate",
+	    "layer" : "lemma",
+	    "key":"der", 
+	    "match": "match:eq"
+	}
+    },
+    "meta":{
+	"startPage":1,
+	"count": 10,
+	"context":{
+	    "left":["char",90],
+	    "right":["char",90]
+	}
+    },
+    "collections": [
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00001"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	},
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00003"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	},
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00001"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00005"
+		    }
+		]
+	    }
+	}
+    ]
+}
diff --git a/src/test/resources/queries/metaquery8-filtered-nested.jsonld b/src/test/resources/queries/metaquery8-filtered-nested.jsonld
new file mode 100644
index 0000000..ebb56ab
--- /dev/null
+++ b/src/test/resources/queries/metaquery8-filtered-nested.jsonld
@@ -0,0 +1,73 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "query": {
+	"@type": "korap:token", 
+	"wrap": {
+	    "@type": "korap:term", 
+	    "foundry": "mate",
+	    "layer" : "lemma",
+	    "key":"der", 
+	    "match": "match:eq"
+	}
+    },
+    "meta":{
+	"startPage":1,
+	"count": 10,
+	"context":{
+	    "left":["char",90],
+	    "right":["char",90]
+	}
+    },
+    "collections": [
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00001"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	},
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00003"
+		    },
+		    {
+			"@type": "korap:group",
+			"relation": "and",
+			"@field": "korap:field#tokens",
+			"operands": [
+			    {
+				"@type": "korap:term",
+				"@value": "s:die"
+			    },
+			    {
+				"@type": "korap:term",
+				"@value": "s:Schriftzeichen"
+			    }
+
+			]
+		    }
+		]
+	    }
+	}
+    ]
+}
diff --git a/src/test/resources/queries/metaquery8-filtered.jsonld b/src/test/resources/queries/metaquery8-filtered.jsonld
new file mode 100644
index 0000000..ddb51cf
--- /dev/null
+++ b/src/test/resources/queries/metaquery8-filtered.jsonld
@@ -0,0 +1,61 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "query": {
+	"@type": "korap:token", 
+	"wrap": {
+	    "@type": "korap:term", 
+	    "foundry": "mate",
+	    "layer" : "lemma",
+	    "key":"der", 
+	    "match": "match:eq"
+	}
+    },
+    "meta":{
+	"startPage":1,
+	"count": 10,
+	"context":{
+	    "left":["char",90],
+	    "right":["char",90]
+	}
+    },
+    "collections": [
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00001"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	},
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00003"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	}
+    ]
+}
diff --git a/src/test/resources/queries/metaquery8-nocollection.jsonld b/src/test/resources/queries/metaquery8-nocollection.jsonld
new file mode 100644
index 0000000..6edf506
--- /dev/null
+++ b/src/test/resources/queries/metaquery8-nocollection.jsonld
@@ -0,0 +1,21 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "query": {
+	"@type": "korap:token", 
+	"wrap": {
+	    "@type": "korap:term", 
+	    "foundry": "mate",
+	    "layer" : "lemma",
+	    "key":"der", 
+	    "match": "match:eq"
+	}
+    },
+    "meta":{
+	"startPage":1,
+	"count": 10,
+	"context":{
+	    "left":["char",90],
+	    "right":["char",90]
+	}
+    }
+}
diff --git a/src/test/resources/queries/metaquery8.jsonld b/src/test/resources/queries/metaquery8.jsonld
new file mode 100644
index 0000000..77dfc4c
--- /dev/null
+++ b/src/test/resources/queries/metaquery8.jsonld
@@ -0,0 +1,42 @@
+{
+    "@context": "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld",
+    "query": {
+	"@type": "korap:token", 
+	"wrap": {
+	    "@type": "korap:term", 
+	    "foundry": "mate",
+	    "layer" : "lemma",
+	    "key":"der", 
+	    "match": "match:eq"
+	}
+    },
+    "meta":{
+	"startPage":1,
+	"count": 10,
+	"context":{
+	    "left":["char",90],
+	    "right":["char",90]
+	}
+    },
+    "collections": [
+	{
+	    "@type": "korap:meta-filter",
+	    "@id": "korap-filter#id-1223232",
+	    "@value": {
+		"@type": "korap:group",
+		"relation": "or",
+		"@field": "korap:field#ID",
+		"operands": [
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00001"
+		    },
+		    {
+			"@type": "korap:term",
+			"@value": "WPD_AAA.00002"
+		    }
+		]
+	    }
+	}
+    ]
+}