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

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


The following commit(s) were added to refs/heads/master by this push:
     new 00c22f18328 Support PostgreSQL escaped and Unicode identifiers (#40220)
00c22f18328 is described below

commit 00c22f1832839b0b03a5f21fbc6f2279ab90ecd5
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 18 20:28:28 2026 +0800

    Support PostgreSQL escaped and Unicode identifiers (#40220)
    
    * Support PostgreSQL escaped and Unicode identifiers
    
    * Preserve decoded PostgreSQL identifiers in projection SQL
---
 .../pojo/generic/SubstitutableColumnNameToken.java |   9 +-
 .../generic/SubstitutableColumnNameTokenTest.java  |  41 ++++
 .../src/main/antlr4/imports/postgresql/Literals.g4 |   2 +-
 .../statement/PostgreSQLStatementVisitor.java      | 214 +++++++++++----------
 .../type/PostgreSQLDALStatementVisitor.java        |   3 +-
 .../type/PostgreSQLDCLStatementVisitor.java        |  18 +-
 .../type/PostgreSQLDDLStatementVisitor.java        |   6 +-
 .../core/value/identifier/IdentifierValue.java     |  23 ++-
 .../core/value/identifier/IdentifierValueTest.java |  43 +++++
 .../main/resources/case/dml/select-expression.xml  |  20 ++
 .../sql/supported/dml/select-expression.xml        |   2 +
 .../postgresql/InternalPostgreSQLParserIT.java     | 117 +++++++++++
 12 files changed, 380 insertions(+), 118 deletions(-)

diff --git 
a/infra/rewrite/core/src/main/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameToken.java
 
b/infra/rewrite/core/src/main/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameToken.java
index 417ea3099e3..55bfdc02da4 100644
--- 
a/infra/rewrite/core/src/main/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameToken.java
+++ 
b/infra/rewrite/core/src/main/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameToken.java
@@ -35,6 +35,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.Iden
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Substitutable column name token.
@@ -112,13 +113,17 @@ public final class SubstitutableColumnNameToken extends 
SQLToken implements Subs
         if (columnProjection.getOwner().isPresent()) {
             IdentifierValue owner = columnProjection.getOwner().get();
             String actualTableOwner = 
logicActualTableNames.getOrDefault(owner.getValue(), owner.getValue());
-            builder.append(getValueWithQuoteCharacters(new 
IdentifierValue(actualTableOwner, owner.getQuoteCharacter()))).append('.');
+            IdentifierValue actualOwner = Objects.equals(actualTableOwner, 
owner.getValue()) ? owner : new IdentifierValue(actualTableOwner, 
owner.getQuoteCharacter());
+            
builder.append(getValueWithQuoteCharacters(actualOwner)).append('.');
         }
         
builder.append(getValueWithQuoteCharacters(columnProjection.getName()));
         columnProjection.getRightParentheses().ifPresent(optional -> 
builder.append(")"));
     }
     
     private String getValueWithQuoteCharacters(final IdentifierValue 
identifierValue) {
-        return QuoteCharacter.NONE == identifierValue.getQuoteCharacter() ? 
identifierValue.getValue() : quoteCharacter.wrap(identifierValue.getValue());
+        if (QuoteCharacter.NONE == identifierValue.getQuoteCharacter()) {
+            return identifierValue.getValue();
+        }
+        return quoteCharacter == identifierValue.getQuoteCharacter() ? 
identifierValue.getValueWithQuoteCharacters() : 
quoteCharacter.wrap(identifierValue.getValue());
     }
 }
diff --git 
a/infra/rewrite/core/src/test/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameTokenTest.java
 
b/infra/rewrite/core/src/test/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameTokenTest.java
index f5ffbe4bc54..6a5b8c9ffa4 100644
--- 
a/infra/rewrite/core/src/test/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameTokenTest.java
+++ 
b/infra/rewrite/core/src/test/java/org/apache/shardingsphere/infra/rewrite/sql/token/common/pojo/generic/SubstitutableColumnNameTokenTest.java
@@ -18,23 +18,33 @@
 package org.apache.shardingsphere.infra.rewrite.sql.token.common.pojo.generic;
 
 import 
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import 
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
 import 
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.SubqueryProjection;
+import org.apache.shardingsphere.infra.route.context.RouteMapper;
 import org.apache.shardingsphere.infra.route.context.RouteUnit;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.SubqueryProjectionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedConstruction;
 
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.when;
 
 class SubstitutableColumnNameTokenTest {
     
@@ -71,4 +81,35 @@ class SubstitutableColumnNameTokenTest {
                         new ColumnProjection(null, "name", null, 
databaseType), new IdentifierValue("name"), databaseType));
         assertThat(new SubstitutableColumnNameToken(0, 1, projections, 
databaseType).toString(mock(RouteUnit.class)), is("temp.id AS id, name AS 
name"));
     }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("getQuotedIdentifiers")
+    void assertToStringWithQuotedIdentifiers(final String name, final 
IdentifierValue owner, final IdentifierValue columnName, final IdentifierValue 
alias, final Collection<RouteMapper> tableMappers,
+                                             final String expected) {
+        DialectDatabaseMetaData databaseMetaData = 
mock(DialectDatabaseMetaData.class);
+        
when(databaseMetaData.getQuoteCharacter()).thenReturn(QuoteCharacter.QUOTE);
+        Collection<Projection> projections = Collections.singletonList(new 
ColumnProjection(owner, columnName, alias, databaseType));
+        RouteUnit routeUnit = new RouteUnit(new RouteMapper("foo_ds", 
"foo_ds"), tableMappers);
+        try (
+                MockedConstruction<DatabaseTypeRegistry> ignored = 
mockConstruction(DatabaseTypeRegistry.class,
+                        (registry, context) -> 
when(registry.getDialectDatabaseMetaData()).thenReturn(databaseMetaData))) {
+            assertThat(new SubstitutableColumnNameToken(0, 1, projections, 
databaseType).toString(routeUnit), is(expected));
+        }
+    }
+    
+    private static Stream<Arguments> getQuotedIdentifiers() {
+        return Stream.of(
+                Arguments.of("decoded alias", null, new 
IdentifierValue("foo_column"), 
IdentifierValue.fromDecodedQuotedName("foo\"bar"), Collections.emptyList(), 
"foo_column AS \"foo\"\"bar\""),
+                Arguments.of("adjacent decoded alias quotes", null, new 
IdentifierValue("foo_column"), 
IdentifierValue.fromDecodedQuotedName("foo\"\"bar"), Collections.emptyList(),
+                        "foo_column AS \"foo\"\"\"\"bar\""),
+                Arguments.of("decoded column", null, 
IdentifierValue.fromDecodedQuotedName("foo\"bar"), null, 
Collections.emptyList(), "\"foo\"\"bar\""),
+                Arguments.of("decoded owner without mapping", 
IdentifierValue.fromDecodedQuotedName("foo\"table"), new 
IdentifierValue("foo_column"), null, Collections.emptyList(),
+                        "\"foo\"\"table\".foo_column"),
+                Arguments.of("decoded owner with same-name mapping", 
IdentifierValue.fromDecodedQuotedName("foo\"table"), new 
IdentifierValue("foo_column"), null,
+                        Collections.singletonList(new 
RouteMapper("foo\"table", "foo\"table")), "\"foo\"\"table\".foo_column"),
+                Arguments.of("renamed owner", 
IdentifierValue.fromDecodedQuotedName("foo\"table"), new 
IdentifierValue("foo_column"), null,
+                        Collections.singletonList(new 
RouteMapper("foo\"table", "bar_table")), "\"bar_table\".foo_column"),
+                Arguments.of("legacy raw doubled quotes", null, new 
IdentifierValue("foo\"\"bar", QuoteCharacter.QUOTE), null, 
Collections.emptyList(), "\"foo\"\"bar\""),
+                Arguments.of("back-quote conversion", null, new 
IdentifierValue("foo_column", QuoteCharacter.BACK_QUOTE), null, 
Collections.emptyList(), "\"foo_column\""));
+    }
 }
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/antlr4/imports/postgresql/Literals.g4
 
