diff --git a/CHANGELOG.md b/CHANGELOG.md index 196a8292b20918926df4a66fb1081651f0ede66e..1a52dbfa8ee747cf8d7400279fa41ab1a487c187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -This is an important security update. - ### Added ### Changed @@ -19,6 +17,9 @@ This is an important security update. ### Fixed +* [caosdb-server#131](https://gitlab.com/caosdb/caosdb-server/-/issues/131) + Query: AND does not work with sub-properties + ### Security diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index c4311ac1cbe03f490b79456e091cc0e73b94c506..f8c2a3b5ea4e7c34f3cc8fd07cdc77c912a5cc7b 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -287,15 +287,36 @@ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a] ; -subproperty returns [SubProperty subp] locals [String p] +subproperty returns [SubProperty subp] @init{ - $p = null; $subp = null; } : - entity_filter {$subp = new SubProperty($entity_filter.filter);} + subproperty_filter {$subp = new SubProperty($subproperty_filter.filter);} ; +subproperty_filter returns [EntityFilterInterface filter] + @init{ + $filter = null; + } +: + which_exp + ( + ( + LPAREN WHITE_SPACE? + ( + filter_expression {$filter = $filter_expression.efi;} + | conjunction {$filter = $conjunction.c;} + | disjunction {$filter = $disjunction.d;} + ) + RPAREN + ) | ( + filter_expression {$filter = $filter_expression.efi;} + ) + )? +; + + backreference returns [Backreference ref] locals [Query.Pattern e, Query.Pattern p] @init{ $e = null; @@ -328,7 +349,7 @@ storedat returns [StoredAt filter] locals [String loc] WHITE_SPACE? ; -conjunction returns [Conjunction c] locals [Conjunction dummy] +conjunction returns [Conjunction c] @init{ $c = new Conjunction(); } @@ -493,7 +514,7 @@ number_with_unit unit : - (~(WHITE_SPACE | WHICH | HAS_A | WITH | WHERE | DOT | AND | OR )) + (~(WHITE_SPACE | WHICH | HAS_A | WITH | WHERE | DOT | AND | OR | RPAREN )) (~(WHITE_SPACE))* | NUM SLASH (~(WHITE_SPACE))+ @@ -510,7 +531,7 @@ atom returns [Query.Pattern ep] : double_quoted {$ep = $double_quoted.ep;} | single_quoted {$ep = $single_quoted.ep;} - | (~(WHITE_SPACE | DOT ))+ {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);} + | (~(WHITE_SPACE | DOT | RPAREN | LPAREN ))+ {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);} ; single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType] diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index cf54bf71b69ea5f539744bd4bbe30fd1c37edcf4..a721fec389aa7635cab8f12c7801db5a6080e265 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -264,6 +264,13 @@ public class TestCQL { String queryMR56 = "FIND ENTITY WITH ((p0 = v0 OR p1=v1) AND p2=v2)"; String versionedQuery1 = "FIND ANY VERSION OF ENTITY e1"; + // https://gitlab.com/caosdb/caosdb-server/-/issues/131 + String issue131a = "FIND ename WITH pname1.x AND pname2"; + String issue131b = "FIND ename WITH (pname1.x < 10) AND (pname1.x)"; + String issue131c = "FIND ename WITH pname2 AND pname1.x "; + String issue131d = "FIND ename WITH (pname1.x) AND pname2"; + String issue131e = "FIND ename WITH (pname1.pname2 > 30) AND (pname1.pname2 < 40)"; + String issue131f = "FIND ename WITH (pname1.pname2 > 30) AND pname1.pname2 < 40"; @Test public void testQuery1() @@ -6740,4 +6747,163 @@ public class TestCQL { // must not throw ParsingException new Query(this.queryIssue131).parse(); } + + /** String issue131a = "FIND ename WITH pname1.x AND pname2"; */ + @Test + public void testIssue131a() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131a)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname1,null,null)", pov1.toString()); + assertEquals("POV(pname2,null,null)", pov2.toString()); + assertTrue(pov1.hasSubProperty()); + assertFalse(pov2.hasSubProperty()); + assertEquals("POV(x,null,null)", pov1.getSubProperty().getFilter().toString()); + } + + /** String issue131b = "FIND ename WITH (pname1.x < 10) AND (pname1.x)"; */ + @Test + public void testIssue131b() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131b)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname1,null,null)", pov1.toString()); + assertEquals("POV(pname1,null,null)", pov2.toString()); + assertTrue(pov1.hasSubProperty()); + assertTrue(pov2.hasSubProperty()); + assertEquals("POV(x,<,10)", pov1.getSubProperty().getFilter().toString()); + assertEquals("POV(x,null,null)", pov2.getSubProperty().getFilter().toString()); + } + + /** String issue131c = "FIND ename WITH pname2 AND pname1.x "; */ + @Test + public void testIssue131c() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131c)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname2,null,null)", pov1.toString()); + assertEquals("POV(pname1,null,null)", pov2.toString()); + assertFalse(pov1.hasSubProperty()); + assertTrue(pov2.hasSubProperty()); + assertEquals("POV(x,null,null)", pov2.getSubProperty().getFilter().toString()); + } + + /** String issue131d = "FIND ename WITH (pname1.x) AND pname2"; */ + @Test + public void testIssue131d() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131d)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname1,null,null)", pov1.toString()); + assertEquals("POV(pname2,null,null)", pov2.toString()); + assertTrue(pov1.hasSubProperty()); + assertFalse(pov2.hasSubProperty()); + assertEquals("POV(x,null,null)", pov1.getSubProperty().getFilter().toString()); + } + + /** String issue131e = "FIND ename WITH (pname1.pname2 > 30) AND (pname1.pname2 < 40)"; */ + @Test + public void testIssue131e() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131e)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname1,null,null)", pov1.toString()); + assertEquals("POV(pname1,null,null)", pov2.toString()); + assertTrue(pov1.hasSubProperty()); + assertTrue(pov2.hasSubProperty()); + assertEquals("POV(pname2,>,30)", pov1.getSubProperty().getFilter().toString()); + assertEquals("POV(pname2,<,40)", pov2.getSubProperty().getFilter().toString()); + } + + /** String issue131f = "FIND ename WITH (pname1.pname2 > 30) AND pname1.pname2 < 40"; */ + @Test + public void testIssue131f() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue131f)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof Conjunction); + LinkedList<EntityFilterInterface> filters = ((Conjunction) sfq.filter).getFilters(); + assertEquals(filters.size(), 2); + assertTrue(filters.get(0) instanceof POV); + assertTrue(filters.get(1) instanceof POV); + POV pov1 = ((POV) filters.get(0)); + POV pov2 = ((POV) filters.get(1)); + assertEquals("POV(pname1,null,null)", pov1.toString()); + assertEquals("POV(pname1,null,null)", pov2.toString()); + assertTrue(pov1.hasSubProperty()); + assertTrue(pov2.hasSubProperty()); + assertEquals("POV(pname2,>,30)", pov1.getSubProperty().getFilter().toString()); + assertEquals("POV(pname2,<,40)", pov2.getSubProperty().getFilter().toString()); + } }