From cd6c702b79fc3e145453ad557c9800c8c3356d07 Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Thu, 7 Jul 2022 22:20:58 +0200
Subject: [PATCH] TST: unit test for caosdb-server#130

---
 .../java/org/caosdb/server/query/CQLLexer.g4  | 45 +++++++++++++++++++
 .../java/org/caosdb/server/query/CQLParser.g4 | 19 ++++++--
 .../java/org/caosdb/server/query/Query.java   |  8 +---
 .../java/org/caosdb/server/query/TestCQL.java | 38 ++++++++++++++++
 4 files changed, 100 insertions(+), 10 deletions(-)

diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4
index a091027c..8f58cb2c 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 f8c2a3b5..c4a68e5f 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 39152435..07a165a3 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 3a151fc3..58de2f31 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());
+  }
 }
-- 
GitLab