b/parser/sql/engine/dialect/postgresql/src/main/antlr4/imports/postgresql/Literals.g4
index 92e30e36f4a..5179be7ea1e 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/antlr4/imports/postgresql/Literals.g4
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/antlr4/imports/postgresql/Literals.g4
@@ -19,7 +19,7 @@ lexer grammar Literals;
 
 import Alphabet, Symbol;
 
-IDENTIFIER_: IDENTIFIER_START_CHAR_ IDENTIFIER_CHAR_* | DQ_ ~'"'+ DQ_;
+IDENTIFIER_: IDENTIFIER_START_CHAR_ IDENTIFIER_CHAR_* | DQ_ (DQ_ DQ_ | ~'"')+ 
DQ_;
 STRING_: E ESCAPE_STRING_LITERAL_ (STRING_CONTINUATION_ 
ESCAPE_STRING_LITERAL_)* | STRING_LITERAL_ (STRING_CONTINUATION_ 
STRING_LITERAL_)*;
 NUMBER_: (INT_ (DOT_ INT_?)? | DOT_ INT_) (E (PLUS_ | MINUS_)? INT_)?;
 HEX_DIGIT_: X SQ_ HEX_* SQ_ (STRING_CONTINUATION_ SQ_ HEX_* SQ_)*;
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
index 09ce8672ff2..d956fa21379 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
@@ -295,7 +295,84 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     @Override
     public final ASTNode visitIdentifier(final IdentifierContext ctx) {
         UnreservedWordContext unreservedWord = ctx.unreservedWord();
-        return null == unreservedWord ? new IdentifierValue(ctx.getText()) : 
visit(unreservedWord);
+        return null == unreservedWord ? createIdentifierValue(ctx.getText()) : 
visit(unreservedWord);
+    }
+    
+    protected final IdentifierValue createIdentifierValue(final String text) {
+        boolean unicode = text.regionMatches(true, 0, "U&", 0, 2);
+        if (!unicode && !text.startsWith("\"")) {
+            return new IdentifierValue(text);
+        }
+        int stopIndex = text.lastIndexOf('"');
+        String value = text.substring(unicode ? 3 : 1, 
stopIndex).replace("\"\"", "\"");
+        if (unicode) {
+            char escape = stopIndex + 1 == text.length() ? '\\' : 
getUnicodeEscapeCharacter(text.substring(stopIndex + 8));
+            value = decodeUnicode(value, escape);
+        }
+        return IdentifierValue.fromDecodedQuotedName(value);
+    }
+    
+    private char getUnicodeEscapeCharacter(final String text) {
+        String literal = concatenateStringLiterals(text);
+        String value = literal.substring(1, literal.length() - 1);
+        if (!text.regionMatches(true, 0, "E", 0, 1) || '\\' != 
value.charAt(0)) {
+            return value.charAt(0);
+        }
+        char character = value.charAt(1);
+        if ('x' == character || 'u' == character || 'U' == character) {
+            return (char) Integer.parseInt(value.substring(2), 16);
+        }
+        if ('0' <= character && '7' >= character) {
+            return (char) Integer.parseInt(value.substring(1), 8);
+        }
+        return 'b' == character ? '\b' : character;
+    }
+    
+    private String concatenateStringLiterals(final String text) {
+        StringBuilder result = new StringBuilder("'");
+        boolean escaped = 'E' == text.charAt(0) || 'e' == text.charAt(0);
+        int index = text.indexOf('\'') + 1;
+        while (index < text.length()) {
+            char character = text.charAt(index);
+            if (escaped && '\\' == character) {
+                result.append(character).append(text.charAt(++index));
+            } else if ('\'' != character) {
+                result.append(character);
+            } else if (index + 1 < text.length() && '\'' == text.charAt(index 
+ 1)) {
+                result.append("''");
+                index++;
+            } else {
+                int nextIndex = text.indexOf('\'', index + 1);
+                if (-1 == nextIndex) {
+                    break;
+                }
+                index = nextIndex;
+            }
+            index++;
+        }
+        return result.append('\'').toString();
+    }
+    
+    private String decodeUnicode(final String value, final char escape) {
+        StringBuilder result = new StringBuilder();
+        int index = 0;
+        while (index < value.length()) {
+            char character = value.charAt(index);
+            if (escape != character) {
+                result.append(character);
+                index++;
+            } else if (escape == value.charAt(index + 1)) {
+                result.append(escape);
+                index += 2;
+            } else {
+                int startIndex = index + 1;
+                int length = '+' == value.charAt(startIndex) ? 6 : 4;
+                startIndex += 6 == length ? 1 : 0;
+                
result.appendCodePoint(Integer.parseInt(value.substring(startIndex, startIndex 
+ length), 16));
+                index = startIndex + length;
+            }
+        }
+        return result.toString();
     }
     
     @Override
@@ -657,7 +734,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
             return createWindowItem(ctx.windowSpecification());
         }
         WindowItemSegment result = new 
WindowItemSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
-        result.setWindowName(new IdentifierValue(ctx.colId().getText()));
+        result.setWindowName(createIdentifierValue(ctx.colId().getText()));
         return result;
     }
     
@@ -830,69 +907,6 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         return new StringLiteralValue(text);
     }
     
-    private String concatenateStringLiterals(final String text) {
-        StringBuilder result = new StringBuilder("'");
-        boolean escaped = 'E' == text.charAt(0) || 'e' == text.charAt(0);
-        int index = text.indexOf('\'') + 1;
-        while (index < text.length()) {
-            char character = text.charAt(index);
-            if (escaped && '\\' == character) {
-                result.append(character).append(text.charAt(++index));
-            } else if ('\'' != character) {
-                result.append(character);
-            } else if (index + 1 < text.length() && '\'' == text.charAt(index 
+ 1)) {
-                result.append("''");
-                index++;
-            } else {
-                int nextIndex = text.indexOf('\'', index + 1);
-                if (-1 == nextIndex) {
-                    break;
-                }
-                index = nextIndex;
-            }
-            index++;
-        }
-        return result.append('\'').toString();
-    }
-    
-    private char getUnicodeEscapeCharacter(final String text) {
-        String literal = concatenateStringLiterals(text);
-        String value = literal.substring(1, literal.length() - 1);
-        if (!text.regionMatches(true, 0, "E", 0, 1) || '\\' != 
value.charAt(0)) {
-            return value.charAt(0);
-        }
-        char character = value.charAt(1);
-        if ('x' == character || 'u' == character || 'U' == character) {
-            return (char) Integer.parseInt(value.substring(2), 16);
-        }
-        if ('0' <= character && '7' >= character) {
-            return (char) Integer.parseInt(value.substring(1), 8);
-        }
-        return 'b' == character ? '\b' : character;
-    }
-    
-    private String decodeUnicode(final String value, final char escape) {
-        StringBuilder result = new StringBuilder();
-        int index = 0;
-        while (index < value.length()) {
-            char character = value.charAt(index);
-            if (escape != character) {
-                result.append(character);
-                index++;
-            } else if (escape == value.charAt(index + 1)) {
-                result.append(escape);
-                index += 2;
-            } else {
-                int startIndex = index + 1;
-                int length = '+' == value.charAt(startIndex) ? 6 : 4;
-                startIndex += 6 == length ? 1 : 0;
-                
result.appendCodePoint(Integer.parseInt(value.substring(startIndex, startIndex 
+ length), 16));
-                index = startIndex + length;
-            }
-        }
-        return result.toString();
-    }
-    
     @Override
     public ASTNode visitColumnref(final ColumnrefContext ctx) {
         if (null != ctx.indirection()) {
@@ -906,17 +920,17 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         }
         if (null != ctx.indirection() && null != 
ctx.indirection().indirectionEl().DOT_ASTERISK_()) {
             ColumnSegment result = new 
ColumnSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue("*"));
-            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
             return result;
         }
         if (null != ctx.indirection() && null != 
ctx.indirection().indirectionEl().attrName()) {
             AttrNameContext attrName = 
ctx.indirection().indirectionEl().attrName();
-            ColumnSegment result = new 
ColumnSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue(attrName.getText()));
-            OwnerSegment owner = new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()));
+            ColumnSegment result = new 
ColumnSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), 
createIdentifierValue(attrName.getText()));
+            OwnerSegment owner = new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText()));
             result.setOwner(owner);
             return result;
         }
