diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a6a7dc4323fe3643fc13c0c1227ba90f6a40bc..249f8186a7ec7560b03a033d1c94083e2fbf80ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* [caosdb-server#130](https://gitlab.com/caosdb/caosdb-server/-/issues/130) + CQL: Spaces are not recognised in sub-property queries. * [caosdb-server#148](https://gitlab.com/caosdb/caosdb-server/-/issues/148) Cannot delete file entities containing unescaped space characters * [caosdb-server#145](https://gitlab.com/caosdb/caosdb-server/-/issues/145) diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index a091027c40abafe60b307367929f1736b62ac046..99c9879de7a772c6032decc51486fe7485d869e5 100644 --- a/src/main/java/org/caosdb/server/query/CQLLexer.g4 +++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4 @@ -498,18 +498,63 @@ mode DOUBLE_QUOTE_MODE; ; +mode SELECT_DOUBLE_QUOTED; + + SELECT_DOUBLE_QUOTE_ESCAPED: + ESC_MARKER + '"' + ; + + SELECT_DOUBLE_QUOTE_END: + '"' WHITE_SPACE_f? {setText("");} -> mode(SELECT_MODE) + ; + + SELECT_DOUBLE_QUOTE_TXT: + . + ; + +mode SELECT_SINGLE_QUOTED; + + SELECT_SINGLE_QUOTE_ESCAPED: + ESC_MARKER + '\'' + ; + + SELECT_SINGLE_QUOTE_END: + '\'' WHITE_SPACE_f? {setText("");} -> mode(SELECT_MODE) + ; + + SELECT_SINGLE_QUOTE_TXT: + . + ; + mode SELECT_MODE; FROM: [Ff][Rr][Oo][Mm]([ \t\n\r])* -> mode(DEFAULT_MODE) ; + SELECT_ESCAPED: + ESC_MARKER + ( '"' | '\\' | '\'' | ',' | '.' ) {setText(getText().substring(1));} + ; + SELECT_DOT: '.' + WHITE_SPACE_f? + ; + + SELECT_DOUBLE_QUOTE: + '"' {setText("");} -> mode(SELECT_DOUBLE_QUOTED) + ; + + SELECT_SINGLE_QUOTE: + '\'' {setText("");} -> mode(SELECT_SINGLE_QUOTED) ; SELECT_COMMA: ',' + WHITE_SPACE_f? ; SELECTOR_TXT: diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index f8c2a3b5ea4e7c34f3cc8fd07cdc77c912a5cc7b..c4a68e5fbd3b4ad6cf61e5a7bd07b02ff1085095 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -74,11 +74,24 @@ prop_sel returns [List<Query.Selection> s] ; prop_subsel returns [Query.Selection sub]: - selector_txt {$sub = new Query.Selection($selector_txt.text);}(SELECT_DOT s=prop_subsel {$sub.setSubSelection($s.sub);})? + selector_txt {$sub = new Query.Selection($selector_txt.text);} + ( + SELECT_DOT s=prop_subsel {$sub.setSubSelection($s.sub);} + )? ; selector_txt: - SELECTOR_TXT+ + ( + SELECT_DOUBLE_QUOTE + ( SELECT_DOUBLE_QUOTE_TXT | SELECT_DOUBLE_QUOTE_ESCAPED )* + SELECT_DOUBLE_QUOTE_END + ) | ( + SELECT_SINGLE_QUOTE + ( SELECT_SINGLE_QUOTE_TXT | SELECT_SINGLE_QUOTE_ESCAPED )* + SELECT_SINGLE_QUOTE_END + ) + | + ( SELECTOR_TXT | SELECT_ESCAPED )+ ; role returns [Query.Role r]: @@ -292,7 +305,7 @@ subproperty returns [SubProperty subp] $subp = null; } : - subproperty_filter {$subp = new SubProperty($subproperty_filter.filter);} + subproperty_filter {$subp = new SubProperty($subproperty_filter.filter);} ; subproperty_filter returns [EntityFilterInterface filter] diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index 3a151fc3b7b908936aa5087b5b7509be73abc278..3217d4228068027628b1846eb6073da1bc18052e 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -273,6 +273,11 @@ public class TestCQL { String issue131e = "FIND ename WITH (pname1.pname2 > 30) AND (pname1.pname2 < 40)"; String issue131f = "FIND ename WITH (pname1.pname2 > 30) AND pname1.pname2 < 40"; + // https://gitlab.com/caosdb/caosdb-server/-/issues/130 + String issue130a = + "SELECT 'name with spaces.and dot', 'name with spaces'.name, name with spaces.name, name with\\,comma and\\.dot and \\'single_quote.sub FROM ENTITY"; + String issue130b = "SELECT 'Wrapper' FROM RECORD TestRT"; + @Test public void testQuery1() throws InterruptedException, SQLException, ConnectionException, QueryException { @@ -6935,4 +6940,53 @@ public class TestCQL { assertNull(pov.getVInt()); assertEquals(1e10, pov.getVDouble().doubleValue(), 0.0); } + + /** Spaces and escaped dots in selects */ + @Test + public void testIssue130a() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue130a)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertNotNull(sfq.s); + assertFalse(sfq.s.isEmpty()); + assertEquals(4, sfq.s.size()); + assertEquals("name with spaces.and dot", sfq.s.get(0).toString()); + assertEquals("name with spaces.and dot", sfq.s.get(0).getSelector()); + assertNull(sfq.s.get(0).getSubselection()); + assertEquals("name with spaces.name", sfq.s.get(1).toString()); + assertEquals("name with spaces", sfq.s.get(1).getSelector()); + assertEquals("name", sfq.s.get(1).getSubselection().toString()); + assertEquals("name with spaces.name", sfq.s.get(2).toString()); + assertEquals("name with spaces", sfq.s.get(2).getSelector()); + assertEquals("name", sfq.s.get(2).getSubselection().toString()); + assertEquals("name with,comma and.dot and 'single_quote.sub", sfq.s.get(3).toString()); + assertEquals("name with,comma and.dot and 'single_quote", sfq.s.get(3).getSelector()); + assertEquals("sub", sfq.s.get(3).getSubselection().toString()); + } + + /** Single quotes around the selector */ + @Test + public void testIssue130b() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue130b)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + System.out.println(sfq.toStringTree(parser)); + + assertNotNull(sfq.s); + assertFalse(sfq.s.isEmpty()); + assertEquals(1, sfq.s.size()); + assertEquals("Wrapper", sfq.s.get(0).toString()); + assertEquals("Wrapper", sfq.s.get(0).getSelector()); + assertNull(sfq.s.get(0).getSubselection()); + } }