You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2020/09/09 16:58:42 UTC

[asterixdb] branch master updated: [NO ISSUE][COMP] Change format of dataverse display name

This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 62f0beb  [NO ISSUE][COMP] Change format of dataverse display name
62f0beb is described below

commit 62f0bebe1eed855a752c7b1ed90ffb686e1d4ddc
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Tue Sep 8 11:44:19 2020 -0700

    [NO ISSUE][COMP] Change format of dataverse display name
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    - Dataverse display name is a concatenation of  name parts,
      separated with dots. This change adds back-tick wrapping around
      those name parts that are not parseable as SQL++ identifiers.
      E.g. the display name of a dataverse name ["a-b", "c-d"] used
      to be a-b.c-d, after this change it is `a-b`.`c-d`
    - Add decode_dataverse_display_name() builtin function that
      converts dataverse canonical name to its display name
    - Add IParser.parseExpression()
    
    Change-Id: Iebc2598022cda55b3d2c287632c9bc75438c2266
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/7824
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Dmitry Lychagin <dm...@couchbase.com>
    Reviewed-by: Dmitry Lychagin <dm...@couchbase.com>
    Reviewed-by: Murtadha Hubail <mh...@apache.org>
---
 .../test/sqlpp/DataverseNameParserTest.java        |  71 ++++++++++++
 .../results_parser_sqlpp/fj-dblp-csx.ast           |   2 +-
 .../results_parser_sqlpp/join-super-key_01.ast     |   2 +-
 .../results_parser_sqlpp/join-super-key_02.ast     |   2 +-
 .../results_parser_sqlpp/loj-super-key_01.ast      |   2 +-
 .../results_parser_sqlpp/loj-super-key_02.ast      |   2 +-
 .../orderby-desc-using-gby.ast                     |   2 +-
 .../results_parser_sqlpp/orders-aggreg.ast         |   2 +-
 .../pull_select_above_eq_join.ast                  |   2 +-
 .../special_chars_2/special_chars_2.1.ddl.sqlpp    |  33 ++++++
 .../special_chars_2/special_chars_2.2.query.sqlpp  |  24 ++++
 .../special_chars_2/special_chars_2.2.adm          |   6 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |   5 +
 .../asterix/common/config/CompilerProperties.java  |   2 +-
 .../asterix/common/metadata/DataverseName.java     | 125 +++++++++++++++------
 .../asterix/common/metadata/DataverseNameTest.java | 120 +++++++++++---------
 asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj  |   4 +-
 .../apache/asterix/lang/common/base/IParser.java   |   2 +
 .../lang/common/util/DataverseNameUtils.java       |   2 +-
 .../lang/common/visitor/FormatPrintVisitor.java    |   2 +-
 .../lang/sqlpp/util/SqlppStatementUtil.java        |   4 +-
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    |   3 +-
 .../asterix/om/functions/BuiltinFunctions.java     |   5 +-
 .../DecodeDataverseDisplayNameDescriptor.java      |  74 ++++++++++++
 .../functions/DecodeDataverseNameDescriptor.java   |   4 +-
 .../runtime/functions/FunctionCollection.java      |   2 +
 .../hyracks/data/std/util/UTF8StringBuilder.java   |   2 +-
 27 files changed, 403 insertions(+), 103 deletions(-)

diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/DataverseNameParserTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/DataverseNameParserTest.java
new file mode 100644
index 0000000..1cd41ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/DataverseNameParserTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.test.sqlpp;
+
+import java.util.List;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.common.metadata.DataverseNameTest;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IParser;
+import org.apache.asterix.lang.common.base.IParserFactory;
+import org.apache.asterix.lang.common.base.IRewriterFactory;
+import org.apache.asterix.lang.common.base.IStatementRewriter;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.parser.SqlppParserFactory;
+import org.apache.asterix.lang.sqlpp.rewrites.SqlppRewriterFactory;
+import org.junit.Assert;
+
+public class DataverseNameParserTest extends DataverseNameTest {
+
+    private final IParserFactory parserFactory = new SqlppParserFactory();
+
+    private final IRewriterFactory rewriterFactory = new SqlppRewriterFactory(parserFactory);
+
+    @Override
+    protected void testDataverseNameImpl(DataverseName dataverseName, List<String> parts, String expectedCanonicalForm,
+            String expectedDisplayForm) throws Exception {
+        super.testDataverseNameImpl(dataverseName, parts, expectedCanonicalForm, expectedDisplayForm);
+
+        String displayForm = dataverseName.toString();
+
+        // check parse-ability of the display form
+        IParser parser = parserFactory.createParser(displayForm);
+        Expression expr = parser.parseExpression();
+        IStatementRewriter rewriter = rewriterFactory.createStatementRewriter();
+
+        for (int i = parts.size() - 1; i >= 0; i--) {
+            String part = parts.get(i);
+            String parsedPart;
+            if (i > 0) {
+                Assert.assertEquals(Expression.Kind.FIELD_ACCESSOR_EXPRESSION, expr.getKind());
+                FieldAccessor faExpr = (FieldAccessor) expr;
+                parsedPart = faExpr.getIdent().getValue();
+                expr = faExpr.getExpr();
+            } else {
+                Assert.assertEquals(Expression.Kind.VARIABLE_EXPRESSION, expr.getKind());
+                VariableExpr varExpr = (VariableExpr) expr;
+                parsedPart = rewriter.toFunctionParameterName(varExpr.getVar());
+            }
+            Assert.assertEquals("unexpected parsed part at position " + i + " in " + parts, part, parsedPart);
+        }
+    }
+}
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/fj-dblp-csx.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/fj-dblp-csx.ast
index 1d1899c..56c7eb1 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/fj-dblp-csx.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/fj-dblp-csx.ast
@@ -1,4 +1,4 @@
-DataverseUse fj-dblp-csx
+DataverseUse `fj-dblp-csx`
 TypeDecl DBLPType [
   open RecordType {
     id : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_01.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_01.ast
index 50b2a5a..b0813fd 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_01.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_01.ast
@@ -1,4 +1,4 @@
-DataverseUse join-super-key_1
+DataverseUse `join-super-key_1`
 TypeDecl SupplierType [
   closed RecordType {
     s_suppkey : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_02.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_02.ast
index 8e8cfe6..0b6b9b4 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_02.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/join-super-key_02.ast
@@ -1,4 +1,4 @@
-DataverseUse join-super-key_01
+DataverseUse `join-super-key_01`
 TypeDecl SupplierType [
   closed RecordType {
     s_suppkey : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_01.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_01.ast
index 46c09a9..b012a70 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_01.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_01.ast
@@ -1,4 +1,4 @@
-DataverseUse loj-super-key_01
+DataverseUse `loj-super-key_01`
 TypeDecl SupplierType [
   closed RecordType {
     s_suppkey : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_02.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_02.ast
index c0390e9..deee859 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_02.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/loj-super-key_02.ast
@@ -1,4 +1,4 @@
-DataverseUse loj-super-key_02
+DataverseUse `loj-super-key_02`
 TypeDecl SupplierType [
   closed RecordType {
     s_suppkey : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orderby-desc-using-gby.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orderby-desc-using-gby.ast
index 675ca8d..ca16111 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orderby-desc-using-gby.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orderby-desc-using-gby.ast
@@ -1,4 +1,4 @@
-DataverseUse gby-using-orderby-desc
+DataverseUse `gby-using-orderby-desc`
 TypeDecl AddressType [
   closed RecordType {
     number : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orders-aggreg.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orders-aggreg.ast
index cd4bca3..4d8ace4 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orders-aggreg.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/orders-aggreg.ast
@@ -1,4 +1,4 @@
-DataverseUse orders-aggreg
+DataverseUse `orders-aggreg`
 TypeDecl OrderType [
   closed RecordType {
     oid : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/pull_select_above_eq_join.ast b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/pull_select_above_eq_join.ast
index ca6fe07..c723a60 100644
--- a/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/pull_select_above_eq_join.ast
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results_parser_sqlpp/pull_select_above_eq_join.ast
@@ -1,4 +1,4 @@
-DataverseUse pull-select-above-eq-join
+DataverseUse `pull-select-above-eq-join`
 TypeDecl UserType [
   open RecordType {
     uid : integer,
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.1.ddl.sqlpp
new file mode 100644
index 0000000..867c91b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.1.ddl.sqlpp
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Description: special characters in multipart dataverse name
+ */
+
+drop dataverse A if exists;
+create dataverse A;
+
+drop dataverse B.C if exists;
+create dataverse B.C;
+
+drop dataverse `C.D.E` if exists;
+create dataverse `C.D.E`;
+
+drop dataverse `a-A`.`b_B`.`c$C`.`z.Z` if exists;
+create dataverse `a-A`.`b_B`.`c$C`.`z.Z`;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.2.query.sqlpp
new file mode 100644
index 0000000..4dc4e3b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/multipart-dataverse/special_chars_2/special_chars_2.2.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+select DataverseName as CanonicalName,
+  decode_dataverse_display_name(DataverseName) as DisplayName,
+  decode_dataverse_name(DataverseName) as NameParts
+from Metadata.`Dataverse`
+order by DataverseName;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/multipart-dataverse/special_chars_2/special_chars_2.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/multipart-dataverse/special_chars_2/special_chars_2.2.adm
new file mode 100644
index 0000000..9abda4f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/multipart-dataverse/special_chars_2/special_chars_2.2.adm
@@ -0,0 +1,6 @@
+{ "CanonicalName": "A", "DisplayName": "A", "NameParts": [ "A" ] }
+{ "CanonicalName": "B.C", "DisplayName": "B.C", "NameParts": [ "B", "C" ] }
+{ "CanonicalName": "C@.D@.E", "DisplayName": "`C.D.E`", "NameParts": [ "C.D.E" ] }
+{ "CanonicalName": "Default", "DisplayName": "Default", "NameParts": [ "Default" ] }
+{ "CanonicalName": "Metadata", "DisplayName": "Metadata", "NameParts": [ "Metadata" ] }
+{ "CanonicalName": "a-A.b_B.c$C.z@.Z", "DisplayName": "`a-A`.b_B.c$C.`z.Z`", "NameParts": [ "a-A", "b_B", "c$C", "z.Z" ] }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index afdedff..7d4dbdd 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -6772,6 +6772,11 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="multipart-dataverse">
+      <compilation-unit name="special_chars_2">
+        <output-dir compare="Text">special_chars_2</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="multipart-dataverse">
       <compilation-unit name="udf_1">
         <output-dir compare="Text">udf_1</output-dir>
       </compilation-unit>
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/CompilerProperties.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/CompilerProperties.java
index ec9b87d..9ec6279 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/CompilerProperties.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/CompilerProperties.java
@@ -76,7 +76,7 @@ public class CompilerProperties extends AbstractProperties {
         COMPILER_INTERNAL_SANITYCHECK(
                 BOOLEAN,
                 AlgebricksConfig.SANITYCHECK_DEFAULT,
-                "Enabling/disable compiler sanity check");
+                "Enable/disable compiler sanity check");
 
         private final IOptionType type;
         private final Object defaultValue;
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DataverseName.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DataverseName.java
index 5ec3f1a..83bf94c 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DataverseName.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/DataverseName.java
@@ -36,14 +36,15 @@ import org.apache.commons.lang3.StringUtils;
  * <p>
  * Each dataverse name can be encoded into a single string (called a canonical form) by
  * {@link #getCanonicalForm()} and decoded back from it with {@link #createFromCanonicalForm(String)}.
- * The canonical form encoding concatenates name parts together with {@link #SEPARATOR_CHAR '.'} character.
- * The {@link #ESCAPE_CHAR '@'} character is used to escape {@link #SEPARATOR_CHAR '.'} and itself in each name part
- * prior to concatenation.
+ * The canonical form encoding concatenates name parts together with {@link #CANONICAL_FORM_SEPARATOR_CHAR '.'}
+ * character. The {@link #CANONICAL_FORM_ESCAPE_CHAR '@'} character is used to escape
+ * {@link #CANONICAL_FORM_SEPARATOR_CHAR '.'} and itself in each name part prior to concatenation.
  * <p>
  * E.g. the canonical form for a dataverse name {@code ["a", "b", "c"]} is {@code "a.b.c"}
  * <p>
- * {@link #toString()} returns a display form which is a {@link #SEPARATOR_CHAR '.'} separated concatenation
- * of name parts without escaping. In general it's impossible to reconstruct a dataverse name from its display form.
+ * {@link #toString()} returns a display form which is a {@link #CANONICAL_FORM_SEPARATOR_CHAR '.'} separated
+ * concatenation of name parts without escaping. In general it's impossible to reconstruct a dataverse name from
+ * its display form.
  * <p>
  * Notes:
  * <li>
@@ -61,11 +62,18 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
 
     private static final long serialVersionUID = 1L;
 
-    public static final char SEPARATOR_CHAR = '.';
+    public static final char CANONICAL_FORM_SEPARATOR_CHAR = '.';
 
-    private static final char ESCAPE_CHAR = '@';
+    private static final char CANONICAL_FORM_ESCAPE_CHAR = '@';
 
-    private static final char[] SEPARATOR_AND_ESCAPE_CHARS = new char[] { SEPARATOR_CHAR, ESCAPE_CHAR };
+    public static final char DISPLAY_FORM_SEPARATOR_CHAR = '.';
+
+    private static final char DISPLAY_FORM_QUOTE_CHAR = '`';
+
+    private static final char DISPLAY_FORM_ESCAPE_CHAR = '\\';
+
+    private static final char[] CANONICAL_FORM_SEPARATOR_AND_ESCAPE_CHARS =
+            new char[] { CANONICAL_FORM_SEPARATOR_CHAR, CANONICAL_FORM_ESCAPE_CHAR };
 
     private final boolean isMultiPart;
 
@@ -106,9 +114,24 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     }
 
     /**
-     * Appends dataverse name parts into a given list
+     * Appends dataverse name parts into a given output collection
      */
     public void getParts(Collection<? super String> outParts) {
+        getPartsFromCanonicalForm(canonicalForm, isMultiPart, outParts);
+    }
+
+    /**
+     * Appends dataverse name parts into a given output collection
+     */
+    public static void getPartsFromCanonicalForm(String canonicalForm, Collection<? super String> outParts) {
+        getPartsFromCanonicalForm(canonicalForm, isMultiPartCanonicalForm(canonicalForm), outParts);
+    }
+
+    /**
+     * Appends dataverse name parts into a given output collection
+     */
+    private static void getPartsFromCanonicalForm(String canonicalForm, boolean isMultiPart,
+            Collection<? super String> outParts) {
         if (isMultiPart) {
             decodeCanonicalForm(canonicalForm, DataverseName::addPartToCollection, outParts);
         } else {
@@ -117,9 +140,7 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     }
 
     /**
-     * Returns a display form which is a {@link #SEPARATOR_CHAR '.'} separated concatenation of name parts without
-     * escaping. In general it's impossible to reconstruct a dataverse name from its display form, so this method
-     * should not be used when roundtripability is required.
+     * Returns a display form which suitable for error messages, and is a valid SQL++ multi-part identifier.
      */
     @Override
     public String toString() {
@@ -129,19 +150,29 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     private String getDisplayForm() {
         String result = displayForm;
         if (result == null) {
-            displayForm = result = createDisplayForm();
+            StringBuilder sb = new StringBuilder(canonicalForm.length() + 1);
+            getDisplayForm(sb);
+            displayForm = result = sb.toString();
         }
         return result;
     }
 
-    private String createDisplayForm() {
+    public void getDisplayForm(StringBuilder out) {
+        getDisplayFormFromCanonicalForm(canonicalForm, isMultiPart, out);
+    }
+
+    public static void getDisplayFormFromCanonicalForm(String canonicalForm, StringBuilder out) {
+        getDisplayFormFromCanonicalForm(canonicalForm, isMultiPartCanonicalForm(canonicalForm), out);
+    }
+
+    private static void getDisplayFormFromCanonicalForm(String canonicalForm, boolean isMultiPart, StringBuilder out) {
         if (isMultiPart) {
-            StringBuilder displayForm = new StringBuilder(canonicalForm.length() + 1);
-            decodeCanonicalForm(canonicalForm, DataverseName::addPartToDisplayForm, displayForm);
-            return displayForm.substring(0, displayForm.length() - 1); // remove last separator char
+            decodeCanonicalForm(canonicalForm, DataverseName::addPartToDisplayForm, out);
         } else {
-            return decodeSinglePartNameFromCanonicalForm(canonicalForm);
+            String singlePart = decodeSinglePartNameFromCanonicalForm(canonicalForm);
+            addPartToDisplayForm(singlePart, out);
         }
+        out.setLength(out.length() - 1); // remove last separator char
     }
 
     @Override
@@ -209,7 +240,7 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
      * Validates that the canonical form of the created dataverse name is the same as its given single name part.
      */
     public static DataverseName createBuiltinDataverseName(String singlePart) {
-        if (StringUtils.containsAny(singlePart, SEPARATOR_AND_ESCAPE_CHARS)) {
+        if (StringUtils.containsAny(singlePart, CANONICAL_FORM_SEPARATOR_AND_ESCAPE_CHARS)) {
             throw new IllegalArgumentException(singlePart);
         }
         DataverseName dataverseName = createSinglePartName(singlePart); // 1-part name
@@ -234,7 +265,7 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
         StringBuilder sb = new StringBuilder(32);
         for (int i = 0; i < partCount; i++) {
             if (i > 0) {
-                sb.append(SEPARATOR_CHAR);
+                sb.append(CANONICAL_FORM_SEPARATOR_CHAR);
             }
             encodePartIntoCanonicalForm(parts.get(fromIndex + i), sb);
         }
@@ -242,7 +273,7 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     }
 
     private static String encodeSinglePartNamePartIntoCanonicalForm(String singlePart) {
-        if (StringUtils.indexOfAny(singlePart, SEPARATOR_AND_ESCAPE_CHARS) < 0) {
+        if (StringUtils.indexOfAny(singlePart, CANONICAL_FORM_SEPARATOR_AND_ESCAPE_CHARS) < 0) {
             // no escaping needed
             return singlePart;
         }
@@ -254,8 +285,8 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     private static void encodePartIntoCanonicalForm(String part, StringBuilder out) {
         for (int i = 0, ln = part.length(); i < ln; i++) {
             char c = part.charAt(i);
-            if (c == SEPARATOR_CHAR || c == ESCAPE_CHAR) {
-                out.append(ESCAPE_CHAR);
+            if (c == CANONICAL_FORM_SEPARATOR_CHAR || c == CANONICAL_FORM_ESCAPE_CHAR) {
+                out.append(CANONICAL_FORM_ESCAPE_CHAR);
             }
             out.append(c);
         }
@@ -268,11 +299,11 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
         for (int i = 0; i < ln; i++) {
             char c = canonicalForm.charAt(i);
             switch (c) {
-                case SEPARATOR_CHAR:
+                case CANONICAL_FORM_SEPARATOR_CHAR:
                     partConsumer.accept(sb, partConsumerArg);
                     sb.setLength(0);
                     break;
-                case ESCAPE_CHAR:
+                case CANONICAL_FORM_ESCAPE_CHAR:
                     i++;
                     c = canonicalForm.charAt(i);
                     // fall through to 'default'
@@ -287,8 +318,8 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     }
 
     // optimization for a single part name
-    private String decodeSinglePartNameFromCanonicalForm(String canonicalForm) {
-        if (canonicalForm.indexOf(ESCAPE_CHAR) < 0) {
+    private static String decodeSinglePartNameFromCanonicalForm(String canonicalForm) {
+        if (canonicalForm.indexOf(CANONICAL_FORM_ESCAPE_CHAR) < 0) {
             // no escaping was done
             return canonicalForm;
         }
@@ -297,9 +328,9 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
         for (int i = 0, ln = canonicalForm.length(); i < ln; i++) {
             char c = canonicalForm.charAt(i);
             switch (c) {
-                case SEPARATOR_CHAR:
+                case CANONICAL_FORM_SEPARATOR_CHAR:
                     throw new IllegalStateException(canonicalForm); // should never happen
-                case ESCAPE_CHAR:
+                case CANONICAL_FORM_ESCAPE_CHAR:
                     i++;
                     c = canonicalForm.charAt(i);
                     // fall through to 'default'
@@ -315,9 +346,9 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
         for (int i = 0, ln = canonicalForm.length(); i < ln; i++) {
             char c = canonicalForm.charAt(i);
             switch (c) {
-                case SEPARATOR_CHAR:
+                case CANONICAL_FORM_SEPARATOR_CHAR:
                     return true;
-                case ESCAPE_CHAR:
+                case CANONICAL_FORM_ESCAPE_CHAR:
                     i++;
                     break;
             }
@@ -330,6 +361,36 @@ public final class DataverseName implements Serializable, Comparable<DataverseNa
     }
 
     private static void addPartToDisplayForm(CharSequence part, StringBuilder out) {
-        out.append(part).append(SEPARATOR_CHAR);
+        boolean needQuote = false;
+        for (int i = 0, ln = part.length(); i < ln; i++) {
+            char c = part.charAt(i);
+            boolean noQuote = isLetter(c) || c == '_' || (i > 0 && (isDigit(c) || c == '$'));
+            if (!noQuote) {
+                needQuote = true;
+                break;
+            }
+        }
+        if (needQuote) {
+            out.append(DISPLAY_FORM_QUOTE_CHAR);
+        }
+        for (int i = 0, ln = part.length(); i < ln; i++) {
+            char c = part.charAt(i);
+            if (c == DISPLAY_FORM_ESCAPE_CHAR || c == DISPLAY_FORM_QUOTE_CHAR) {
+                out.append(DISPLAY_FORM_ESCAPE_CHAR);
+            }
+            out.append(c);
+        }
+        if (needQuote) {
+            out.append(DISPLAY_FORM_QUOTE_CHAR);
+        }
+        out.append(DISPLAY_FORM_SEPARATOR_CHAR);
+    }
+
+    private static boolean isLetter(char c) {
+        return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+    }
+
+    private static boolean isDigit(char c) {
+        return '0' <= c && c <= '9';
     }
 }
\ No newline at end of file
diff --git a/asterixdb/asterix-common/src/test/java/org/apache/asterix/common/metadata/DataverseNameTest.java b/asterixdb/asterix-common/src/test/java/org/apache/asterix/common/metadata/DataverseNameTest.java
index 1a47a3f..2f0dff5 100644
--- a/asterixdb/asterix-common/src/test/java/org/apache/asterix/common/metadata/DataverseNameTest.java
+++ b/asterixdb/asterix-common/src/test/java/org/apache/asterix/common/metadata/DataverseNameTest.java
@@ -28,7 +28,7 @@ import java.util.List;
 import java.util.function.Supplier;
 
 import org.apache.commons.collections4.ListUtils;
-import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
 import org.junit.Assert;
 import org.junit.Test;
@@ -55,97 +55,114 @@ public class DataverseNameTest {
             // escape character is not allowed
             "c@d");
 
-    private static final List<Pair<String, String>> TEST_SINGLE_PART_NAME_PARAMS = Arrays.asList(
-            // <1-part-name, canonical-form>
-            new Pair<>("abc", "abc"),
-            // with escape character
-            new Pair<>("a@b", "a@@b"),
-            // with separator character
-            new Pair<>("a.b", "a@.b"),
-            // with both escape and separator characters
-            new Pair<>("a@.b", "a@@@.b"));
-
-    private static final List<Pair<List<String>, String>> TEST_MULTI_PART_NAME_PARAMS = Arrays.asList(
-            // <multi-part-name, canonical-form>
-            new Pair<>(Arrays.asList("aa", "bb", "cc"), "aa.bb.cc"),
-            // with escape character
-            new Pair<>(Arrays.asList("a@a@", "@b@b", "@c@c"), "a@@a@@.@@b@@b.@@c@@c"),
-            // with separator character
-            new Pair<>(Arrays.asList("a.a.", ".b.b.", ".c.c"), "a@.a@..@.b@.b@..@.c@.c"),
-            // with both escape and separator characters
-            new Pair<>(Arrays.asList("a@a.", "@b.b@", ".c@c"), "a@@a@..@@b@.b@@.@.c@@c"),
-            // with both escape and separator characters repeated
-            new Pair<>(Arrays.asList("a@@a..", "@@b..b@@", "..c@@c"), "a@@@@a@.@..@@@@b@.@.b@@@@.@.@.c@@@@c"));
+    private static final List<Triple<String, String, String>> TEST_SINGLE_PART_NAME_PARAMS = Arrays.asList(
+            // <1-part-name, canonical-form, display-form>
+            new Triple<>("abz", "abz", "abz"),
+            // upper-case letters
+            new Triple<>("ABZ", "ABZ", "ABZ"),
+            // letters and digits
+            new Triple<>("aA09", "aA09", "aA09"),
+            // with canonical form escape character
+            new Triple<>("a@b", "a@@b", "`a@b`"),
+            // with canonical form separator character
+            new Triple<>("a.b", "a@.b", "`a.b`"),
+            // with canonical form escape and separator characters
+            new Triple<>("a@.b", "a@@@.b", "`a@.b`"),
+            // with display form escape character
+            new Triple<>("a\\b", "a\\b", "`a\\\\b`"));
+
+    private static final List<Triple<List<String>, String, String>> TEST_MULTI_PART_NAME_PARAMS = Arrays.asList(
+            // <multi-part-name, canonical-form, display-form>
+            new Triple<>(Arrays.asList("aa", "bb", "cc"), "aa.bb.cc", "aa.bb.cc"),
+            // mixed case letters, digits
+            new Triple<>(Arrays.asList("az", "AZ", "a09Z"), "az.AZ.a09Z", "az.AZ.a09Z"),
+            // with canonical form escape character
+            new Triple<>(Arrays.asList("a@a@", "@b@b", "@c@c"), "a@@a@@.@@b@@b.@@c@@c", "`a@a@`.`@b@b`.`@c@c`"),
+            // with canonical form separator character
+            new Triple<>(Arrays.asList("a.a.", ".b.b.", ".c.c"), "a@.a@..@.b@.b@..@.c@.c", "`a.a.`.`.b.b.`.`.c.c`"),
+            // with canonical form escape and separator characters
+            new Triple<>(Arrays.asList("a@a.", "@b.b@", ".c@c"), "a@@a@..@@b@.b@@.@.c@@c", "`a@a.`.`@b.b@`.`.c@c`"),
+            // with canonical form escape and separator characters repeated
+            new Triple<>(Arrays.asList("a@@a..", "@@b..b@@", "..c@@c"), "a@@@@a@.@..@@@@b@.@.b@@@@.@.@.c@@@@c",
+                    "`a@@a..`.`@@b..b@@`.`..c@@c`"),
+            // with display form escape character
+            new Triple<>(Arrays.asList("a\\b", "c\\d"), "a\\b.c\\d", "`a\\\\b`.`c\\\\d`"));
 
     @Test
-    public void testBuiltinDataverseName() {
+    public void testBuiltinDataverseName() throws Exception {
         for (String p : TEST_BUILTIN_DATAVERSE_NAME_PARAMS) {
             testBuiltinDataverseNameImpl(p);
         }
     }
 
     @Test
-    public void testSinglePartName() {
-        for (Pair<String, String> p : TEST_SINGLE_PART_NAME_PARAMS) {
-            String singlePart = p.first;
-            String expectedCanonicalForm = p.second;
-            testSinglePartNameImpl(singlePart, expectedCanonicalForm);
+    public void testSinglePartName() throws Exception {
+        for (Triple<String, String, String> t : TEST_SINGLE_PART_NAME_PARAMS) {
+            String singlePart = t.first;
+            String expectedCanonicalForm = t.second;
+            String expectedDisplayForm = t.third;
+            testSinglePartNameImpl(singlePart, expectedCanonicalForm, expectedDisplayForm);
         }
     }
 
     @Test
-    public void testMultiPartName() {
+    public void testMultiPartName() throws Exception {
         // test single part names
-        for (Pair<String, String> p : TEST_SINGLE_PART_NAME_PARAMS) {
-            List<String> parts = Collections.singletonList(p.first);
-            String expectedCanonicalForm = p.second;
-            testMultiPartNameImpl(parts, expectedCanonicalForm);
+        for (Triple<String, String, String> t : TEST_SINGLE_PART_NAME_PARAMS) {
+            List<String> parts = Collections.singletonList(t.first);
+            String expectedCanonicalForm = t.second;
+            String expectedDisplayForm = t.third;
+            testMultiPartNameImpl(parts, expectedCanonicalForm, expectedDisplayForm);
         }
         // test multi part names
-        for (Pair<List<String>, String> p : TEST_MULTI_PART_NAME_PARAMS) {
-            List<String> parts = p.first;
-            String expectedCanonicalForm = p.second;
-            testMultiPartNameImpl(parts, expectedCanonicalForm);
+        for (Triple<List<String>, String, String> t : TEST_MULTI_PART_NAME_PARAMS) {
+            List<String> parts = t.first;
+            String expectedCanonicalForm = t.second;
+            String expectedDisplayForm = t.third;
+            testMultiPartNameImpl(parts, expectedCanonicalForm, expectedDisplayForm);
         }
     }
 
-    private void testBuiltinDataverseNameImpl(String singlePart) {
+    private void testBuiltinDataverseNameImpl(String singlePart) throws Exception {
         DataverseName dvBuiltin = DataverseName.createBuiltinDataverseName(singlePart);
         DataverseName dv = DataverseName.createSinglePartName(singlePart);
         Assert.assertEquals("same-builtin", dv, dvBuiltin);
-        // part = canonical-form = persistent-form for builtins
-        testSinglePartNameImpl(singlePart, singlePart);
+        // part = canonical-form = display-form for builtins
+        testSinglePartNameImpl(singlePart, singlePart, singlePart);
     }
 
-    private void testSinglePartNameImpl(String singlePart, String expectedCanonicalForm) {
+    private void testSinglePartNameImpl(String singlePart, String expectedCanonicalForm, String expectedDisplayForm)
+            throws Exception {
         List<String> parts = Collections.singletonList(singlePart);
 
         // construction using createSinglePartName()
         DataverseName dvConstr1 = DataverseName.createSinglePartName(singlePart);
-        testDataverseNameImpl(dvConstr1, parts, expectedCanonicalForm);
+        testDataverseNameImpl(dvConstr1, parts, expectedCanonicalForm, expectedDisplayForm);
 
         // construction using create(list)
         DataverseName dvConstr2 = DataverseName.create(Collections.singletonList(singlePart));
-        testDataverseNameImpl(dvConstr2, parts, expectedCanonicalForm);
+        testDataverseNameImpl(dvConstr2, parts, expectedCanonicalForm, expectedDisplayForm);
 
         // construction using create(list, from, to)
         DataverseName dvConstr3 = DataverseName.create(Arrays.asList(null, null, singlePart, null, null), 2, 3);
-        testDataverseNameImpl(dvConstr3, parts, expectedCanonicalForm);
+        testDataverseNameImpl(dvConstr3, parts, expectedCanonicalForm, expectedDisplayForm);
     }
 
-    private void testMultiPartNameImpl(List<String> parts, String expectedCanonicalForm) {
+    private void testMultiPartNameImpl(List<String> parts, String expectedCanonicalForm, String expectedDisplayForm)
+            throws Exception {
         // construction using create(list)
         DataverseName dvConstr1 = DataverseName.create(parts);
-        testDataverseNameImpl(dvConstr1, parts, expectedCanonicalForm);
+        testDataverseNameImpl(dvConstr1, parts, expectedCanonicalForm, expectedDisplayForm);
 
         // construction using create(list, from, to)
         List<String> dv2InputParts =
                 ListUtils.union(ListUtils.union(Arrays.asList(null, null), parts), Arrays.asList(null, null));
         DataverseName dvConstr2 = DataverseName.create(dv2InputParts, 2, 2 + parts.size());
-        testDataverseNameImpl(dvConstr2, parts, expectedCanonicalForm);
+        testDataverseNameImpl(dvConstr2, parts, expectedCanonicalForm, expectedDisplayForm);
     }
 
-    private void testDataverseNameImpl(DataverseName dataverseName, List<String> parts, String expectedCanonicalForm) {
+    protected void testDataverseNameImpl(DataverseName dataverseName, List<String> parts, String expectedCanonicalForm,
+            String expectedDisplayForm) throws Exception {
         boolean isMultiPart = parts.size() > 1;
         Assert.assertEquals("is-multipart", isMultiPart, dataverseName.isMultiPart());
 
@@ -156,15 +173,16 @@ public class DataverseNameTest {
         Assert.assertArrayEquals("get-parts-1", parts.toArray(), outParts.toArray());
 
         // test canonical form
-        Assert.assertEquals("canonical-form", expectedCanonicalForm, dataverseName.getCanonicalForm());
+        String canonicalForm = dataverseName.getCanonicalForm();
+        Assert.assertEquals("canonical-form", expectedCanonicalForm, canonicalForm);
         DataverseName dvFromCanonical = DataverseName.createFromCanonicalForm(expectedCanonicalForm);
         Assert.assertEquals("canonical-form-round-trip", dataverseName, dvFromCanonical);
         Assert.assertEquals("canonical-form-round-trip-cmp", 0, dataverseName.compareTo(dvFromCanonical));
         Assert.assertEquals("canonical-form-round-trip-hash", dataverseName.hashCode(), dvFromCanonical.hashCode());
 
         // test display form
-        String expectedDisplayForm = String.join(".", parts);
-        Assert.assertEquals("display-form", expectedDisplayForm, dataverseName.toString());
+        String displayForm = dataverseName.toString();
+        Assert.assertEquals("display-form", expectedDisplayForm, displayForm);
     }
 
     @Test
diff --git a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
index e903977..d899632 100644
--- a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
+++ b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
@@ -291,6 +291,7 @@ class AQLParser extends ScopeChecker implements IParser {
         //st.accept(new AQLPrintVisitor(), 0);
     }
 
+    @Override
     public List<Statement> parse() throws CompilationException {
         return parseImpl(new ParseFunction<List<Statement>>() {
             @Override
@@ -300,7 +301,8 @@ class AQLParser extends ScopeChecker implements IParser {
         });
     }
 
-    private Expression parseExpression() throws CompilationException {
+    @Override
+    public Expression parseExpression() throws CompilationException {
         return parseImpl(new ParseFunction<Expression>() {
             @Override
             public Expression parse() throws ParseException {
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
index 3f73789..bd05f28 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/IParser.java
@@ -30,6 +30,8 @@ public interface IParser {
 
     List<Statement> parse() throws CompilationException;
 
+    Expression parseExpression() throws CompilationException;
+
     FunctionDecl parseFunctionBody(FunctionSignature signature, List<String> paramNames) throws CompilationException;
 
     /**
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/DataverseNameUtils.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/DataverseNameUtils.java
index 1eb3868..1a3f26a 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/DataverseNameUtils.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/DataverseNameUtils.java
@@ -76,7 +76,7 @@ public class DataverseNameUtils {
         dataverseName.getParts(dataverseNameParts);
         for (int i = 0, ln = dataverseNameParts.size(); i < ln; i++) {
             if (i > 0) {
-                sb.append(DataverseName.SEPARATOR_CHAR);
+                sb.append(DataverseName.CANONICAL_FORM_SEPARATOR_CHAR);
             }
             sb.append(normalize(dataverseNameParts.get(i)));
         }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
index f5caccf..290f9ea 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
@@ -1026,7 +1026,7 @@ public abstract class FormatPrintVisitor implements ILangVisitor<Void, Integer>
         dataverseName.getParts(dataverseNameParts);
         for (int i = 0, ln = dataverseNameParts.size(); i < ln; i++) {
             if (i > 0) {
-                sb.append(DataverseName.SEPARATOR_CHAR);
+                sb.append(".");
             }
             sb.append(normalize(dataverseNameParts.get(i)));
         }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
index 9336c79..ba6d9e4 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppStatementUtil.java
@@ -108,7 +108,7 @@ public class SqlppStatementUtil {
 
     /**
      * Encloses each part of the {@param dataverseName} in back-ticks and concatenates them with
-     * {@link DataverseName#SEPARATOR_CHAR} separator
+     * {@link #DOT} separator
      * @param stringBuilder where the dataverse name will be appended
      * @param dataverseName a dataverse name which could be a valid one or one that needs to be delimited
      * @return {@param stringBuilder} with the <i>delimited</i> dataverseName appended
@@ -117,7 +117,7 @@ public class SqlppStatementUtil {
         List<String> parts = dataverseName.getParts();
         for (int i = 0, ln = parts.size(); i < ln; i++) {
             if (i > 0) {
-                stringBuilder.append(DataverseName.SEPARATOR_CHAR);
+                stringBuilder.append(DOT);
             }
             enclose(stringBuilder, parts.get(i));
         }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 347dc18..b23492a 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -343,7 +343,8 @@ class SQLPPParser extends ScopeChecker implements IParser {
         });
     }
 
-    private Expression parseExpression() throws CompilationException {
+    @Override
+    public Expression parseExpression() throws CompilationException {
         return parseImpl(new ParseFunction<Expression>() {
             @Override
             public Expression parse() throws ParseException {
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index f74e730..cf3ea0e 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -1592,6 +1592,8 @@ public class BuiltinFunctions {
 
     public static final FunctionIdentifier DECODE_DATAVERSE_NAME =
             new FunctionIdentifier(FunctionConstants.ASTERIX_NS, "decode-dataverse-name", 1);
+    public static final FunctionIdentifier DECODE_DATAVERSE_DISPLAY_NAME =
+            new FunctionIdentifier(FunctionConstants.ASTERIX_NS, "decode-dataverse-display-name", 1);
 
     static {
         // first, take care of Algebricks builtin functions
@@ -2358,7 +2360,8 @@ public class BuiltinFunctions {
         addFunction(META, OpenARecordTypeComputer.INSTANCE, true);
         addPrivateFunction(META_KEY, AnyTypeComputer.INSTANCE, false);
 
-        addFunction(DECODE_DATAVERSE_NAME, OrderedListOfAStringTypeComputer.INSTANCE, true);
+        addFunction(DECODE_DATAVERSE_NAME, OrderedListOfAStringTypeComputer.INSTANCE_NULLABLE, true);
+        addFunction(DECODE_DATAVERSE_DISPLAY_NAME, AStringTypeComputer.INSTANCE_NULLABLE, true);
 
         addPrivateFunction(COLLECTION_TO_SEQUENCE, CollectionToSequenceTypeComputer.INSTANCE, true);
 
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseDisplayNameDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseDisplayNameDescriptor.java
new file mode 100644
index 0000000..a622fe8
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseDisplayNameDescriptor.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.runtime.evaluators.functions;
+
+import java.io.IOException;
+
+import org.apache.asterix.common.annotations.MissingNullInOutFunction;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.UTF8StringPointable;
+
+@MissingNullInOutFunction
+public class DecodeDataverseDisplayNameDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    public static final IFunctionDescriptorFactory FACTORY = DecodeDataverseDisplayNameDescriptor::new;
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new AbstractUnaryStringStringEval(ctx, args[0], getIdentifier(), sourceLoc) {
+
+                    private final StringBuilder sb = new StringBuilder();
+
+                    @Override
+                    void process(UTF8StringPointable inputString, IPointable resultPointable) throws IOException {
+                        String dataverseCanonicalName = inputString.toString();
+
+                        sb.setLength(0);
+                        DataverseName.getDisplayFormFromCanonicalForm(dataverseCanonicalName, sb);
+
+                        resultBuilder.reset(resultArray, inputString.getUTF8Length());
+                        resultBuilder.appendString(sb);
+                        resultBuilder.finish();
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return BuiltinFunctions.DECODE_DATAVERSE_DISPLAY_NAME;
+    }
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseNameDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseNameDescriptor.java
index 1ec6a5a..fbbe5c5 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseNameDescriptor.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/DecodeDataverseNameDescriptor.java
@@ -101,10 +101,8 @@ public final class DecodeDataverseNameDescriptor extends AbstractScalarFunctionD
                         strPtr.set(bytes, offset + 1, len - 1);
                         String dataverseCanonicalName = strPtr.toString();
 
-                        DataverseName dataverseName = DataverseName.createFromCanonicalForm(dataverseCanonicalName);
-
                         dataverseNameParts.clear();
-                        dataverseName.getParts(dataverseNameParts);
+                        DataverseName.getPartsFromCanonicalForm(dataverseCanonicalName, dataverseNameParts);
 
                         resultStorage.reset();
                         listBuilder.reset(listType);
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
index e1c9164..42e27da 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
@@ -331,6 +331,7 @@ import org.apache.asterix.runtime.evaluators.functions.CreatePolygonDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.CreateQueryUIDDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.CreateRectangleDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.CreateUUIDDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.DecodeDataverseDisplayNameDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.DecodeDataverseNameDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.DeepEqualityDescriptor;
 import org.apache.asterix.runtime.evaluators.functions.FullTextContainsDescriptor;
@@ -1214,6 +1215,7 @@ public final class FunctionCollection implements IFunctionCollection {
 
         // Other functions
         fc.add(DecodeDataverseNameDescriptor.FACTORY);
+        fc.add(DecodeDataverseDisplayNameDescriptor.FACTORY);
         fc.add(RandomWithSeedDescriptor.FACTORY);
 
         ServiceLoader.load(IFunctionRegistrant.class).iterator().forEachRemaining(c -> c.register(fc));
diff --git a/hyracks-fullstack/hyracks/hyracks-data/hyracks-data-std/src/main/java/org/apache/hyracks/data/std/util/UTF8StringBuilder.java b/hyracks-fullstack/hyracks/hyracks-data/hyracks-data-std/src/main/java/org/apache/hyracks/data/std/util/UTF8StringBuilder.java
index 147ac86..9913a39 100644
--- a/hyracks-fullstack/hyracks/hyracks-data/hyracks-data-std/src/main/java/org/apache/hyracks/data/std/util/UTF8StringBuilder.java
+++ b/hyracks-fullstack/hyracks/hyracks-data/hyracks-data-std/src/main/java/org/apache/hyracks/data/std/util/UTF8StringBuilder.java
@@ -36,7 +36,7 @@ public class UTF8StringBuilder extends AbstractVarLenObjectBuilder {
         }
     }
 
-    public void appendString(String string) throws IOException {
+    public void appendString(CharSequence string) throws IOException {
         for (int i = 0; i < string.length(); i++) {
             appendChar(string.charAt(i));
         }