-        return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()));
+        return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText()));
     }
     
     private ColumnSegment createSubscriptColumn(final ColumnrefContext ctx, 
final Collection<IndirectionElContext> elements) {
@@ -930,10 +944,10 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
             }
         }
         if (null == columnName) {
-            return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()));
+            return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText()));
         }
-        ColumnSegment result = new ColumnSegment(ctx.start.getStartIndex(), 
columnName.stop.getStopIndex(), new IdentifierValue(columnName.getText()));
-        result.setOwner(new OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+        ColumnSegment result = new ColumnSegment(ctx.start.getStartIndex(), 
columnName.stop.getStopIndex(), createIdentifierValue(columnName.getText()));
+        result.setOwner(new OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
         return result;
     }
     
@@ -1150,7 +1164,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         SimpleTableSegment result = (SimpleTableSegment) 
visit(ctx.qualifiedName());
         if (null != ctx.AS()) {
             ColIdContext colId = ctx.colId();
-            result.setAlias(new AliasSegment(colId.start.getStartIndex(), 
colId.stop.getStopIndex(), new IdentifierValue(colId.getText())));
+            result.setAlias(new AliasSegment(colId.start.getStartIndex(), 
colId.stop.getStopIndex(), createIdentifierValue(colId.getText())));
         }
         return result;
     }
@@ -1172,8 +1186,8 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     public ASTNode visitQualifiedName(final QualifiedNameContext ctx) {
         if (null != ctx.indirection()) {
             AttrNameContext attrName = 
ctx.indirection().indirectionEl().attrName();
-            TableNameSegment tableName = new 
TableNameSegment(attrName.start.getStartIndex(), attrName.stop.getStopIndex(), 
new IdentifierValue(attrName.getText()));
-            OwnerSegment owner = new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()));
+            TableNameSegment tableName = new 
TableNameSegment(attrName.start.getStartIndex(), attrName.stop.getStopIndex(), 
createIdentifierValue(attrName.getText()));
+            OwnerSegment owner = new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText()));
             SimpleTableSegment result = new SimpleTableSegment(tableName);
             if (null != ctx.indirection().indirection()) {
                 OwnerSegment tableOwner = 
createTableOwner(ctx.indirection().indirection());
@@ -1184,7 +1198,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
             }
             return result;
         }
-        return new SimpleTableSegment(new 
TableNameSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+        return new SimpleTableSegment(new 
TableNameSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
     }
     
     @SuppressWarnings("unchecked")
@@ -1228,11 +1242,11 @@ public abstract class PostgreSQLStatementVisitor 
extends PostgreSQLStatementPars
         visitIndirectionExpressions(ctx.optIndirection());
         if (null != ctx.optIndirection().indirectionEl() && null != 
ctx.optIndirection().indirectionEl().attrName()) {
             AttrNameContext attrName = 
ctx.optIndirection().indirectionEl().attrName();
-            ColumnSegment result = new 
ColumnSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue(attrName.getText()));
-            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+            ColumnSegment result = new 
ColumnSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), 
createIdentifierValue(attrName.getText()));
+            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
             return result;
         }
-        return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()));
+        return new ColumnSegment(ctx.colId().start.getStartIndex(), 
ctx.stop.getStopIndex(), createIdentifierValue(ctx.colId().getText()));
     }
     
     private void visitIndirectionExpressions(final OptIndirectionContext ctx) {
@@ -1301,7 +1315,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     public ASTNode visitSetTarget(final SetTargetContext ctx) {
         visitIndirectionExpressions(ctx.optIndirection());
         List<IdentifierValue> fieldAttributes = 
getSetTargetFieldAttributes(ctx.optIndirection());
-        ColumnSegment result = new 
ColumnSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue(ctx.colId().getText()));
+        ColumnSegment result = new 
ColumnSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), 
createIdentifierValue(ctx.colId().getText()));
         if (!fieldAttributes.isEmpty()) {
             result.setNestedObjectAttributes(fieldAttributes);
         }
@@ -1319,7 +1333,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
                     () -> new SQLParsingException("Only field indirection is 
supported in assignment targets"));
             AttrNameContext attrName = current.indirectionEl().attrName();
             if (null != attrName) {
-                result.add(0, new IdentifierValue(attrName.getText()));
+                result.add(0, createIdentifierValue(attrName.getText()));
             }
             current = current.optIndirection();
         }
@@ -1330,7 +1344,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     public ASTNode visitRelationExprOptAlias(final RelationExprOptAliasContext 
ctx) {
         SimpleTableSegment result = (SimpleTableSegment) 
visit(ctx.relationExpr().qualifiedName());
         if (null != ctx.colId()) {
-            result.setAlias(new 
AliasSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue(ctx.colId().getText())));
+            result.setAlias(new 
AliasSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), 
createIdentifierValue(ctx.colId().getText())));
         }
         return result;
     }
@@ -1415,7 +1429,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     public ASTNode visitReturningClause(final ReturningClauseContext ctx) {
         ReturningSegment result = new 
ReturningSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), 
(ProjectionsSegment) visit(ctx.targetList()));
         for (ReturningOptionContext each : ctx.returningOption()) {
-            AliasSegment alias = new 
AliasSegment(each.colId().start.getStartIndex(), 
each.colId().stop.getStopIndex(), new IdentifierValue(each.colId().getText()));
+            AliasSegment alias = new 
AliasSegment(each.colId().start.getStartIndex(), 
each.colId().stop.getStopIndex(), 
createIdentifierValue(each.colId().getText()));
             if (null != each.OLD()) {
                 result.setOldAlias(alias);
             } else {
@@ -1538,12 +1552,12 @@ public abstract class PostgreSQLStatementVisitor 
extends PostgreSQLStatementPars
     }
     
     private ColumnSegment createColumnSegment(final ColIdContext ctx) {
-        return new ColumnSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), new IdentifierValue(ctx.getText()));
+        return new ColumnSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), createIdentifierValue(ctx.getText()));
     }
     
     @Override
     public ASTNode visitAlias(final AliasContext ctx) {
-        return new AliasSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), new IdentifierValue(ctx.identifier().getText()));
+        return new AliasSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), createIdentifierValue(ctx.identifier().getText()));
     }
     
     @Override
