diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index a091027c40abafe60b307367929f1736b62ac046..8f58cb2c1d01c962b35301911e5889ebd040b0d8 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: + '"' {setText("");} -> mode(SELECT_MODE) + ; + + SELECT_DOUBLE_QUOTE_TXT: + . + ; + +mode SELECT_SINGLE_QUOTED; + + SELECT_SINGLE_QUOTE_ESCAPED: + ESC_MARKER + '\'' + ; + + SELECT_SINGLE_QUOTE_END: + '\'' {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/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 3915243565182aa4b680dec253ef65812d77cd30..07a165a352ba9a07a345018e7d287cd961f4a00b 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -100,13 +100,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac /** No parsing, just sets the selector string. */ public Selection(final String selector) { - if (selector.trim().startsWith("'") && selector.trim().endsWith("'")) { - this.selector = selector.replaceFirst("^\\s*'", "").replaceFirst("'\\s*$", "").trim(); - } else if (selector.trim().startsWith("\"") && selector.trim().endsWith("\"")) { - this.selector = selector.replaceFirst("^\\s*\"", "").replaceFirst("\"\\s*$", "").trim(); - } else { - this.selector = selector.trim(); - } + this.selector = selector.trim(); } public String getSelector() { diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index 3a151fc3b7b908936aa5087b5b7509be73abc278..58de2f311ae7636531cf66658fb5f31a92fb9de2 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -273,6 +273,10 @@ 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 issue130 = + "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"; + @Test public void testQuery1() throws InterruptedException, SQLException, ConnectionException, QueryException { @@ -6935,4 +6939,38 @@ public class TestCQL { assertNull(pov.getVInt()); assertEquals(1e10, pov.getVDouble().doubleValue(), 0.0); } + + /** + * Spaces and escaped dots in selects + * + * <p>String issue130 = "SELECT 'name with spaces.and dot', 'name with spaces'.name, name with + * spaces.name FROM ENTITY"; + */ + @Test + public void testIssue130() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue130)); + 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()); + } }