@@ -1665,7 +1679,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     @Override
     public ASTNode visitWindowDefinition(final WindowDefinitionContext ctx) {
         WindowItemSegment result = new 
WindowItemSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
-        result.setWindowName(new IdentifierValue(ctx.colId().getText()));
+        result.setWindowName(createIdentifierValue(ctx.colId().getText()));
         setWindowSpecification(ctx.windowSpecification(), result);
         return result;
     }
@@ -1673,7 +1687,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     private WindowItemSegment createWindowItem(final 
WindowSpecificationContext ctx) {
         WindowItemSegment result = new 
WindowItemSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
         if (null != ctx.existingWindowName()) {
-            result.setWindowName(new 
IdentifierValue(ctx.existingWindowName().colId().getText()));
+            
result.setWindowName(createIdentifierValue(ctx.existingWindowName().colId().getText()));
         }
         setWindowSpecification(ctx, result);
         return result;
@@ -1748,7 +1762,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         ProjectionSegment result = createProjectionSegment(ctx, ctx.aExpr());
         ParserRuleContext alias = null == ctx.colLabel() ? ctx.identifier() : 
ctx.colLabel();
         if (null != alias) {
-            ((AliasAvailable) result).setAlias(new 
AliasSegment(alias.start.getStartIndex(), alias.stop.getStopIndex(), new 
IdentifierValue(alias.getText())));
+            ((AliasAvailable) result).setAlias(new 
AliasSegment(alias.start.getStartIndex(), alias.stop.getStopIndex(), 
createIdentifierValue(alias.getText())));
         }
         return result;
     }
@@ -1759,7 +1773,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         }
         if (null != ctx.DOT_ASTERISK_()) {
             ShorthandProjectionSegment result = new 
ShorthandProjectionSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex());
-            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+            result.setOwner(new 
OwnerSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
             return result;
         }
         if (null != ctx.aExpr()) {
@@ -1893,7 +1907,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         } else if (null != ctx.funcAliasClause()) {
             if (null != ctx.funcAliasClause().colId()) {
                 ColIdContext alias = ctx.funcAliasClause().colId();
-                result.setAlias(new AliasSegment(alias.start.getStartIndex(), 
alias.stop.getStopIndex(), new IdentifierValue(alias.getText())));
+                result.setAlias(new AliasSegment(alias.start.getStartIndex(), 
alias.stop.getStopIndex(), createIdentifierValue(alias.getText())));
             }
             
result.getColumns().addAll(createTableFunctionColumns(ctx.funcAliasClause().tableFuncElementList().tableFuncElement()));
         }
@@ -1968,7 +1982,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
                 defaultString = getOriginalText(each.bExpr());
             } else {
                 stringsAsIdentifiers.add(new 
XmlNamespaceStringAsIdentifierSegment(each.start.getStartIndex(), 
each.stop.getStopIndex(), getOriginalText(each.bExpr()),
-                        new 
IdentifierValue(each.identifier().getText()).getValue(), 
getOriginalText(each)));
+                        
createIdentifierValue(each.identifier().getText()).getValue(), 
getOriginalText(each)));
             }
         }
         XmlNamespacesClauseSegment result = new 
XmlNamespacesClauseSegment(ctx.xmlNamespaceList().start.getStartIndex(), 
ctx.xmlNamespaceList().stop.getStopIndex(), defaultString,
@@ -1994,7 +2008,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
                 params.add(expression);
             }
         }
-        return new XmlTableColumnSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), new IdentifierValue(ctx.colId().getText()).getValue(),
+        return new XmlTableColumnSegment(ctx.start.getStartIndex(), 
ctx.stop.getStopIndex(), 
createIdentifierValue(ctx.colId().getText()).getValue(),
                 null == ctx.typeName() ? null : 
getOriginalText(ctx.typeName()), path, defaultExpr, getOriginalText(ctx));
     }
     
@@ -2008,7 +2022,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         }
         FunctionSegment result = new 
FunctionSegment(ctx.start.getStartIndex(), ctx.stop.getStopIndex(), 
functionName, getOriginalText(ctx));
         if (null != funcNameContext.colId()) {
-            result.setOwner(new 
OwnerSegment(funcNameContext.colId().start.getStartIndex(), 
funcNameContext.colId().stop.getStopIndex(), new 
IdentifierValue(funcNameContext.colId().getText())));
+            result.setOwner(new 
OwnerSegment(funcNameContext.colId().start.getStartIndex(), 
funcNameContext.colId().stop.getStopIndex(), 
createIdentifierValue(funcNameContext.colId().getText())));
         } else if (null != funcNameContext.owner() && null != 
funcNameContext.typeFunctionName()) {
             result.setOwner((OwnerSegment) visit(funcNameContext.owner()));
         }
@@ -2073,7 +2087,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         if (null != ctx.USING()) {
             joinTableSource.setUsing(generateUsingColumn(ctx.nameList()));
             if (null != ctx.colId()) {
-                joinTableSource.setUsingAlias(new 
AliasSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), new IdentifierValue(ctx.colId().getText())));
+                joinTableSource.setUsingAlias(new 
AliasSegment(ctx.colId().start.getStartIndex(), 
ctx.colId().stop.getStopIndex(), createIdentifierValue(ctx.colId().getText())));
             }
         }
     }
@@ -2084,14 +2098,14 @@ public abstract class PostgreSQLStatementVisitor 
extends PostgreSQLStatementPars
             result.addAll(generateUsingColumn(ctx.nameList()));
         }
         if (null != ctx.name()) {
-            result.add(new ColumnSegment(ctx.name().start.getStartIndex(), 
ctx.name().stop.getStopIndex(), new IdentifierValue(ctx.name().getText())));
+            result.add(new ColumnSegment(ctx.name().start.getStartIndex(), 
ctx.name().stop.getStopIndex(), createIdentifierValue(ctx.name().getText())));
         }
         return result;
     }
     
     @Override
     public ASTNode visitAliasClause(final AliasClauseContext ctx) {
-        AliasSegment result = new 
AliasSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), new 
IdentifierValue(ctx.colId().getText()));
+        AliasSegment result = new 
AliasSegment(ctx.colId().start.getStartIndex(), ctx.stop.getStopIndex(), 
createIdentifierValue(ctx.colId().getText()));
         if (null != ctx.nameList()) {
             
result.getColumnAliases().addAll(generateAliasColumns(ctx.nameList()));
         }
@@ -2104,14 +2118,14 @@ public abstract class PostgreSQLStatementVisitor 
extends PostgreSQLStatementPars
             result.addAll(generateAliasColumns(ctx.nameList()));
         }
         if (null != ctx.name()) {
-            result.add(new IdentifierValue(ctx.name().getText()));
+            result.add(createIdentifierValue(ctx.name().getText()));
         }
         return result;
     }
     
     private OwnerSegment createTableOwner(final IndirectionContext ctx) {
         AttrNameContext attrName = ctx.indirectionEl().attrName();
-        return new OwnerSegment(attrName.start.getStartIndex(), 
attrName.stop.getStopIndex(), new IdentifierValue(attrName.getText()));
+        return new OwnerSegment(attrName.start.getStartIndex(), 
attrName.stop.getStopIndex(), createIdentifierValue(attrName.getText()));
     }
     
     @Override
@@ -2237,7 +2251,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
         if (null != ctx.attrs()) {
             result.combine((CollectionValue<NameSegment>) visit(ctx.attrs()));
         }
-        result.getValue().add(new 
NameSegment(ctx.colId().getStart().getStartIndex(), 
ctx.colId().getStop().getStopIndex(), new 
IdentifierValue(ctx.colId().getText())));
+        result.getValue().add(new 
NameSegment(ctx.colId().getStart().getStartIndex(), 
ctx.colId().getStop().getStopIndex(), 
createIdentifierValue(ctx.colId().getText())));
         return result;
     }
     
@@ -2245,7 +2259,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
     @SuppressWarnings("unchecked")
     public ASTNode visitAttrs(final AttrsContext ctx) {
         CollectionValue<NameSegment> result = new CollectionValue<>();
-        result.getValue().add(new 
NameSegment(ctx.attrName().getStart().getStartIndex(), 
ctx.attrName().getStop().getStopIndex(), new 
IdentifierValue(ctx.attrName().getText())));
+        result.getValue().add(new 
NameSegment(ctx.attrName().getStart().getStartIndex(), 
ctx.attrName().getStop().getStopIndex(), 
createIdentifierValue(ctx.attrName().getText())));
         if (null != ctx.attrs()) {
             result.combine((CollectionValue<NameSegment>) visit(ctx.attrs()));
         }
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDALStatementVisitor.java
 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDALStatementVisitor.java
index 065ea5e8263..64c807ec88c 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDALStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDALStatementVisitor.java
@@ -49,7 +49,6 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.Se
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.ShowStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.MergeStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.value.collection.CollectionValue;
-import 
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 import 
org.apache.shardingsphere.sql.parser.statement.postgresql.dal.PostgreSQLCheckpointStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.postgresql.dal.PostgreSQLLoadStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.postgresql.dal.PostgreSQLResetParameterStatement;
@@ -142,7 +141,7 @@ public final class PostgreSQLDALStatementVisitor extends 
PostgreSQLStatementVisi
         CollectionValue<SimpleTableSegment> result = new CollectionValue<>();
         for (VacuumRelationContext each : ctx.vacuumRelation()) {
             ColIdContext colId = each.qualifiedName().colId();
-            TableNameSegment tableName = new 
TableNameSegment(colId.start.getStartIndex(), colId.stop.getStopIndex(), new 
IdentifierValue(colId.getText()));
+            TableNameSegment tableName = new 
TableNameSegment(colId.start.getStartIndex(), colId.stop.getStopIndex(), 
createIdentifierValue(colId.getText()));
             result.getValue().add(new SimpleTableSegment(tableName));
         }
         return result;
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDCLStatementVisitor.java
 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDCLStatementVisitor.java
index 3bf7d7c1d2c..41f43f693c4 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDCLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDCLStatementVisitor.java
@@ -171,12 +171,12 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
             fillNamePrivilegeObjects(result, objectType, ctx.nameList());
         }
         NameContext name = ctx.name();
-        addPrivilegeObject(result, objectType, name, 
Collections.singletonList(new IdentifierValue(name.getText())));
+        addPrivilegeObject(result, objectType, name, 
Collections.singletonList(createIdentifierValue(name.getText())));
     }
     
     private List<IdentifierValue> getAnyNameIdentifiers(final AnyNameContext 
ctx) {
         List<IdentifierValue> result = new LinkedList<>();
-        result.add(new IdentifierValue(ctx.colId().getText()));
+        result.add(createIdentifierValue(ctx.colId().getText()));
         if (null != ctx.attrs()) {
             fillAttrsIdentifiers(result, ctx.attrs());
         }
@@ -187,7 +187,7 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
         if (null != ctx.attrs()) {
             fillAttrsIdentifiers(result, ctx.attrs());
         }
-        result.add(new IdentifierValue(ctx.attrName().getText()));
+        result.add(createIdentifierValue(ctx.attrName().getText()));
     }
     
     private List<IdentifierValue> getFunctionIdentifiers(final 
FunctionWithArgtypesContext ctx) {
@@ -198,7 +198,7 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
         if (null != ctx.typeFuncNameKeyword()) {
             result.add(new 
IdentifierValue(ctx.typeFuncNameKeyword().getText()));
         } else {
-            result.add(new IdentifierValue(ctx.colId().getText()));
+            result.add(createIdentifierValue(ctx.colId().getText()));
             fillIndirectionIdentifiers(result, ctx.indirection());
         }
         return result;
@@ -207,14 +207,14 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
     private List<IdentifierValue> getFunctionIdentifiers(final FuncNameContext 
ctx) {
         List<IdentifierValue> result = new LinkedList<>();
         if (null == ctx.typeFunctionName()) {
-            result.add(new IdentifierValue(ctx.colId().getText()));
+            result.add(createIdentifierValue(ctx.colId().getText()));
             fillIndirectionIdentifiers(result, ctx.indirection());
             return result;
         }
         if (null != ctx.owner()) {
-            result.add(new IdentifierValue(ctx.owner().getText()));
+            result.add(createIdentifierValue(ctx.owner().getText()));
         }
-        result.add(new IdentifierValue(ctx.typeFunctionName().getText()));
+        result.add(createIdentifierValue(ctx.typeFunctionName().getText()));
         return result;
     }
     
@@ -226,7 +226,7 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
             fillIndirectionIdentifiers(result, ctx.indirection());
         }
         if (null != ctx.indirectionEl().attrName()) {
-            result.add(new 
IdentifierValue(ctx.indirectionEl().attrName().getText()));
+            
result.add(createIdentifierValue(ctx.indirectionEl().attrName().getText()));
         }
     }
     
@@ -252,7 +252,7 @@ public final class PostgreSQLDCLStatementVisitor extends 
PostgreSQLStatementVisi
             identifiers.add(new IdentifierValue("*"));
         } else if (null != ctx.schemaName()) {
             identifiers.add((IdentifierValue) visit(ctx.schemaName()));
-            identifiers.add(new IdentifierValue(ctx.routineName().getText()));
+            
identifiers.add(createIdentifierValue(ctx.routineName().getText()));
         } else {
             identifiers.add(new IdentifierValue("*"));
             if (null != ctx.DOT_ASTERISK_()) {
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDDLStatementVisitor.java
 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDDLStatementVisitor.java
index 78a2eaeb9a9..4a99422cb4b 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDDLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/type/PostgreSQLDDLStatementVisitor.java
@@ -765,7 +765,7 @@ public final class PostgreSQLDDLStatementVisitor extends 
PostgreSQLStatementVisi
         CollectionValue<ColumnSegment> result = new CollectionValue<>();
         for (IndexElemContext each : ctx.indexElem()) {
             if (null != each.colId()) {
-                result.getValue().add(new 
ColumnSegment(each.colId().start.getStartIndex(), 
each.colId().stop.getStopIndex(), new IdentifierValue(each.colId().getText())));
+                result.getValue().add(new 
ColumnSegment(each.colId().start.getStartIndex(), 
each.colId().stop.getStopIndex(), 
createIdentifierValue(each.colId().getText())));
             }
             if (null != each.functionExprWindowless()) {
                 ExpressionSegment expression = (ExpressionSegment) 
visit(each.functionExprWindowless());
@@ -958,7 +958,7 @@ public final class PostgreSQLDDLStatementVisitor extends 
PostgreSQLStatementVisi
     @Override
     public ASTNode visitCreateTrigger(final CreateTriggerContext ctx) {
         CreateTriggerStatement result = new 
CreateTriggerStatement(getDatabaseType());
-        result.setTriggerName(new 
FunctionNameSegment(ctx.name().start.getStartIndex(), 
ctx.name().stop.getStopIndex(), new IdentifierValue(ctx.name().getText())));
+        result.setTriggerName(new 
FunctionNameSegment(ctx.name().start.getStartIndex(), 
ctx.name().stop.getStopIndex(), createIdentifierValue(ctx.name().getText())));
         result.setTable((SimpleTableSegment) visit(ctx.qualifiedName(0)));
         return result;
     }
@@ -1120,7 +1120,7 @@ public final class PostgreSQLDDLStatementVisitor extends 
PostgreSQLStatementVisi
     public ASTNode visitCreateSchema(final CreateSchemaContext ctx) {
         CreateSchemaStatement result = new 
CreateSchemaStatement(getDatabaseType());
         if (null != ctx.createSchemaClauses().colId()) {
-            result.setSchemaName(new 
IdentifierValue(ctx.createSchemaClauses().colId().getText()));
+            
result.setSchemaName(createIdentifierValue(ctx.createSchemaClauses().colId().getText()));
         }
         if (null != ctx.createSchemaClauses().roleSpec() && null != 
ctx.createSchemaClauses().roleSpec().identifier()) {
             result.setUsername((IdentifierValue) 
visit(ctx.createSchemaClauses().roleSpec().identifier()));
diff --git 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValue.java
 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValue.java
index e15bca764d1..a4241930ae2 100644
--- 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValue.java
+++ 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValue.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.sql.parser.statement.core.value.identifier;
 
+import lombok.AccessLevel;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
@@ -38,6 +39,11 @@ public final class IdentifierValue implements 
ValueASTNode<String> {
     
     private final QuoteCharacter quoteCharacter;
     
+    @Getter(AccessLevel.NONE)
+    @EqualsAndHashCode.Exclude
+    @ToString.Exclude
+    private boolean escapeQuoteCharacters;
+    
     public IdentifierValue(final String text) {
         quoteCharacter = QuoteCharacter.getQuoteCharacter(text);
         if (QuoteCharacter.BACK_QUOTE == quoteCharacter) {
@@ -47,12 +53,27 @@ public final class IdentifierValue implements 
ValueASTNode<String> {
         }
     }
     
+    /**
+     * Create identifier value from a decoded double-quoted name.
+     *
+     * @param value decoded identifier name
+     * @return identifier value whose internal double quotes are escaped when 
rendered
+     */
+    public static IdentifierValue fromDecodedQuotedName(final String value) {
+        IdentifierValue result = new IdentifierValue(value, 
QuoteCharacter.QUOTE);
+        result.escapeQuoteCharacters = true;
+        return result;
+    }
+    
     /**
      * Get value with quote characters, i.e. `table1` or `field1`
      *
      * @return value with quote characters
      */
     public String getValueWithQuoteCharacters() {
-        return null == value ? "" : quoteCharacter.wrap(value);
+        if (null == value) {
+            return "";
+        }
+        return quoteCharacter.wrap(escapeQuoteCharacters ? value.replace("\"", 
"\"\"") : value);
     }
 }
diff --git 
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValueTest.java
 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValueTest.java
index 57e271bc4cd..246e5281178 100644
--- 
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValueTest.java
+++ 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/value/identifier/IdentifierValueTest.java
@@ -17,7 +17,13 @@
 
 package org.apache.shardingsphere.sql.parser.statement.core.value.identifier;
 
+import 
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -48,6 +54,29 @@ class IdentifierValueTest {
         assertThat(new IdentifierValue(text).getValue(), 
is("ds_${[1,2]}.t_order"));
     }
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("getDecodedQuotedNames")
+    void assertFromDecodedQuotedName(final String name, final String value, 
final String expectedQuotedValue) {
+        IdentifierValue actual = IdentifierValue.fromDecodedQuotedName(value);
+        assertThat(actual.getValue(), is(value));
+        assertThat(actual.getValueWithQuoteCharacters(), 
is(expectedQuotedValue));
+    }
+    
+    private static Stream<Arguments> getDecodedQuotedNames() {
+        return Stream.of(
+                Arguments.of("normal name", "foo_table", "\"foo_table\""),
+                Arguments.of("single interior quote", "foo_\"table", 
"\"foo_\"\"table\""),
+                Arguments.of("adjacent interior quotes", "foo_\"\"table", 
"\"foo_\"\"\"\"table\""));
+    }
+    
+    @Test
+    void assertFromDecodedQuotedNameWithExistingEquality() {
+        IdentifierValue actual = 
IdentifierValue.fromDecodedQuotedName("foo_\"table");
+        IdentifierValue expected = new IdentifierValue("foo_\"table", 
QuoteCharacter.QUOTE);
+        assertThat(actual, is(expected));
+        assertThat(actual.hashCode(), is(expected.hashCode()));
+    }
+    
     @Test
     void assertGetValueWithQuoteCharactersWithNullValue() {
         assertThat(new IdentifierValue(null).getValueWithQuoteCharacters(), 
is(""));
@@ -58,4 +87,18 @@ class IdentifierValueTest {
         String text = "[foo]";
         assertThat(new IdentifierValue(text).getValueWithQuoteCharacters(), 
is("[foo]"));
     }
+    
+    @Test
+    void assertGetValueWithQuoteCharactersWithEscapedRawName() {
+        IdentifierValue actual = new IdentifierValue("\"foo_\"\"table\"");
+        assertThat(actual.getValue(), is("foo_\"\"table"));
+        assertThat(actual.getValueWithQuoteCharacters(), 
is("\"foo_\"\"table\""));
+    }
+    
+    @Test
+    void assertGetValueWithQuoteCharactersWithEscapedExplicitName() {
+        IdentifierValue actual = new IdentifierValue("foo_\"\"table", 
QuoteCharacter.QUOTE);
+        assertThat(actual.getValue(), is("foo_\"\"table"));
+        assertThat(actual.getValueWithQuoteCharacters(), 
is("\"foo_\"\"table\""));
+    }
 }
diff --git a/test/it/parser/src/main/resources/case/dml/select-expression.xml 
b/test/it/parser/src/main/resources/case/dml/select-expression.xml
index 311308a7cbd..465f75de876 100644
--- a/test/it/parser/src/main/resources/case/dml/select-expression.xml
+++ b/test/it/parser/src/main/resources/case/dml/select-expression.xml
@@ -4422,6 +4422,26 @@
         </projections>
     </select>
 
+    <select sql-case-id="select_postgresql_escaped_identifiers">
+        <projections start-index="7" stop-index="81">
+            <column-projection name="foo&quot;bar" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="7" stop-index="16" />
+            <column-projection name="foo&quot;&quot;bar" 
start-delimiter="&quot;" end-delimiter="&quot;" start-index="19" 
stop-index="30" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="33" stop-index="51" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="54" stop-index="81" />
+        </projections>
+    </select>
+
+    <select sql-case-id="select_postgresql_unicode_identifier_controls">
+        <projections start-index="7" stop-index="211">
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="7" stop-index="35" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="38" stop-index="69" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="72" stop-index="103" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="106" stop-index="139" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="142" stop-index="179" />
+            <column-projection name="data" start-delimiter="&quot;" 
end-delimiter="&quot;" start-index="182" stop-index="211" />
+        </projections>
+    </select>
+
     <select sql-case-id="select_postgresql_unicode_strings">
         <projections start-index="7" stop-index="69">
             <expression-projection start-index="7" stop-index="25" 
text="U&amp;'d\0061t\+000061'">
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-expression.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/select-expression.xml
index 1a636901b35..de991a052a0 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-expression.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-expression.xml
@@ -188,6 +188,8 @@
     <sql-case id="select_postgresql_numeric_literals" value="SELECT 1_000, .5, 
4., 4664.E+5, 0x2a, 0X_FF, 0o_77, 0O17, 0b_1010, 0B11, -0x2A, +0o10, 
0xffffffffffffffffffff" db-types="PostgreSQL" case-types="literal" />
     <sql-case id="select_postgresql_bit_literals" value="SELECT B'1010', b'', 
X'2a', x'', B'10'&#10;'01', X'2'&#10;'a'" db-types="PostgreSQL" 
case-types="literal" />
     <sql-case id="select_postgresql_string_continuation" value="SELECT 
'foo'&#10;'bar', E'foo'&#10;'bar', 'a\b', 'a''b'" db-types="PostgreSQL" 
case-types="literal" />
+    <sql-case id="select_postgresql_escaped_identifiers" value="SELECT 
&quot;foo&quot;&quot;bar&quot;, &quot;foo&quot;&quot;&quot;&quot;bar&quot;, 
U&amp;&quot;d\0061t\+000061&quot;, U&amp;&quot;d!0061t!0061&quot; UESCAPE '!'" 
db-types="PostgreSQL" case-types="literal" />
+    <sql-case id="select_postgresql_unicode_identifier_controls" value="SELECT 
U&amp;&quot;d!0061t!0061&quot; UESCAPE E'!', U&amp;&quot;d!0061t!0061&quot; 
UESCAPE E'\041', U&amp;&quot;d!0061t!0061&quot; UESCAPE E'\x21', 
U&amp;&quot;d!0061t!0061&quot; UESCAPE E'\u0021', 
U&amp;&quot;d!0061t!0061&quot; UESCAPE E'\U00000021', 
U&amp;&quot;d\0061t\0061&quot; UESCAPE E'\\'" db-types="PostgreSQL" 
case-types="literal" />
     <sql-case id="select_postgresql_unicode_strings" value="SELECT 
U&amp;'d\0061t\+000061', U&amp;'d!0061t!0061' UESCAPE '!', U&amp;'foo\\bar'" 
db-types="PostgreSQL" case-types="literal" />
     <sql-case id="select_postgresql_custom_binary_operators" value="SELECT 1 
*= 2, 1 *&lt;= 2, 1 ^@ 2, 1 ~&lt;~ 2, 1 &lt;&lt;&lt; 2, 1 === 2, 1 @- 2, 1 
OPERATOR(pg_catalog.+) 2" db-types="PostgreSQL" case-types="literal" />
     <sql-case id="select_postgresql_custom_prefix_operators" value="SELECT @ 
?, |/ ?, !! ?, ~ ?" db-types="PostgreSQL" case-types="placeholder" />
diff --git 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/InternalPostgreSQLParserIT.java
 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/InternalPostgreSQLParserIT.java
index 1bdef0860f7..881cea64ad9 100644
--- 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/InternalPostgreSQLParserIT.java
+++ 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/InternalPostgreSQLParserIT.java
@@ -25,6 +25,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.enums.JoinType;
 import 
org.apache.shardingsphere.sql.parser.statement.core.extractor.ColumnExtractor;
 import 
org.apache.shardingsphere.sql.parser.statement.core.extractor.ExpressionExtractor;
 import 
org.apache.shardingsphere.sql.parser.statement.core.extractor.TableExtractor;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dcl.PrivilegeObjectSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.ColumnAssignmentSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
@@ -47,8 +48,13 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.TableSQLStatementAttribute;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.AnalyzeTableStatement;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dcl.GrantStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.FetchStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.MoveStatement;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.index.CreateIndexStatement;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.schema.CreateSchemaStatement;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.trigger.CreateTriggerStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
@@ -79,6 +85,117 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 @InternalSQLParserITSettings("PostgreSQL")
 class InternalPostgreSQLParserIT extends InternalSQLParserIT {
     
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("getQuotedIdentifierCases")
+    void assertQuotedIdentifierRoundTrip(final String name, final String 
identifier, final String expectedValue, final String expectedQuotedValue) {
+        String sql = "SELECT " + identifier + " AS " + identifier + " FROM " + 
identifier + "." + identifier + " AS " + identifier;
+        SelectStatement actual = (SelectStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        ColumnProjectionSegment actualProjection = (ColumnProjectionSegment) 
actual.getProjections().getProjections().iterator().next();
+        SimpleTableSegment actualTable = (SimpleTableSegment) 
actual.getFrom().get();
+        ColumnSegment actualColumn = actualProjection.getColumn();
+        assertThat(sql.substring(actualColumn.getStartIndex(), 
actualColumn.getStopIndex() + 1), is(identifier));
+        assertThat(sql.substring(actualTable.getTableName().getStartIndex(), 
actualTable.getTableName().getStopIndex() + 1), is(identifier));
+        Collection<IdentifierValue> actualIdentifiers = 
Arrays.asList(actualColumn.getIdentifier(), actualProjection.getAlias().get(), 
actualTable.getOwner().get().getIdentifier(),
+                actualTable.getTableName().getIdentifier(), 
actualTable.getAlias().get());
+        for (IdentifierValue each : actualIdentifiers) {
+            assertThat(each.getValue(), is(expectedValue));
+            assertThat(each.getValueWithQuoteCharacters(), 
is(expectedQuotedValue));
+            String roundTripSQL = "SELECT " + 
each.getValueWithQuoteCharacters();
+            SelectStatement actualRoundTrip = (SelectStatement) new 
SQLStatementVisitorEngine("PostgreSQL")
+                    .visit(new SQLParserEngine("PostgreSQL", new 
CacheOption(128, 1024L)).parse(roundTripSQL, false));
+            assertThat(((ColumnProjectionSegment) 
actualRoundTrip.getProjections().getProjections().iterator().next()).getColumn().getIdentifier().getValue(),
 is(expectedValue));
+        }
+    }
+    
+    private static Stream<Arguments> getQuotedIdentifierCases() {
+        return Stream.of(
+                Arguments.of("one embedded quote", "\"foo\"\"bar\"", 
"foo\"bar", "\"foo\"\"bar\""),
+                Arguments.of("two adjacent embedded quotes", 
"\"foo\"\"\"\"bar\"", "foo\"\"bar", "\"foo\"\"\"\"bar\""),
+                Arguments.of("default Unicode escape", "U&\"foo\\0061\"", 
"fooa", "\"fooa\""),
+                Arguments.of("six digit Unicode escape", "U&\"foo\\+01F600\"", 
"foo😀", "\"foo😀\""),
+                Arguments.of("surrogate pair Unicode escape", 
"U&\"foo\\D83D\\DE00\"", "foo😀", "\"foo😀\""),
+                Arguments.of("literal Unicode escape character", 
"U&\"foo\\\\bar\"", "foo\\bar", "\"foo\\bar\""),
+                Arguments.of("custom Unicode escape", "U&\"foo!0061\" UESCAPE 
'!'", "fooa", "\"fooa\""),
+                Arguments.of("escaped custom Unicode escape", "U&\"foo!0061\" 
UESCAPE E'\\041'", "fooa", "\"fooa\""),
+                Arguments.of("escaped backslash Unicode escape", 
"U&\"foo\\0061\" UESCAPE E'\\\\'", "fooa", "\"fooa\""),
+                Arguments.of("Unicode embedded quote", "U&\"foo\\0022bar\"", 
"foo\"bar", "\"foo\"\"bar\""),
+                Arguments.of("Unicode adjacent decoded quotes", 
"U&\"foo\\0022\\0022bar\"", "foo\"\"bar", "\"foo\"\"\"\"bar\""),
+                Arguments.of("Unicode decoded and literal quotes", 
"U&\"foo!0022\"\"bar\" UESCAPE '!'", "foo\"\"bar", "\"foo\"\"\"\"bar\""));
+    }
+    
+    @Test
+    void assertCreateSchemaQuotedIdentifierRoundTrip() {
+        String sql = "CREATE SCHEMA U&\"foo!0022\"\"schema\" UESCAPE '!'";
+        CreateSchemaStatement actual = (CreateSchemaStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        IdentifierValue actualIdentifier = actual.getSchemaName().get();
+        assertThat(actualIdentifier.getValue(), is("foo\"\"schema"));
+        String roundTripSQL = "CREATE SCHEMA " + 
actualIdentifier.getValueWithQuoteCharacters();
+        CreateSchemaStatement actualRoundTrip = (CreateSchemaStatement) new 
SQLStatementVisitorEngine("PostgreSQL")
+                .visit(new SQLParserEngine("PostgreSQL", new CacheOption(128, 
1024L)).parse(roundTripSQL, false));
+        assertThat(actualRoundTrip.getSchemaName().get().getValue(), 
is("foo\"\"schema"));
+    }
+    
+    @Test
+    void assertCreateIndexQuotedColumnRoundTrip() {
+        String identifier = "U&\"foo\\0022\\0022column\"";
+        String sql = "CREATE INDEX foo_index ON foo_table (" + identifier + 
")";
+        CreateIndexStatement actual = (CreateIndexStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        ColumnSegment actualColumn = actual.getColumns().iterator().next();
+        assertThat(actualColumn.getIdentifier().getValue(), 
is("foo\"\"column"));
+        assertThat(sql.substring(actualColumn.getStartIndex(), 
actualColumn.getStopIndex() + 1), is(identifier));
+        String roundTripSQL = "CREATE INDEX foo_index ON foo_table (" + 
actualColumn.getIdentifier().getValueWithQuoteCharacters() + ")";
+        CreateIndexStatement actualRoundTrip = (CreateIndexStatement) new 
SQLStatementVisitorEngine("PostgreSQL")
+                .visit(new SQLParserEngine("PostgreSQL", new CacheOption(128, 
1024L)).parse(roundTripSQL, false));
+        
assertThat(actualRoundTrip.getColumns().iterator().next().getIdentifier().getValue(),
 is("foo\"\"column"));
+    }
+    
+    @Test
+    void assertCreateTriggerQuotedIdentifierRoundTrip() {
+        String triggerClause = " BEFORE INSERT ON foo_table FOR EACH ROW 
EXECUTE FUNCTION foo_function()";
+        String sql = "CREATE TRIGGER U&\"foo!0022!0022trigger\" UESCAPE '!'" + 
triggerClause;
+        CreateTriggerStatement actual = (CreateTriggerStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        IdentifierValue actualIdentifier = 
actual.getTriggerName().get().getIdentifier();
+        assertThat(actualIdentifier.getValue(), is("foo\"\"trigger"));
+        String roundTripSQL = "CREATE TRIGGER " + 
actualIdentifier.getValueWithQuoteCharacters() + triggerClause;
+        CreateTriggerStatement actualRoundTrip = (CreateTriggerStatement) new 
SQLStatementVisitorEngine("PostgreSQL")
+                .visit(new SQLParserEngine("PostgreSQL", new CacheOption(128, 
1024L)).parse(roundTripSQL, false));
+        
assertThat(actualRoundTrip.getTriggerName().get().getIdentifier().getValue(), 
is("foo\"\"trigger"));
+    }
+    
+    @Test
+    void assertAnalyzeQuotedTableRoundTrip() {
+        String identifier = "U&\"foo!0022\"\"table\" UESCAPE '!'";
+        String sql = "ANALYZE " + identifier;
+        AnalyzeTableStatement actual = (AnalyzeTableStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        SimpleTableSegment actualTable = actual.getTables().iterator().next();
+        assertThat(actualTable.getTableName().getIdentifier().getValue(), 
is("foo\"\"table"));
+        assertThat(sql.substring(actualTable.getStartIndex(), 
actualTable.getStopIndex() + 1), is(identifier));
+        String roundTripSQL = "ANALYZE " + 
actualTable.getTableName().getIdentifier().getValueWithQuoteCharacters();
+        AnalyzeTableStatement actualRoundTrip = (AnalyzeTableStatement) new 
SQLStatementVisitorEngine("PostgreSQL")
+                .visit(new SQLParserEngine("PostgreSQL", new CacheOption(128, 
1024L)).parse(roundTripSQL, false));
+        
assertThat(actualRoundTrip.getTables().iterator().next().getTableName().getIdentifier().getValue(),
 is("foo\"\"table"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("getQuotedPrivilegeObjectCases")
+    void assertQuotedPrivilegeObjectRoundTrip(final String name, final String 
sqlPrefix, final String objectName, final String sqlSuffix, final 
Collection<String> expectedIdentifiers) {
+        String sql = sqlPrefix + objectName + sqlSuffix;
+        GrantStatement actual = (GrantStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(sql, false));
+        PrivilegeObjectSegment actualObject = 
actual.getPrivilegeObjects().iterator().next();
+        
assertThat(actualObject.getIdentifiers().stream().map(IdentifierValue::getValue).collect(Collectors.toList()),
 is(expectedIdentifiers));
+        String roundTripSQL = sqlPrefix + 
actualObject.getIdentifiers().stream().map(IdentifierValue::getValueWithQuoteCharacters).collect(Collectors.joining("."))
 + sqlSuffix;
+        GrantStatement actualRoundTrip = (GrantStatement) new 
SQLStatementVisitorEngine("PostgreSQL").visit(new SQLParserEngine("PostgreSQL", 
new CacheOption(128, 1024L)).parse(roundTripSQL, false));
+        
assertThat(actualRoundTrip.getPrivilegeObjects().iterator().next().getIdentifiers().stream().map(IdentifierValue::getValue).collect(Collectors.toList()),
 is(expectedIdentifiers));
+    }
+    
+    private static Stream<Arguments> getQuotedPrivilegeObjectCases() {
+        return Stream.of(
+                Arguments.of("schema name", "GRANT ALL ON SCHEMA ", 
"U&\"foo\\0061\"", " TO foo_role", Collections.singletonList("fooa")),
+                Arguments.of("type name and attribute", "GRANT ALL ON TYPE ", 
"U&\"foo\\0061\".U&\"bar!0022!0022\" UESCAPE '!'", " TO foo_role", 
Arrays.asList("fooa", "bar\"\"")),
+                Arguments.of("function owner and name", "GRANT ALL ON FUNCTION 
", "U&\"foo\\0061\".U&\"bar!0062\" UESCAPE '!'", "(integer) TO foo_role", 
Arrays.asList("fooa", "barb")),
+                Arguments.of("function indirection", "GRANT ALL ON FUNCTION ", 
"U&\"foo\\0061\".U&\"bar!0062\" UESCAPE '!'", " TO foo_role", 
Arrays.asList("fooa", "barb")));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("getUnsupportedFilterExpressions")
     void assertUnsupportedFilter(final String expression) {

Reply via email to