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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ccf99721 CAY-2975 Mnemonic table aliases in generated SQL
5ccf99721 is described below

commit 5ccf99721d2dd0f8f2942b8bfb1ad9b2377080c5
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Jul 5 19:36:50 2026 -0400

    CAY-2975 Mnemonic table aliases in generated SQL
    
    (also required a great deal of refactoring between SQLAppendable and 
SQLGenerationContext)
---
 RELEASE-NOTES.txt                                  |   1 +
 .../access/sqlbuilder/DefaultSQLAppendable.java    |  17 +---
 .../cayenne/access/sqlbuilder/SQLAppendable.java   |   2 -
 .../access/sqlbuilder/SQLGenerationContext.java    |  13 ++-
 .../access/sqlbuilder/SQLGenerationVisitor.java    |   6 +-
 .../access/sqlbuilder/sqltree/AliasedNode.java     |   3 +-
 .../access/sqlbuilder/sqltree/BitwiseNotNode.java  |   3 +-
 .../access/sqlbuilder/sqltree/CaseNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/ColumnNode.java      |   6 +-
 .../access/sqlbuilder/sqltree/DeleteNode.java      |   3 +-
 .../access/sqlbuilder/sqltree/DistinctNode.java    |   3 +-
 .../access/sqlbuilder/sqltree/ElseNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/EmptyNode.java       |   3 +-
 .../access/sqlbuilder/sqltree/ExistsNode.java      |   3 +-
 .../access/sqlbuilder/sqltree/ExpressionNode.java  |   3 +-
 .../access/sqlbuilder/sqltree/FromNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/FunctionNode.java    |   3 +-
 .../access/sqlbuilder/sqltree/GroupByNode.java     |   3 +-
 .../access/sqlbuilder/sqltree/HavingNode.java      |   3 +-
 .../cayenne/access/sqlbuilder/sqltree/InNode.java  |   3 +-
 .../sqlbuilder/sqltree/InsertColumnsNode.java      |   3 +-
 .../access/sqlbuilder/sqltree/InsertNode.java      |   3 +-
 .../sqlbuilder/sqltree/InsertValuesNode.java       |   3 +-
 .../access/sqlbuilder/sqltree/JoinNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/LimitOffsetNode.java |   3 +-
 .../cayenne/access/sqlbuilder/sqltree/Node.java    |   6 +-
 .../cayenne/access/sqlbuilder/sqltree/NotNode.java |   3 +-
 .../sqlbuilder/sqltree/OffsetFetchNextNode.java    |   3 +-
 .../access/sqlbuilder/sqltree/OffsetNode.java      |   3 +-
 .../access/sqlbuilder/sqltree/OrderByNode.java     |   3 +-
 .../access/sqlbuilder/sqltree/SelectNode.java      |   3 +-
 .../sqlbuilder/sqltree/SelectResultNode.java       |   3 +-
 .../access/sqlbuilder/sqltree/SubqueryNode.java    |   3 +-
 .../access/sqlbuilder/sqltree/TableNode.java       |  10 +-
 .../access/sqlbuilder/sqltree/TextNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/ThenNode.java        |   3 +-
 .../cayenne/access/sqlbuilder/sqltree/TopNode.java |   3 +-
 .../sqlbuilder/sqltree/TrimmingColumnNode.java     |  22 +++--
 .../access/sqlbuilder/sqltree/UpdateNode.java      |   3 +-
 .../access/sqlbuilder/sqltree/UpdateSetNode.java   |   3 +-
 .../access/sqlbuilder/sqltree/ValueNode.java       |  93 +++++++++---------
 .../access/sqlbuilder/sqltree/WhenNode.java        |   3 +-
 .../access/sqlbuilder/sqltree/WhereNode.java       |   3 +-
 .../translator/batch/BaseBatchTranslator.java      |   4 +-
 .../translator/batch/BatchTranslatorContext.java   |  12 ++-
 .../translator/select/SQLGenerationStage.java      |   4 +-
 .../translator/select/SelectTranslatorContext.java |  11 ++-
 .../access/translator/select/TableTree.java        |  46 +++++++--
 .../cayenne/dba/derby/sqltree/DerbyValueNode.java  |  10 +-
 .../dba/firebird/FirebirdSQLTreeProcessor.java     |   5 +-
 .../dba/firebird/sqltree/FirebirdLimitNode.java    |   3 +-
 .../dba/ingres/IngressSQLTreeProcessor.java        |   3 +-
 .../cayenne/dba/mysql/sqltree/ConvertNode.java     |   3 +-
 .../sqltree/PostgresExtractFunctionNode.java       |   3 +-
 .../postgres/sqltree/PostgresLimitOffsetNode.java  |   3 +-
 .../dba/sqlserver/sqltree/SQLServerColumnNode.java |   5 +-
 .../sqltree/SQLServerLimitOffsetNode.java          |   3 +-
 .../main/java/org/apache/cayenne/map/DbEntity.java |  64 ++++++++++++
 .../org/apache/cayenne/map/SqlReservedWords.java   |  79 +++++++++++++++
 .../java/org/apache/cayenne/access/Cay2641IT.java  |  13 +--
 .../access/sqlbuilder/BaseSqlBuilderTest.java      |   8 +-
 .../access/sqlbuilder/sqltree/NodeTest.java        |   3 +-
 .../DbAdapterDelegatedSelectTranslatorIT.java      | 109 ++++++++++-----------
 .../select/DefaultObjectSelectTranslatorIT.java    |  42 ++++----
 .../QualifierTranslatorExistExpressionIT.java      |  80 +++++++--------
 .../translator/select/QualifierTranslatorIT.java   |  12 +--
 .../translator/select/QualifierTranslatorTest.java |  14 +--
 .../java/org/apache/cayenne/map/DbEntityTest.java  |  34 +++++++
 68 files changed, 558 insertions(+), 290 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index df2a21fff..0af45a945 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-2970 Tighten deferred value resolution contract on commit
 CAY-2971 Remove extra spaces within SQL parenthesis
 CAY-2972 Fewer parentheses in generated SQL
 CAY-2974 CayenneSqlException with a reference to translated query
+CAY-2975 Mnemonic table aliases in generated SQL
 
 Bug Fixes:
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/DefaultSQLAppendable.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/DefaultSQLAppendable.java
index 0cc1b71c7..9c4bf3f3e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/DefaultSQLAppendable.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/DefaultSQLAppendable.java
@@ -27,20 +27,12 @@ import org.apache.cayenne.dba.QuotingStrategy;
 public class DefaultSQLAppendable implements SQLAppendable {
 
     final StringBuilder builder;
-    private final SQLGenerationContext context;
     private final QuotingStrategy quotingStrategy;
     private boolean suppressNextSeparator;
 
-    public DefaultSQLAppendable(SQLGenerationContext context) {
+    public DefaultSQLAppendable(QuotingStrategy quotingStrategy) {
         this.builder = new StringBuilder();
-        this.context = context;
-        this.quotingStrategy = resolveQuotes(context);
-    }
-
-    private static QuotingStrategy resolveQuotes(SQLGenerationContext context) 
{
-        return context == null
-                ? QuotingStrategy.NONE
-                : 
context.getAdapter().getQuotingStrategy(context.getRootDbEntity());
+        this.quotingStrategy = quotingStrategy == null ? QuotingStrategy.NONE 
: quotingStrategy;
     }
 
     @Override
@@ -89,11 +81,6 @@ public class DefaultSQLAppendable implements SQLAppendable {
         return this;
     }
 
-    @Override
-    public SQLGenerationContext getContext() {
-        return context;
-    }
-
     @Override
     public String getSql() {
         return builder.toString();
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLAppendable.java 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLAppendable.java
index c7df379a5..424b8f346 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLAppendable.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLAppendable.java
@@ -54,7 +54,5 @@ public interface SQLAppendable {
 
     SQLAppendable appendQuoted(String str);
 
-    SQLGenerationContext getContext();
-
     String getSql();
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
index f130451c6..23d404043 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.access.jdbc.PSParameter;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbEntity;
 
-import java.util.Collection;
+import java.util.List;
 
 /**
  * @since 4.2
@@ -32,7 +32,16 @@ public interface SQLGenerationContext {
 
     DbAdapter getAdapter();
 
-    Collection<PSParameter> getBindings();
+    List<PSParameter<?>> getBindings();
 
     DbEntity getRootDbEntity();
+
+    /**
+     * Returns true if the statement being generated uses a single table, in 
which case table nodes
+     * and column nodes omit the table alias / prefix for readability (e.g. 
{@code SELECT NAME FROM
+     * ARTIST} rather than {@code SELECT a.NAME FROM ARTIST a}).
+     *
+     * @since 5.0
+     */
+    boolean isSingleTableSQL();
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
index 93becb3e3..ec4608df8 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
@@ -27,14 +27,16 @@ import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 public class SQLGenerationVisitor implements NodeTreeVisitor {
 
     private final SQLAppendable appendable;
+    private final SQLGenerationContext context;
 
-    public SQLGenerationVisitor(SQLAppendable appendable) {
+    public SQLGenerationVisitor(SQLAppendable appendable, SQLGenerationContext 
context) {
         this.appendable = appendable;
+        this.context = context;
     }
 
     @Override
     public boolean onNodeStart(Node node) {
-        node.append(appendable);
+        node.append(appendable, context);
         node.appendChildrenStart(appendable);
         return true;
     }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
index cd5edeb6d..74801d393 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -41,7 +42,7 @@ public class AliasedNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         if(skipContent()) {
             buffer.appendTokenSeparator().append(alias);
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
index eb3171603..abcd9f81b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class BitwiseNotNode extends ExpressionNode {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append('~');
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/CaseNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/CaseNode.java
index ba83fb659..7dcd81855 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/CaseNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/CaseNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 5.0
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class CaseNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("CASE");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
index d989f3de3..7ea584f2a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.map.DbAttribute;
 
 import java.util.Objects;
@@ -43,9 +44,10 @@ public class ColumnNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         buffer.appendTokenSeparator();
-        if (table != null) {
+        // omit the table prefix for a single-table statement
+        if (table != null && (context == null || !context.isSingleTableSQL())) 
{
             buffer.appendQuoted(table).append('.');
         }
         buffer.appendQuoted(column);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
index a7fdf242b..b60172c0e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -30,7 +31,7 @@ public class DeleteNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append("DELETE FROM");
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
index 69a83ad73..db22931ec 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -31,7 +32,7 @@ public class DistinctNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("DISTINCT");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ElseNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ElseNode.java
index 08b7d1157..5b11cc15c 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ElseNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ElseNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 5.0
@@ -31,7 +32,7 @@ public class ElseNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("ELSE");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
index d41e8ba1c..ffcf1b7fc 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class EmptyNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
index 263cb14d4..80a0392d4 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class ExistsNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("EXISTS");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
index fa8b6b5ad..90385311b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -35,7 +36,7 @@ public class ExpressionNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
index a63f25e4c..988be1ff2 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -31,7 +32,7 @@ public class FromNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("FROM");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
index f303b3454..ad89f00c3 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -51,7 +52,7 @@ public class FunctionNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         if(skipContent()) {
             buffer.appendTokenSeparator().append(alias);
         } else {
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
index 4dcc48f9d..8263c44ed 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class GroupByNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("GROUP BY");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
index 945fc8cc4..accf97884 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
@@ -20,13 +20,14 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
  */
 public class HavingNode extends Node {
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("HAVING");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
index 9e5e97981..686ccde4a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -36,7 +37,7 @@ public class InNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
index ed736e817..903b76b27 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -36,7 +37,7 @@ public class InsertColumnsNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
index d538bc6c9..dc79c3b57 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -31,7 +32,7 @@ public class InsertNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append("INSERT INTO");
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertValuesNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertValuesNode.java
index f82cfb2e5..e975e38fb 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertValuesNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertValuesNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -36,7 +37,7 @@ public class InsertValuesNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("VALUES");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
index d73905854..2ae7df290 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.JoinType;
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -40,7 +41,7 @@ public class JoinNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append(joinType.getName()).append("JOIN");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
index cdc695b0f..c28430512 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -38,7 +39,7 @@ public class LimitOffsetNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         if(limit == 0 && offset == 0) {
             return buffer;
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
index 8eb75f58c..f224b8a9b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
@@ -21,7 +21,9 @@ package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.DefaultSQLAppendable;
+import org.apache.cayenne.dba.QuotingStrategy;
 
 /**
  * @since 4.2
@@ -130,7 +132,7 @@ public abstract class Node {
 
     @Override
     public String toString() {
-        return "Node {" + append(new DefaultSQLAppendable(null)).getSql() + 
"}";
+        return "Node {" + append(new 
DefaultSQLAppendable(QuotingStrategy.NONE), null).getSql() + "}";
     }
 
     public NodeType getType() {
@@ -139,7 +141,7 @@ public abstract class Node {
 
     public abstract Node copy();
 
-    public abstract SQLAppendable append(SQLAppendable buffer);
+    public abstract SQLAppendable append(SQLAppendable buffer, 
SQLGenerationContext context);
 
     public void appendChildrenSeparator(SQLAppendable buffer, int childInd) {
     }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NotNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NotNode.java
index 3e99df4f2..d54447970 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NotNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NotNode.java
@@ -20,13 +20,14 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
  */
 public class NotNode extends Node {
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("NOT");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
index 6082ec2b8..5d7fb7580 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -35,7 +36,7 @@ public class OffsetFetchNextNode extends LimitOffsetNode {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         // OFFSET X ROWS FETCH NEXT Y ROWS ONLY
         if(offset > 0) {
             
buffer.appendTokenSeparator().append("OFFSET").appendTokenSeparator().append(offset).appendTokenSeparator().append("ROWS");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
index 2eb1e756d..f3926412a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -34,7 +35,7 @@ public class OffsetNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return 
buffer.appendTokenSeparator().append("OFFSET").appendTokenSeparator().append(offset);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OrderByNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OrderByNode.java
index eb0a082f5..0de97c470 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OrderByNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OrderByNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class OrderByNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("ORDER BY");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
index 950b93b96..2c01cb839 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -27,7 +28,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
 public class SelectNode extends Node {
 
     @Override
-    public SQLAppendable append(SQLAppendable builder) {
+    public SQLAppendable append(SQLAppendable builder, SQLGenerationContext 
context) {
         if(parent != null) {
             builder.appendTokenSeparator().append('(');
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
index 1130b5cdd..8ea2110d4 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -31,7 +32,7 @@ public class SelectResultNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
index 6ce751519..3526baefa 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -35,7 +36,7 @@ public class SubqueryNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append(subqueryType);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
index 0b16891b6..54818c93b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
@@ -19,11 +19,12 @@
 
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
-import java.util.Objects;
-
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.map.DbEntity;
 
+import java.util.Objects;
+
 /**
  * @since 4.2
  */
@@ -46,7 +47,7 @@ public class TableNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         buffer.appendTokenSeparator();
 
         if(dbEntity != null) {
@@ -61,7 +62,8 @@ public class TableNode extends Node {
             buffer.appendQuoted(tableName);
         }
 
-        if (alias != null) {
+        // omit the alias for a single-table statement
+        if (alias != null && (context == null || !context.isSingleTableSQL())) 
{
             buffer.appendTokenSeparator().appendQuoted(alias);
         }
         return buffer;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
index 408da6105..bc8612316 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -35,7 +36,7 @@ public class TextNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append(text);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ThenNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ThenNode.java
index 60ee9b960..64be06a94 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ThenNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ThenNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 5.0
@@ -31,7 +32,7 @@ public class ThenNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("THEN");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
index 35d0cc265..e500f7a43 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.util.Objects;
 
@@ -35,7 +36,7 @@ public class TopNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return 
buffer.appendTokenSeparator().append("TOP").appendTokenSeparator().append(count);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
index 7cba285a2..3a5490819 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 import java.sql.Types;
 import java.util.Objects;
@@ -36,17 +37,17 @@ public class TrimmingColumnNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         boolean isResult = isResultNode();
         if(columnNode.getAlias() == null || isResult) {
             if(isCharType() && isAllowedForTrimming()) {
-                appendRtrim(buffer);
+                appendRtrim(buffer, context);
                 appendAlias(buffer, isResult);
             } else if(isComparisonWithClob()) {
-                appendClobColumnNode(buffer);
+                appendClobColumnNode(buffer, context);
                 appendAlias(buffer, isResult);
             } else {
-                columnNode.append(buffer);
+                columnNode.append(buffer, context);
             }
         } else {
             appendAlias(buffer, false);
@@ -65,9 +66,9 @@ public class TrimmingColumnNode extends Node {
                 && columnNode.getAttribute().getType() == Types.CLOB;
     }
 
-    protected void appendRtrim(SQLAppendable buffer) {
+    protected void appendRtrim(SQLAppendable buffer, SQLGenerationContext 
context) {
         buffer.appendTokenSeparator().append("RTRIM(");
-        appendColumnNode(buffer);
+        appendColumnNode(buffer, context);
         buffer.append(")");
     }
 
@@ -109,14 +110,15 @@ public class TrimmingColumnNode extends Node {
         return false;
     }
 
-    protected void appendClobColumnNode(SQLAppendable buffer) {
+    protected void appendClobColumnNode(SQLAppendable buffer, 
SQLGenerationContext context) {
         buffer.appendTokenSeparator().append("CAST(");
-        appendColumnNode(buffer);
+        appendColumnNode(buffer, context);
         
buffer.appendTokenSeparator().append("AS").appendTokenSeparator().append("VARCHAR(").append(getColumnSize()).append("))");
     }
 
-    protected void appendColumnNode(SQLAppendable buffer) {
-        if (columnNode.getTable() != null) {
+    protected void appendColumnNode(SQLAppendable buffer, SQLGenerationContext 
context) {
+        // omit the table prefix for a single-table statement
+        if (columnNode.getTable() != null && (context == null || 
!context.isSingleTableSQL())) {
             buffer.appendQuoted(columnNode.getTable()).append('.');
         }
         buffer.appendQuoted(columnNode.getColumn());
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
index 33030771f..9f42b2642 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -32,7 +33,7 @@ public class UpdateNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append("UPDATE");
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateSetNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateSetNode.java
index 8c92789d5..898d091a9 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateSetNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateSetNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -36,7 +37,7 @@ public class UpdateSetNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("SET");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
index 123b2acff..fc38528b8 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
@@ -63,8 +63,8 @@ public class ValueNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
-        appendValue(value, buffer);
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
+        appendValue(value, buffer, context);
         return buffer;
     }
 
@@ -72,7 +72,7 @@ public class ValueNode extends Node {
         buffer.appendTokenSeparator().append("NULL");
     }
 
-    private void appendValue(Object val, SQLAppendable buffer) {
+    private void appendValue(Object val, SQLAppendable buffer, 
SQLGenerationContext context) {
         if (val == null) {
             appendNullValue(buffer);
             return;
@@ -80,54 +80,53 @@ public class ValueNode extends Node {
 
         if (isArray && val.getClass().isArray()) {
             switch (val) {
-                case short[] shorts -> appendValue(shorts, buffer);
-                case char[] chars -> appendValue(chars, buffer);
-                case int[] ints -> appendValue(ints, buffer);
-                case long[] longs -> appendValue(longs, buffer);
-                case float[] floats -> appendValue(floats, buffer);
-                case double[] doubles -> appendValue(doubles, buffer);
-                case boolean[] booleans -> appendValue(booleans, buffer);
-                case Object[] objects -> appendValue(objects, buffer);
+                case short[] shorts -> appendValue(shorts, buffer, context);
+                case char[] chars -> appendValue(chars, buffer, context);
+                case int[] ints -> appendValue(ints, buffer, context);
+                case long[] longs -> appendValue(longs, buffer, context);
+                case float[] floats -> appendValue(floats, buffer, context);
+                case double[] doubles -> appendValue(doubles, buffer, context);
+                case boolean[] booleans -> appendValue(booleans, buffer, 
context);
+                case Object[] objects -> appendValue(objects, buffer, context);
                 // append byte[] array as a single object
-                case byte[] bytes -> appendValue(bytes, buffer);
+                case byte[] bytes -> appendValue(bytes, buffer, context);
                 default -> throw new CayenneRuntimeException("Unsupported 
array type %s", val.getClass().getName());
             }
         } else {
             switch (val) {
-                case Persistent persistent -> appendValue(persistent, buffer);
-                case ObjectId objectId -> appendValue(objectId, buffer);
-                case Supplier<?> supplier -> appendValue(supplier.get(), 
buffer);
-                case CharSequence charSequence -> appendStringValue(buffer, 
charSequence);
-                default -> appendObjectValue(buffer, val);
+                case Persistent persistent -> appendValue(persistent, buffer, 
context);
+                case ObjectId objectId -> appendValue(objectId, buffer, 
context);
+                case Supplier<?> supplier -> appendValue(supplier.get(), 
buffer, context);
+                case CharSequence charSequence -> appendStringValue(buffer, 
context, charSequence);
+                default -> appendObjectValue(buffer, context, val);
             }
         }
     }
 
-    protected void appendObjectValue(SQLAppendable buffer, Object value) {
+    protected void appendObjectValue(SQLAppendable buffer, 
SQLGenerationContext context, Object value) {
         if (value == null) {
             return;
         }
-        if (buffer.getContext() == null || !needBinding) {
+        if (context == null || !needBinding) {
             buffer.appendTokenSeparator().append(value.toString());
         } else {
             buffer.appendTokenSeparator().append('?');
-            addValueBinding(buffer, value);
+            addValueBinding(context, value);
         }
     }
 
-    protected void appendStringValue(SQLAppendable buffer, CharSequence value) 
{
-        if (buffer.getContext() == null || !needBinding) {
+    protected void appendStringValue(SQLAppendable buffer, 
SQLGenerationContext context, CharSequence value) {
+        if (context == null || !needBinding) {
             
buffer.appendTokenSeparator().append('\'').append(value.toString()).append('\'');
         } else {
             // value can't be null here
             buffer.appendTokenSeparator().append('?');
-            addValueBinding(buffer, value);
+            addValueBinding(context, value);
         }
     }
 
-    protected void addValueBinding(SQLAppendable buffer, Object value) {
+    protected void addValueBinding(SQLGenerationContext context, Object value) 
{
         // value can't be null here
-        SQLGenerationContext context = buffer.getContext();
         // allow translation in out-of-context scope, to be able to use as a 
standalone SQL generator
         ExtendedType<?> extendedType = 
context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
 
@@ -143,17 +142,17 @@ public class ValueNode extends Node {
         context.getBindings().add(binding);
     }
 
-    private void appendValue(Persistent value, SQLAppendable buffer) {
-        appendValue(value.getObjectId(), buffer);
+    private void appendValue(Persistent value, SQLAppendable buffer, 
SQLGenerationContext context) {
+        appendValue(value.getObjectId(), buffer, context);
     }
 
-    private void appendValue(ObjectId value, SQLAppendable buffer) {
+    private void appendValue(ObjectId value, SQLAppendable buffer, 
SQLGenerationContext context) {
         for (Object idVal : value.getIdSnapshot().values()) {
-            appendValue(idVal, buffer);
+            appendValue(idVal, buffer, context);
         }
     }
 
-    private void appendValue(short[] val, SQLAppendable buffer) {
+    private void appendValue(short[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (short i : val) {
             if (first) {
@@ -161,11 +160,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(char[] val, SQLAppendable buffer) {
+    private void appendValue(char[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (char i : val) {
             if (first) {
@@ -173,11 +172,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(int[] val, SQLAppendable buffer) {
+    private void appendValue(int[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (int i : val) {
             if (first) {
@@ -185,11 +184,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(long[] val, SQLAppendable buffer) {
+    private void appendValue(long[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (long i : val) {
             if (first) {
@@ -197,11 +196,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(float[] val, SQLAppendable buffer) {
+    private void appendValue(float[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (float i : val) {
             if (first) {
@@ -209,11 +208,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(double[] val, SQLAppendable buffer) {
+    private void appendValue(double[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (double i : val) {
             if (first) {
@@ -221,11 +220,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(boolean[] val, SQLAppendable buffer) {
+    private void appendValue(boolean[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (boolean i : val) {
             if (first) {
@@ -233,11 +232,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(byte[] val, SQLAppendable buffer) {
+    private void appendValue(byte[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (byte i : val) {
             if (first) {
@@ -245,11 +244,11 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
-    private void appendValue(Object[] val, SQLAppendable buffer) {
+    private void appendValue(Object[] val, SQLAppendable buffer, 
SQLGenerationContext context) {
         boolean first = true;
         for (Object i : val) {
             if (first) {
@@ -257,7 +256,7 @@ public class ValueNode extends Node {
             } else {
                 buffer.append(',');
             }
-            appendValue(i, buffer);
+            appendValue(i, buffer, context);
         }
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhenNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhenNode.java
index fd9531327..797a7005d 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhenNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhenNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 5.0
@@ -31,7 +32,7 @@ public class WhenNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("WHEN");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
index 6f47db403..e15bac2e5 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 
 /**
  * @since 4.2
@@ -31,7 +32,7 @@ public class WhereNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.appendTokenSeparator().append("WHERE");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
index 3fac805ee..ba6d12562 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
@@ -80,7 +80,9 @@ public abstract class BaseBatchTranslator<T extends 
BatchQuery> implements Batch
     protected String doTranslate(BatchTranslatorContext<T> context, 
NodeBuilder nodeBuilder) {
         Node node = 
context.getAdapter().getSqlTreeProcessor().process(nodeBuilder.build());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(context));
+        DefaultSQLAppendable buffer = new DefaultSQLAppendable(
+                
context.getAdapter().getQuotingStrategy(context.getRootDbEntity()));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(buffer, 
context);
         node.visit(visitor);
 
         return visitor.getSQLString();
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
index 1aa799e4f..0a2ae32f1 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
@@ -19,14 +19,13 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.jdbc.PSParameter;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.query.BatchQuery;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -37,7 +36,7 @@ class BatchTranslatorContext<T extends BatchQuery> implements 
SQLGenerationConte
 
     private final T query;
     private final DbAdapter adapter;
-    private final List<PSParameter> bindings;
+    private final List<PSParameter<?>> bindings;
 
     BatchTranslatorContext(T query, DbAdapter adapter) {
         this.query = query;
@@ -51,7 +50,7 @@ class BatchTranslatorContext<T extends BatchQuery> implements 
SQLGenerationConte
     }
 
     @Override
-    public Collection<PSParameter> getBindings() {
+    public List<PSParameter<?>> getBindings() {
         return bindings;
     }
 
@@ -63,4 +62,9 @@ class BatchTranslatorContext<T extends BatchQuery> implements 
SQLGenerationConte
     public T getQuery() {
         return query;
     }
+
+    @Override
+    public boolean isSingleTableSQL() {
+        return true;
+    }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
index 9c072c9a9..1eb9bf62b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java
@@ -38,7 +38,9 @@ class SQLGenerationStage implements TranslationStage {
         // convert to database flavour
         node = context.getAdapter().getSqlTreeProcessor().process(node);
         // generate SQL
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(context));
+        DefaultSQLAppendable buffer = new DefaultSQLAppendable(
+                
context.getAdapter().getQuotingStrategy(context.getRootDbEntity()));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(buffer, 
context);
         node.visit(visitor);
         context.setFinalSQL(visitor.getSQLString());
     }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorContext.java
index 2e2c9bdba..a13d605d7 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorContext.java
@@ -92,7 +92,7 @@ class SelectTranslatorContext implements SQLGenerationContext 
{
      * - order by expressions
      * - where expression (including qualifiers from all used DbEntities and 
ObjEntities)
      */
-    private final Collection<PSParameter> bindings;
+    private final List<PSParameter<?>> bindings;
 
     // Translated query
     private final TranslatableQueryWrapper query;
@@ -139,7 +139,7 @@ class SelectTranslatorContext implements 
SQLGenerationContext {
         this.parentContext = parentContext;
         this.tableTree = new TableTree(metadata.getDbEntity(), parentContext 
== null ? null : parentContext.getTableTree());
         this.columns = new ArrayList<>();
-        this.bindings = new ArrayList<>(4);
+        this.bindings = new ArrayList<>();
         this.selectBuilder = SQLBuilder.select();
         this.pathTranslator = new PathTranslator(this);
         this.qualifierTranslator = new QualifierTranslator(this);
@@ -191,7 +191,7 @@ class SelectTranslatorContext implements 
SQLGenerationContext {
         return columns;
     }
 
-    public Collection<PSParameter> getBindings() {
+    public List<PSParameter<?>> getBindings() {
         return bindings;
     }
 
@@ -233,6 +233,11 @@ class SelectTranslatorContext implements 
SQLGenerationContext {
         return metadata.getDbEntity();
     }
 
+    @Override
+    public boolean isSingleTableSQL() {
+        return tableTree.totalAliasCount() == 1;
+    }
+
     boolean hasAggregate() {
         if (hasAggregate != null) {
             return hasAggregate;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
index 276282c64..385576c48 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java
@@ -19,8 +19,11 @@
 
 package org.apache.cayenne.access.translator.select;
 
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
@@ -52,13 +55,18 @@ class TableTree {
     private final TableTreeNode rootNode;
 
     private TableTreeNode activeNode;
-    private int tableAliasSequence;
+
+    // alias bookkeeping, maintained only in the root tree (parentTree == 
null); nested trees
+    // delegate generation upwards so that aliases stay unique across the 
whole statement
+    private final Set<String> usedAliases;
+    private final Map<String, Integer> nextAliasSuffix;
 
     TableTree(DbEntity root, TableTree parentTree) {
-        this.tableAliasSequence = 0;
         this.parentTree = parentTree;
+        this.usedAliases = new HashSet<>();
+        this.nextAliasSuffix = new HashMap<>();
         this.tableNodes = new LinkedHashMap<>();
-        this.rootNode = new TableTreeNode(root, nextTableAlias());
+        this.rootNode = new TableTreeNode(root, nextTableAlias(root));
     }
 
     void addJoinTable(CayennePath path, DbRelationship relationship, JoinType 
joinType) {
@@ -71,7 +79,8 @@ class TableTree {
             return;
         }
 
-        TableTreeNode node = new TableTreeNode(path, relationship, 
nextTableAlias(), joinType, additionalQualifier);
+        String alias = nextTableAlias(relationship.getTargetEntity());
+        TableTreeNode node = new TableTreeNode(path, relationship, alias, 
joinType, additionalQualifier);
         tableNodes.put(path, node);
     }
 
@@ -90,12 +99,23 @@ class TableTree {
         return node.getTableAlias();
     }
 
-    String nextTableAlias() {
-        // delegate actual generation to parent if any
+    String nextTableAlias(DbEntity entity) {
+        // delegate actual generation to parent if any, so that aliases stay 
unique across the
+        // whole statement (including correlated subqueries)
         if(parentTree != null) {
-            return parentTree.nextTableAlias();
+            return parentTree.nextTableAlias(entity);
+        }
+
+        // the base is already guaranteed to be non-reserved (see 
DbEntity.getTableAliasBase), and a
+        // numeric suffix can never produce a reserved word, so we only need 
to resolve collisions
+        String base = entity == null ? "t" : entity.getTableAliasBase();
+        int suffix = nextAliasSuffix.getOrDefault(base, 0);
+        String candidate = suffix == 0 ? base : base + suffix;
+        while (!usedAliases.add(candidate)) {
+            candidate = base + (++suffix);
         }
-        return 't' + String.valueOf(tableAliasSequence++);
+        nextAliasSuffix.put(base, suffix + 1);
+        return candidate;
     }
 
     TableTreeNode nonNullActiveNode() {
@@ -113,6 +133,16 @@ class TableTree {
         return tableNodes.size() + 1;
     }
 
+    /**
+     * Returns the number of distinct table aliases used across the whole 
statement, including nested
+     * (subquery) trees, as tracked during translation. A value of 1 means the 
statement uses a single
+     * table and aliases can be omitted from the generated SQL.
+     */
+    int totalAliasCount() {
+        // all aliases are registered in the root tree, as nested trees 
delegate generation upwards
+        return parentTree != null ? parentTree.totalAliasCount() : 
usedAliases.size();
+    }
+
     boolean hasToManyJoin() {
         if(getNodeCount() <= 1) {
             return false;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
index 46837200f..d857bf2c9 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/derby/sqltree/DerbyValueNode.java
@@ -19,13 +19,14 @@
 
 package org.apache.cayenne.dba.derby.sqltree;
 
-import java.sql.Types;
-
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
 import org.apache.cayenne.map.DbAttribute;
 
+import java.sql.Types;
+
 /**
  * @since 4.2
  */
@@ -35,13 +36,14 @@ public class DerbyValueNode extends ValueNode {
         super(value, isArray, attribute, needBinding);
     }
 
-    protected void appendStringValue(SQLAppendable buffer, CharSequence value) 
{
+    @Override
+    protected void appendStringValue(SQLAppendable buffer, 
SQLGenerationContext context, CharSequence value) {
         if(getAttribute() == null || (getAttribute() != null && 
getAttribute().getType() == Types.CLOB)) {
             buffer.append(" CAST(? AS 
VARCHAR(").append(Math.max(1,value.length())).append("))");
         } else {
             buffer.append(" ?");
         }
-        addValueBinding(buffer, value);
+        addValueBinding(context, value);
     }
 
     @Override
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
index 0d44695dd..a32ef8ff0 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
@@ -24,6 +24,7 @@ import java.util.List;
 
 import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
@@ -51,9 +52,9 @@ public class FirebirdSQLTreeProcessor extends 
BaseSQLTreeProcessor {
     protected void onValueNode(Node parent, ValueNode child, int index) {
         replaceChild(parent, index, new ValueNode(child.getValue(), 
child.isArray(), child.getAttribute(), child.isNeedBinding()) {
             @Override
-            protected void appendStringValue(SQLAppendable buffer, 
CharSequence value) {
+            protected void appendStringValue(SQLAppendable buffer, 
SQLGenerationContext context, CharSequence value) {
                 buffer.append("CAST(");
-                super.appendStringValue(buffer, value);
+                super.appendStringValue(buffer, context, value);
                 buffer.append(" AS 
VARCHAR(").append(Math.max(1,value.length())).append("))");
             }
         });
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
index 7065f540e..d206b622e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.firebird.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
 /**
@@ -35,7 +36,7 @@ public class FirebirdLimitNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         buffer.append(" ROWS ");
         if(from > 0) {
             buffer.append(from).append(" TO ");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
index 57156f5aa..d80489900 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.ingres;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
@@ -43,7 +44,7 @@ public class IngressSQLTreeProcessor extends 
BaseSQLTreeProcessor {
     protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int 
index) {
         replaceChild(parent, index, new OffsetFetchNextNode(child) {
             @Override
-            public SQLAppendable append(SQLAppendable buffer) {
+            public SQLAppendable append(SQLAppendable buffer, 
SQLGenerationContext context) {
                 // OFFSET X FETCH NEXT Y ROWS ONLY
                 if(offset > 0) {
                     buffer.append("OFFSET ").append(offset);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/sqltree/ConvertNode.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/sqltree/ConvertNode.java
index 33efa039f..1c84a852a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/sqltree/ConvertNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/sqltree/ConvertNode.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.dba.mysql.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
 /**
@@ -32,7 +33,7 @@ public class ConvertNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         return buffer.append("CONVERT(");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
index 170dd4804..445b06e02 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.postgres.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
 /**
@@ -33,7 +34,7 @@ public class PostgresExtractFunctionNode extends Node {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         buffer.append(" EXTRACT(");
         if ("DAY_OF_MONTH".equals(functionName)) {
             buffer.append("day");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
index 20a5abdd2..45bc1fd87 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.postgres.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
@@ -37,7 +38,7 @@ public class PostgresLimitOffsetNode extends LimitOffsetNode {
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         if(limit == 0 && offset == 0) {
             return buffer;
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
index 0abce311a..cb4aa1cc2 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerColumnNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.sqlserver.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
 
@@ -33,9 +34,9 @@ public class SQLServerColumnNode extends TrimmingColumnNode {
     }
 
     @Override
-    protected void appendClobColumnNode(SQLAppendable buffer) {
+    protected void appendClobColumnNode(SQLAppendable buffer, 
SQLGenerationContext context) {
         buffer.append(" CAST(");
-        appendColumnNode(buffer);
+        appendColumnNode(buffer, context);
         buffer.append(" AS NVARCHAR(MAX))");
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
index 291b17390..355cc0b70 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/sqltree/SQLServerLimitOffsetNode.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba.sqlserver.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
@@ -33,7 +34,7 @@ public class SQLServerLimitOffsetNode extends LimitOffsetNode 
{
     }
 
     @Override
-    public SQLAppendable append(SQLAppendable buffer) {
+    public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
         // OFFSET X ROWS FETCH NEXT Y ROWS ONLY
         if(limit == 0 && offset == 0) {
             return buffer;
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/DbEntity.java 
b/cayenne/src/main/java/org/apache/cayenne/map/DbEntity.java
index dd7ee1e16..78c0e54d6 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/DbEntity.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/DbEntity.java
@@ -66,6 +66,13 @@ public class DbEntity extends Entity<DbEntity, DbAttribute, 
DbRelationship>
      */
     protected Expression qualifier;
 
+    /**
+     * Lazily computed, cached mnemonic base for table aliases in generated 
SQL.
+     *
+     * @since 5.0
+     */
+    private volatile String tableAliasBase;
+
     /**
      * Creates an unnamed DbEntity.
      */
@@ -133,6 +140,63 @@ public class DbEntity extends Entity<DbEntity, 
DbAttribute, DbRelationship>
                 + name;
     }
 
+    @Override
+    public void setName(String name) {
+        super.setName(name);
+        // invalidate the cached alias base, as it is derived from the name
+        this.tableAliasBase = null;
+    }
+
+    /**
+     * Returns a short mnemonic base used to build table aliases in generated 
SQL. It is derived from
+     * the entity name treated as "snake case", taking the first letter of 
each name part, in lower
+     * case (e.g. "USER_ROLE" becomes "ur", "ARTIST" becomes "a"). If the 
result collides with a SQL
+     * reserved word (e.g. "IV_SUB1" would become "is"), a numeric suffix is 
appended to make it safe.
+     * The value is computed once and cached. Actual aliases in a query may 
append a further numeric
+     * suffix to this base to resolve conflicts between tables.
+     *
+     * @since 5.0
+     */
+    public String getTableAliasBase() {
+        String result = tableAliasBase;
+        if (result == null) {
+            result = computeTableAliasBase(name);
+            tableAliasBase = result;
+        }
+        return result;
+    }
+
+    private static String computeTableAliasBase(String name) {
+        if (name == null || name.isEmpty()) {
+            return "t";
+        }
+
+        StringBuilder base = new StringBuilder();
+        boolean newPart = true;
+        for (int i = 0; i < name.length(); i++) {
+            char c = name.charAt(i);
+            if (c == '_') {
+                newPart = true;
+            } else if (newPart) {
+                base.append(Character.toLowerCase(c));
+                newPart = false;
+            }
+        }
+
+        // fall back to a safe alias if the result is empty or not a valid 
identifier start
+        if (base.length() == 0 || !Character.isLetter(base.charAt(0))) {
+            return "t";
+        }
+
+        // avoid aliases that collide with a SQL reserved word; a numeric 
suffix always resolves it,
+        // as no reserved word ends with a digit
+        String result = base.toString();
+        for (int i = 1; SqlReservedWords.isReserved(result); i++) {
+            result = base.toString() + i;
+        }
+        return result;
+    }
+
     /**
      * Returns database schema of this table.
      *
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/SqlReservedWords.java 
b/cayenne/src/main/java/org/apache/cayenne/map/SqlReservedWords.java
new file mode 100644
index 000000000..b08dfa192
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/map/SqlReservedWords.java
@@ -0,0 +1,79 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.cayenne.map;
+
+import java.util.Set;
+
+/**
+ * A set of SQL reserved words used to avoid generating unquoted table aliases 
that would collide with keywords. This
+ * is a broad union of the SQL standard and common reserved words across the 
databases Cayenne supports; it does not
+ * need to be exhaustive, only good enough that mnemonic aliases stay safe.
+ */
+final class SqlReservedWords {
+
+    private static final Set<String> WORDS = Set.of(
+            "ABSOLUTE", "ACTION", "ADD", "ALL", "ALLOCATE", "ALTER", "AND", 
"ANY", "ARE", "AS",
+            "ASC", "ASSERTION", "AT", "AUTHORIZATION", "AVG",
+            "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BOOLEAN", "BOTH", 
"BY",
+            "CALL", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", 
"CHARACTER", "CHECK",
+            "CLOSE", "COALESCE", "COLLATE", "COLLATION", "COLUMN", "COMMIT", 
"CONNECT", "CONNECTION",
+            "CONSTRAINT", "CONTINUE", "CONVERT", "CORRESPONDING", "COUNT", 
"CREATE", "CROSS",
+            "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", 
"CURRENT_USER", "CURSOR",
+            "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", 
"DEFAULT", "DEFERRABLE",
+            "DEFERRED", "DELETE", "DESC", "DESCRIBE", "DIAGNOSTICS", 
"DISCONNECT", "DISTINCT",
+            "DOMAIN", "DOUBLE", "DROP",
+            "ELSE", "END", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC", "EXECUTE", 
"EXISTS", "EXTERNAL",
+            "EXTRACT",
+            "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", 
"FROM", "FULL", "FUNCTION",
+            "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP",
+            "HAVING", "HOUR",
+            "IDENTITY", "IF", "IMMEDIATE", "IN", "INDEX", "INDICATOR", 
"INITIALLY", "INNER", "INPUT",
+            "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", 
"INTERVAL", "INTO", "IS",
+            "ISOLATION",
+            "JOIN",
+            "KEY",
+            "LANGUAGE", "LAST", "LEADING", "LEFT", "LEVEL", "LIKE", "LIMIT", 
"LOCAL", "LOWER",
+            "MATCH", "MAX", "MIN", "MINUTE", "MODULE", "MONTH",
+            "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NOT", 
"NULL", "NULLIF", "NUMERIC",
+            "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR", 
"ORDER", "OUTER", "OUTPUT",
+            "OVER", "OVERLAPS",
+            "PAD", "PARTIAL", "PARTITION", "POSITION", "PRECISION", "PREPARE", 
"PRESERVE", "PRIMARY",
+            "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC",
+            "READ", "REAL", "REFERENCES", "RELATIVE", "RESTRICT", "REVOKE", 
"RIGHT", "ROLLBACK",
+            "ROW", "ROWS",
+            "SCHEMA", "SCROLL", "SECOND", "SECTION", "SELECT", "SESSION", 
"SESSION_USER", "SET",
+            "SIZE", "SMALLINT", "SOME", "SPACE", "SQL", "SQLCODE", "SQLERROR", 
"SQLSTATE", "START",
+            "SUBSTRING", "SUM", "SYSTEM_USER",
+            "TABLE", "TEMPORARY", "THEN", "TIME", "TIMESTAMP", 
"TIMEZONE_HOUR", "TIMEZONE_MINUTE",
+            "TO", "TOP", "TRAILING", "TRANSACTION", "TRANSLATE", 
"TRANSLATION", "TRIGGER", "TRIM",
+            "TRUE",
+            "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPPER", "USAGE", "USER", 
"USING",
+            "VALUE", "VALUES", "VARCHAR", "VARYING", "VIEW",
+            "WHEN", "WHENEVER", "WHERE", "WHILE", "WITH", "WORK", "WRITE",
+            "YEAR",
+            "ZONE");
+
+    static boolean isReserved(String word) {
+        return WORDS.contains(word.toUpperCase());
+    }
+
+    private SqlReservedWords() {
+    }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/Cay2641IT.java 
b/cayenne/src/test/java/org/apache/cayenne/access/Cay2641IT.java
index fca827034..7b7c0a259 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/Cay2641IT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/Cay2641IT.java
@@ -70,16 +70,17 @@ public class Cay2641IT {
         TranslatedSelect translator = new 
DbAdapterDelegatedSelectTranslator().translate(artists, adapter, 
env.context().getEntityResolver());
 
         String sql = translator.sql();
-        assertFalse(sql.contains("t0.NAME"));
+        // single-table query: no alias, so the lazy NAME column is simply 
absent
+        assertFalse(sql.contains(" NAME"));
 
-        String pattern = "SELECT t0.SURNAME( c0)?, t0.ID( c1)? FROM ArtistLazy 
t0";
+        String pattern = "SELECT SURNAME( c0)?, ID( c1)? FROM ArtistLazy";
         MatcherAssert.assertThat(sql, Matchers.matchesPattern(pattern));
 
         ColumnSelect<String> select = 
ObjectSelect.columnQuery(ArtistLazy.class, ArtistLazy.NAME);
         translator = new 
DbAdapterDelegatedSelectTranslator().translate(select, adapter, 
env.context().getEntityResolver());
         sql = translator.sql();
 
-        assertTrue(sql.contains("t0.NAME"));
+        assertTrue(sql.contains("NAME"));
     }
 
     @Test
@@ -110,10 +111,10 @@ public class Cay2641IT {
         ObjectSelect<PaintingLazy> paintingLazyObjectSelect = 
ObjectSelect.query(PaintingLazy.class).prefetch(PaintingLazy.ARTIST.joint());
         TranslatedSelect translator = new 
DbAdapterDelegatedSelectTranslator().translate(paintingLazyObjectSelect, 
adapter, env.context().getEntityResolver());
         String sql = translator.sql();
-        assertFalse(sql.contains("t0.NAME"));
+        assertFalse(sql.contains(".NAME"));
 
-        String pattern = "SELECT t0.ARTIST_ID( c0)?, t0.ID( c1)?, t1.ID( c2)?, 
t1.SURNAME( c3)?"
-                + " FROM PaintingLazy t0 LEFT JOIN ArtistLazy t1 ON 
t0.ARTIST_ID = t1.ID";
+        String pattern = "SELECT p.ARTIST_ID( c0)?, p.ID( c1)?, a.ID( c2)?, 
a.SURNAME( c3)?"
+                + " FROM PaintingLazy p LEFT JOIN ArtistLazy a ON p.ARTIST_ID 
= a.ID";
         MatcherAssert.assertThat(sql, Matchers.matchesPattern(pattern));
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/BaseSqlBuilderTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/BaseSqlBuilderTest.java
index d7a16201f..1008d1ed3 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/BaseSqlBuilderTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/BaseSqlBuilderTest.java
@@ -29,19 +29,19 @@ public class BaseSqlBuilderTest {
     }
 
     protected void assertQuotedSQL(String expected, Node node) {
-        assertSQL(expected, node, new TestSQLAppendable(null));
+        assertSQL(expected, node, new TestSQLAppendable());
     }
 
     protected void assertSQL(String expected, Node node, SQLAppendable 
appendable) {
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(appendable);
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(appendable, 
null);
         node.visit(visitor);
         assertEquals(expected, visitor.getSQLString());
     }
 
     protected static class TestSQLAppendable extends DefaultSQLAppendable {
 
-        public TestSQLAppendable(SQLGenerationContext context) {
-            super(context);
+        public TestSQLAppendable() {
+            super(null);
         }
 
         @Override
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
index a4a1f09e4..00296f7af 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeTest.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
 import org.apache.cayenne.access.sqlbuilder.SQLAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -34,7 +35,7 @@ public class NodeTest {
         }
 
         @Override
-        public SQLAppendable append(SQLAppendable buffer) {
+        public SQLAppendable append(SQLAppendable buffer, SQLGenerationContext 
context) {
             return buffer.append("test");
         }
     }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DbAdapterDelegatedSelectTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DbAdapterDelegatedSelectTranslatorIT.java
index 2171dd298..f9bb44035 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DbAdapterDelegatedSelectTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DbAdapterDelegatedSelectTranslatorIT.java
@@ -259,14 +259,10 @@ public class DbAdapterDelegatedSelectTranslatorIT {
                assertTrue(generatedSql.indexOf(" FROM ") > 0);
                assertTrue(generatedSql.indexOf(" WHERE ") > 
generatedSql.indexOf(" FROM "));
 
-               // check that there are 2 distinct aliases for the ARTIST table
-               int ind1 = generatedSql.indexOf("ARTIST t", 
generatedSql.indexOf(" FROM "));
-               assertTrue(ind1 > 0);
-
-               int ind2 = generatedSql.indexOf("ARTIST t", ind1 + 1);
-               assertTrue(ind2 > 0);
-
-               assertTrue(generatedSql.charAt(ind1 + "ARTIST t".length()) != 
generatedSql.charAt(ind2 + "ARTIST t".length()));
+               // check that there are 2 distinct aliases for the ARTIST table 
(leading space excludes
+               // ARTIST_EXHIBIT / ARTIST_GROUP, trailing space excludes the 
second one for the first match)
+               assertTrue(generatedSql.contains(" ARTIST a "), generatedSql);
+               assertTrue(generatedSql.contains(" ARTIST a1 "), generatedSql);
        }
 
        /**
@@ -289,12 +285,12 @@ public class DbAdapterDelegatedSelectTranslatorIT {
                assertTrue(generatedSql.indexOf(" FROM ") > 0);
                assertTrue(generatedSql.indexOf(" WHERE ") > 
generatedSql.indexOf(" FROM "));
 
-               // check that there is only one distinct alias for the ARTIST
-               // table
-               int ind1 = generatedSql.indexOf("ARTIST t", 
generatedSql.indexOf(" FROM "));
-               assertTrue(ind1 > 0);
+               // check that there is only one distinct alias for the ARTIST 
table (leading and trailing
+               // spaces exclude ARTIST_EXHIBIT and the ARTIST_ID / 
ARTIST_NAME columns)
+               int ind1 = generatedSql.indexOf(" ARTIST ", 
generatedSql.indexOf(" FROM "));
+               assertTrue(ind1 > 0, generatedSql);
 
-               int ind2 = generatedSql.indexOf("ARTIST t", ind1 + 1);
+               int ind2 = generatedSql.indexOf(" ARTIST ", ind1 + 1);
                assertTrue(ind2 < 0, generatedSql);
        }
 
@@ -501,20 +497,20 @@ public class DbAdapterDelegatedSelectTranslatorIT {
                        String charEnd = 
env.testDbAdapter().getIdentifiersEndQuote();
 
                        String s = new 
DbAdapterDelegatedSelectTranslator().translate(q, env.dataNode().getAdapter(), 
env.dataNode().getEntityResolver()).sql();
+                       // single-table query: no table aliases are generated
                        assertTrue(s.startsWith("SELECT "));
                        int iFrom = s.indexOf(" FROM ");
                        assertTrue(iFrom > 0);
-                       int artistName = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
+                       int artistName = s.indexOf(charStart + "ARTIST_NAME" + 
charEnd);
                        assertTrue(artistName > 0 && artistName < iFrom);
-                       int artistId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistId = s.indexOf(charStart + "ARTIST_ID" + 
charEnd);
                        assertTrue(artistId > 0 && artistId < iFrom);
-                       int dateOfBirth = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd);
+                       int dateOfBirth = s.indexOf(charStart + "DATE_OF_BIRTH" 
+ charEnd);
                        assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom);
-                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "t0" + charEnd);
+                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd);
                        assertTrue(iArtist > iFrom);
                        int iOrderBy = s.indexOf(" ORDER BY ");
-                       int dateOfBirth2 = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd,
-                                       iOrderBy);
+                       int dateOfBirth2 = s.indexOf(charStart + 
"DATE_OF_BIRTH" + charEnd, iOrderBy);
                        assertTrue(iOrderBy > iArtist);
                        assertTrue(dateOfBirth2 > iOrderBy);
 
@@ -541,26 +537,27 @@ public class DbAdapterDelegatedSelectTranslatorIT {
 
                        String s = new 
DbAdapterDelegatedSelectTranslator().translate(q, env.dataNode().getAdapter(), 
env.dataNode().getEntityResolver()).sql();
 
+                       // single-table query: no table aliases are generated
                        assertTrue(s.startsWith("SELECT "));
                        int iFrom = s.indexOf(" FROM ");
                        assertTrue(iFrom > 0);
-                       int artistName = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
+                       int artistName = s.indexOf(charStart + "ARTIST_NAME" + 
charEnd);
                        assertTrue(artistName > 0 && artistName < iFrom);
-                       int artistId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistId = s.indexOf(charStart + "ARTIST_ID" + 
charEnd);
                        assertTrue(artistId > 0 && artistId < iFrom);
-                       int dateOfBirth = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd);
+                       int dateOfBirth = s.indexOf(charStart + "DATE_OF_BIRTH" 
+ charEnd);
                        assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom);
-                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "t0" + charEnd);
+                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd);
                        assertTrue(iArtist > iFrom);
                        int iWhere = s.indexOf(" WHERE ");
                        assertTrue(iWhere > iArtist);
 
-                       int dateOfBirth2 = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd + " > ?");
+                       int dateOfBirth2 = s.indexOf(charStart + 
"DATE_OF_BIRTH" + charEnd + " > ?");
                        assertTrue(dateOfBirth2 > iWhere);
 
                        int iAnd = s.indexOf(" AND ");
                        assertTrue(iAnd > iWhere);
-                       int dateOfBirth3 = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd + " < ?");
+                       int dateOfBirth3 = s.indexOf(charStart + 
"DATE_OF_BIRTH" + charEnd + " < ?");
                        assertTrue(dateOfBirth3 > iAnd);
 
                } finally {
@@ -587,59 +584,60 @@ public class DbAdapterDelegatedSelectTranslatorIT {
 
                        String s = new 
DbAdapterDelegatedSelectTranslator().translate(q, env.dataNode().getAdapter(), 
env.dataNode().getEntityResolver()).sql();
 
+                       // root ARTIST -> "a", PAINTING (where join) -> "p", 
PAINTING (prefetch left join) -> "p1"
                        assertTrue(s.startsWith("SELECT DISTINCT "), s);
                        int iFrom = s.indexOf(" FROM ");
                        assertTrue(iFrom > 0, s);
-                       int artistName = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
+                       int artistName = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
                        assertTrue(artistName > 0 && artistName < iFrom, s);
-                       int artistId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistId = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
                        assertTrue(artistId > 0 && artistId < iFrom, s);
-                       int dateOfBirth = s.indexOf(charStart + "t0" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd);
+                       int dateOfBirth = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "DATE_OF_BIRTH" + charEnd);
                        assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom, s);
-                       int estimatedPrice = s.indexOf(charStart + "t2" + 
charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
+                       int estimatedPrice = s.indexOf(charStart + "p1" + 
charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
                        assertTrue(estimatedPrice > 0 && estimatedPrice < 
iFrom, s);
-                       int paintingDescription = s.indexOf(charStart + "t2" + 
charEnd + "." + charStart + "PAINTING_DESCRIPTION"
+                       int paintingDescription = s.indexOf(charStart + "p1" + 
charEnd + "." + charStart + "PAINTING_DESCRIPTION"
                                        + charEnd);
                        assertTrue(paintingDescription > 0 && 
paintingDescription < iFrom, s);
-                       int paintingTitle = s.indexOf(charStart + "t2" + 
charEnd + "." + charStart + "PAINTING_TITLE" + charEnd);
+                       int paintingTitle = s.indexOf(charStart + "p1" + 
charEnd + "." + charStart + "PAINTING_TITLE" + charEnd);
                        assertTrue(paintingTitle > 0 && paintingTitle < iFrom, 
s);
-                       int artistIdT2 = s.indexOf(charStart + "t2" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistIdT2 = s.indexOf(charStart + "p1" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
                        assertTrue(artistIdT2 > 0 && artistIdT2 < iFrom, s);
-                       int galleryId = s.indexOf(charStart + "t2" + charEnd + 
"." + charStart + "GALLERY_ID" + charEnd);
+                       int galleryId = s.indexOf(charStart + "p1" + charEnd + 
"." + charStart + "GALLERY_ID" + charEnd);
                        assertTrue(galleryId > 0 && galleryId < iFrom, s);
-                       int paintingId = s.indexOf(charStart + "t2" + charEnd + 
"." + charStart + "PAINTING_ID" + charEnd);
+                       int paintingId = s.indexOf(charStart + "p1" + charEnd + 
"." + charStart + "PAINTING_ID" + charEnd);
                        assertTrue(paintingId > 0 && paintingId < iFrom, s);
-                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "t0" + charEnd);
+                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "a" + charEnd);
                        assertTrue(iArtist > iFrom, s);
                        int iJoin = s.indexOf("JOIN");
                        assertTrue(iJoin > iFrom, s);
-                       int iPainting2 = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "t1" + charEnd);
+                       int iPainting2 = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "p" + charEnd);
                        assertTrue(iPainting2 > iJoin, s);
                        int iOn2 = s.indexOf(" ON ", iJoin);
                        assertTrue(iOn2 > iJoin, s);
-                       int iArtistId2 = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iJoin);
+                       int iArtistId2 = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iJoin);
                        assertTrue(iArtistId2 > iOn2, s);
-                       int iArtistId2T1 = s.indexOf(charStart + "t1" + charEnd 
+ "." + charStart + "ARTIST_ID" + charEnd, iJoin);
+                       int iArtistId2T1 = s.indexOf(charStart + "p" + charEnd 
+ "." + charStart + "ARTIST_ID" + charEnd, iJoin);
                        assertTrue(iArtistId2T1 > iOn2, s);
                        int i2 = s.indexOf("=", iJoin);
                        assertTrue(iArtistId2T1 > i2 || iArtistId2 > i2, s);
                        int iLeftJoin = s.indexOf("LEFT JOIN");
                        assertTrue(iLeftJoin > iJoin, s);
-                       int iPainting = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "t2" + charEnd);
+                       int iPainting = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "p1" + charEnd);
                        assertTrue(iPainting > iLeftJoin, s);
                        int iOn = s.indexOf(" ON ", iLeftJoin);
                        assertTrue(iOn > iLeftJoin, s);
-                       int iArtistId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
+                       int iArtistId = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
                        assertTrue(iArtistId > iOn, s);
                        int iArtistIdT2 = s
-                                       .indexOf(charStart + "t2" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
+                                       .indexOf(charStart + "p1" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
                        assertTrue(iArtistIdT2 > iOn, s);
                        int i = s.indexOf("=", iLeftJoin);
                        assertTrue(iArtistIdT2 > i || iArtistId > i, s);
                        int iWhere = s.indexOf(" WHERE ");
                        assertTrue(iWhere > iLeftJoin, s);
 
-                       int paintingTitle2 = s.indexOf(charStart + "t1" + 
charEnd + "." + charStart + "PAINTING_TITLE" + charEnd + " = ?");
+                       int paintingTitle2 = s.indexOf(charStart + "p" + 
charEnd + "." + charStart + "PAINTING_TITLE" + charEnd + " = ?");
                        assertTrue(paintingTitle2 > iWhere, s);
 
                } finally {
@@ -664,42 +662,43 @@ public class DbAdapterDelegatedSelectTranslatorIT {
 
                        String s = new 
DbAdapterDelegatedSelectTranslator().translate(q, env.dataNode().getAdapter(), 
env.dataNode().getEntityResolver()).sql();
 
+                       // root PAINTING -> "p", ARTIST (prefetch left join) -> 
"a"
                        assertTrue(s.startsWith("SELECT "), s);
                        int iFrom = s.indexOf(" FROM ");
                        assertTrue(iFrom > 0, s);
 
-                       int paintingDescription = s.indexOf(charStart + "t0" + 
charEnd + "." + charStart + "PAINTING_DESCRIPTION"
+                       int paintingDescription = s.indexOf(charStart + "p" + 
charEnd + "." + charStart + "PAINTING_DESCRIPTION"
                                        + charEnd);
                        assertTrue(paintingDescription > 0 && 
paintingDescription < iFrom, s);
-                       int paintingTitle = s.indexOf(charStart + "t0" + 
charEnd + "." + charStart + "PAINTING_TITLE" + charEnd);
+                       int paintingTitle = s.indexOf(charStart + "p" + charEnd 
+ "." + charStart + "PAINTING_TITLE" + charEnd);
                        assertTrue(paintingTitle > 0 && paintingTitle < iFrom, 
s);
-                       int artistIdT1 = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistIdT1 = s.indexOf(charStart + "p" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
                        assertTrue(artistIdT1 > 0 && artistIdT1 < iFrom, s);
-                       int estimatedPrice = s.indexOf(charStart + "t0" + 
charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
+                       int estimatedPrice = s.indexOf(charStart + "p" + 
charEnd + "." + charStart + "ESTIMATED_PRICE" + charEnd);
                        assertTrue(estimatedPrice > 0 && estimatedPrice < 
iFrom, s);
-                       int galleryId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "GALLERY_ID" + charEnd);
+                       int galleryId = s.indexOf(charStart + "p" + charEnd + 
"." + charStart + "GALLERY_ID" + charEnd);
                        assertTrue(galleryId > 0 && galleryId < iFrom, s);
-                       int paintingId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "PAINTING_ID" + charEnd);
+                       int paintingId = s.indexOf(charStart + "p" + charEnd + 
"." + charStart + "PAINTING_ID" + charEnd);
                        assertTrue(paintingId > 0 && paintingId < iFrom, s);
-                       int artistName = s.indexOf(charStart + "t1" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
+                       int artistName = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_NAME" + charEnd);
                        assertTrue(artistName > 0 && artistName < iFrom, s);
-                       int artistId = s.indexOf(charStart + "t1" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
+                       int artistId = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd);
                        assertTrue(artistId > 0 && artistId < iFrom, s);
-                       int dateOfBirth = s.indexOf(charStart + "t1" + charEnd 
+ "." + charStart + "DATE_OF_BIRTH" + charEnd);
+                       int dateOfBirth = s.indexOf(charStart + "a" + charEnd + 
"." + charStart + "DATE_OF_BIRTH" + charEnd);
                        assertTrue(dateOfBirth > 0 && dateOfBirth < iFrom, s);
-                       int iPainting = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "t0" + charEnd);
+                       int iPainting = s.indexOf(charStart + "PAINTING" + 
charEnd + " " + charStart + "p" + charEnd);
                        assertTrue(iPainting > iFrom, s);
 
                        int iLeftJoin = s.indexOf("LEFT JOIN");
                        assertTrue(iLeftJoin > iFrom, s);
-                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "t1" + charEnd);
+                       int iArtist = s.indexOf(charStart + "ARTIST" + charEnd 
+ " " + charStart + "a" + charEnd);
                        assertTrue(iArtist > iLeftJoin, s);
                        int iOn = s.indexOf(" ON ");
                        assertTrue(iOn > iLeftJoin, s);
-                       int iArtistId = s.indexOf(charStart + "t0" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
+                       int iArtistId = s.indexOf(charStart + "p" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
                        assertTrue(iArtistId > iOn, s);
                        int iArtistIdT1 = s
-                                       .indexOf(charStart + "t1" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
+                                       .indexOf(charStart + "a" + charEnd + 
"." + charStart + "ARTIST_ID" + charEnd, iLeftJoin);
                        assertTrue(iArtistIdT1 > iOn, s);
                        int i = s.indexOf("=", iLeftJoin);
                        assertTrue(iArtistIdT1 > i || iArtistId > i, s);
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
index 8294a431f..3b0f172d3 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultObjectSelectTranslatorIT.java
@@ -42,10 +42,10 @@ public class DefaultObjectSelectTranslatorIT {
 
         String sql = translator.sql();
         assertTrue(sql.startsWith("SELECT "));
-        assertTrue(sql.contains("t0.ARTIST_NAME"));
-        assertTrue(sql.contains("t0.DATE_OF_BIRTH"));
-        assertTrue(sql.contains("t0.ARTIST_ID"));
-        assertTrue(sql.indexOf("FROM ARTIST t0") > 
sql.indexOf("t0.ARTIST_ID"));
+        assertTrue(sql.contains("ARTIST_NAME"));
+        assertTrue(sql.contains("DATE_OF_BIRTH"));
+        assertTrue(sql.contains("ARTIST_ID"));
+        assertTrue(sql.indexOf("FROM ARTIST") > sql.indexOf("ARTIST_ID"));
 
         assertEquals(0, translator.bindings().length);
         assertEquals(3, translator.resultColumns().length);
@@ -65,12 +65,12 @@ public class DefaultObjectSelectTranslatorIT {
 
         String sql = translator.sql();
         assertTrue(sql.startsWith("SELECT DISTINCT"));
-        assertTrue(sql.contains("t0.ARTIST_NAME"));
-        assertTrue(sql.contains("t0.DATE_OF_BIRTH"));
-        assertTrue(sql.contains("t0.ARTIST_ID"));
-        assertTrue(sql.indexOf("FROM ARTIST t0") > 
sql.indexOf("t0.ARTIST_ID"));
-        assertTrue(sql.indexOf("JOIN PAINTING t1") > sql.indexOf("FROM ARTIST 
t0"));
-        assertTrue(sql.indexOf("WHERE") > sql.indexOf("JOIN PAINTING t1"));
+        assertTrue(sql.contains("a.ARTIST_NAME"));
+        assertTrue(sql.contains("a.DATE_OF_BIRTH"));
+        assertTrue(sql.contains("a.ARTIST_ID"));
+        assertTrue(sql.indexOf("FROM ARTIST a") > sql.indexOf("a.ARTIST_ID"));
+        assertTrue(sql.indexOf("JOIN PAINTING p") > sql.indexOf("FROM ARTIST 
a"));
+        assertTrue(sql.indexOf("WHERE") > sql.indexOf("JOIN PAINTING p"));
 
         assertEquals(2, translator.bindings().length);
         assertEquals("ARTIST_NAME", 
translator.bindings()[0].attribute().getName());
@@ -94,17 +94,17 @@ public class DefaultObjectSelectTranslatorIT {
         String sql = translator.sql();
 
         assertTrue(sql.startsWith("SELECT "));
-        assertTrue(sql.contains("t0.ESTIMATED_PRICE"));
-        assertTrue(sql.contains("t0.PAINTING_DESCRIPTION"));
-        assertTrue(sql.contains("t0.PAINTING_TITLE"));
-        assertTrue(sql.contains("t0.ARTIST_ID"));
-        assertTrue(sql.contains("t0.GALLERY_ID"));
-        assertTrue(sql.contains("t0.PAINTING_ID"));
-        assertTrue(sql.contains("t1.ARTIST_ID"));
-        assertTrue(sql.contains("t1.DATE_OF_BIRTH"));
-        assertTrue(sql.contains("t1.ARTIST_NAME"));
-        assertTrue(sql.indexOf("FROM PAINTING t0") > 
sql.indexOf("t1.ARTIST_NAME"));
-        assertTrue(sql.indexOf("LEFT JOIN ARTIST t1") > sql.indexOf("FROM 
PAINTING t0"));
+        assertTrue(sql.contains("p.ESTIMATED_PRICE"));
+        assertTrue(sql.contains("p.PAINTING_DESCRIPTION"));
+        assertTrue(sql.contains("p.PAINTING_TITLE"));
+        assertTrue(sql.contains("p.ARTIST_ID"));
+        assertTrue(sql.contains("p.GALLERY_ID"));
+        assertTrue(sql.contains("p.PAINTING_ID"));
+        assertTrue(sql.contains("a.ARTIST_ID"));
+        assertTrue(sql.contains("a.DATE_OF_BIRTH"));
+        assertTrue(sql.contains("a.ARTIST_NAME"));
+        assertTrue(sql.indexOf("FROM PAINTING p") > 
sql.indexOf("a.ARTIST_NAME"));
+        assertTrue(sql.indexOf("LEFT JOIN ARTIST a") > sql.indexOf("FROM 
PAINTING p"));
 
         assertEquals(0, translator.bindings().length);
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorExistExpressionIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorExistExpressionIT.java
index 3bb2b69b2..fd9501537 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorExistExpressionIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorExistExpressionIT.java
@@ -54,8 +54,8 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE t1.ARTIST_ID = t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE p.ARTIST_ID = a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -73,8 +73,8 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE t1.ARTIST_ID = t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE p.ARTIST_ID = a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -93,7 +93,7 @@ public class QualifierTranslatorExistExpressionIT {
 
         Node node = qualifierTranslator.translate(query.getWhere());
 
-        assertSQL(" t0.ARTIST_NAME IS NOT NULL", node);
+        assertSQL(" a.ARTIST_NAME IS NOT NULL", node);
     }
 
     @Test
@@ -112,8 +112,8 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE t1.PAINTING_TITLE IS NOT NULL AND t1.ARTIST_ID = 
t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE p.PAINTING_TITLE IS NOT NULL AND p.ARTIST_ID = 
a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -133,8 +133,8 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE t1.GALLERY_ID IS NOT NULL AND t1.ARTIST_ID = 
t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE p.GALLERY_ID IS NOT NULL AND p.ARTIST_ID = a.ARTIST_ID" 
+
                 ")", node);
     }
 
@@ -154,10 +154,10 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT DISTINCT t1.ARTIST_ID, t1.GROUP_ID FROM ARTIST_GROUP 
t1 " +
-                "JOIN ARTGROUP t2 ON t1.GROUP_ID = t2.GROUP_ID " +
-                "JOIN ARTGROUP t3 ON t2.GROUP_ID = t3.PARENT_GROUP_ID " +
-                "WHERE t3.GROUP_ID IS NOT NULL AND t1.ARTIST_ID = 
t0.ARTIST_ID" +
+                "SELECT DISTINCT ag.ARTIST_ID, ag.GROUP_ID FROM ARTIST_GROUP 
ag " +
+                "JOIN ARTGROUP a1 ON ag.GROUP_ID = a1.GROUP_ID " +
+                "JOIN ARTGROUP a2 ON a1.GROUP_ID = a2.PARENT_GROUP_ID " +
+                "WHERE a2.GROUP_ID IS NOT NULL AND ag.ARTIST_ID = a.ARTIST_ID" 
+
                 ")", node);
     }
 
@@ -177,8 +177,8 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE t1.PAINTING_TITLE = 'test' AND t1.ARTIST_ID = 
t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE p.PAINTING_TITLE = 'test' AND p.ARTIST_ID = 
a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -199,9 +199,9 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE (t1.PAINTING_TITLE = 'test' OR t1.PAINTING_TITLE = 
'test2') " +
-                        "AND t1.ARTIST_ID = t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE (p.PAINTING_TITLE = 'test' OR p.PAINTING_TITLE = 
'test2') " +
+                        "AND p.ARTIST_ID = a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -221,9 +221,9 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                "WHERE (t1.PAINTING_TITLE = 'test' OR t1.PAINTING_TITLE = 
'test2') " +
-                "AND t1.ARTIST_ID = t0.ARTIST_ID" +
+                "SELECT p.PAINTING_ID FROM PAINTING p " +
+                "WHERE (p.PAINTING_TITLE = 'test' OR p.PAINTING_TITLE = 
'test2') " +
+                "AND p.ARTIST_ID = a.ARTIST_ID" +
                 ")", node);
     }
 
@@ -244,14 +244,14 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                    "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                    "WHERE (t1.PAINTING_TITLE = 'test' OR t1.PAINTING_TITLE = 
'test2') " +
-                            "AND t1.ARTIST_ID = t0.ARTIST_ID" +
+                    "SELECT p.PAINTING_ID FROM PAINTING p " +
+                    "WHERE (p.PAINTING_TITLE = 'test' OR p.PAINTING_TITLE = 
'test2') " +
+                            "AND p.ARTIST_ID = a.ARTIST_ID" +
                 ") " +
                 "AND EXISTS (" +
-                    "SELECT t2.ARTIST_ID, t2.GROUP_ID FROM ARTIST_GROUP t2 " +
-                    "JOIN ARTGROUP t3 ON t2.GROUP_ID = t3.GROUP_ID " +
-                    "WHERE t3.NAME = 'test' AND t2.ARTIST_ID = t0.ARTIST_ID)", 
node);
+                    "SELECT ag.ARTIST_ID, ag.GROUP_ID FROM ARTIST_GROUP ag " +
+                    "JOIN ARTGROUP a1 ON ag.GROUP_ID = a1.GROUP_ID " +
+                    "WHERE a1.NAME = 'test' AND ag.ARTIST_ID = a.ARTIST_ID)", 
node);
     }
 
     @Test
@@ -271,19 +271,19 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                    "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                    "WHERE t1.PAINTING_TITLE = 'test' AND t1.ARTIST_ID = 
t0.ARTIST_ID" +
+                    "SELECT p.PAINTING_ID FROM PAINTING p " +
+                    "WHERE p.PAINTING_TITLE = 'test' AND p.ARTIST_ID = 
a.ARTIST_ID" +
                 ") " +
                 "AND (" +
                     "EXISTS (" +
-                        "SELECT t2.ARTIST_ID, t2.GROUP_ID FROM ARTIST_GROUP t2 
" +
-                        "JOIN ARTGROUP t3 ON t2.GROUP_ID = t3.GROUP_ID " +
-                        "WHERE t3.NAME = 'test' AND t2.ARTIST_ID = 
t0.ARTIST_ID" +
+                        "SELECT ag.ARTIST_ID, ag.GROUP_ID FROM ARTIST_GROUP ag 
" +
+                        "JOIN ARTGROUP a1 ON ag.GROUP_ID = a1.GROUP_ID " +
+                        "WHERE a1.NAME = 'test' AND ag.ARTIST_ID = 
a.ARTIST_ID" +
                     ") " +
                     "OR " +
                     "EXISTS (" +
-                        "SELECT t4.PAINTING_ID FROM PAINTING t4 " +
-                        "WHERE t4.PAINTING_TITLE = 'test2' AND t4.ARTIST_ID = 
t0.ARTIST_ID" +
+                        "SELECT p1.PAINTING_ID FROM PAINTING p1 " +
+                        "WHERE p1.PAINTING_TITLE = 'test2' AND p1.ARTIST_ID = 
a.ARTIST_ID" +
                     ")" +
                 ")", node);
     }
@@ -304,20 +304,20 @@ public class QualifierTranslatorExistExpressionIT {
         Node node = qualifierTranslator.translate(query.getWhere());
 
         assertSQL(" EXISTS (" +
-                    "SELECT t1.PAINTING_ID FROM PAINTING t1 " +
-                    "WHERE LENGTH(t1.PAINTING_TITLE) IN (1, 2, 3) AND 
t1.ARTIST_ID = t0.ARTIST_ID" +
+                    "SELECT p.PAINTING_ID FROM PAINTING p " +
+                    "WHERE LENGTH(p.PAINTING_TITLE) IN (1, 2, 3) AND 
p.ARTIST_ID = a.ARTIST_ID" +
                 ") OR " +
                 "EXISTS (" +
-                    "SELECT t2.ARTIST_ID, t2.GROUP_ID FROM ARTIST_GROUP t2 " +
-                    "JOIN ARTGROUP t3 ON t2.GROUP_ID = t3.GROUP_ID " +
-                    "WHERE LENGTH(t3.NAME) < 10 AND t2.ARTIST_ID = 
t0.ARTIST_ID" +
+                    "SELECT ag.ARTIST_ID, ag.GROUP_ID FROM ARTIST_GROUP ag " +
+                    "JOIN ARTGROUP a1 ON ag.GROUP_ID = a1.GROUP_ID " +
+                    "WHERE LENGTH(a1.NAME) < 10 AND ag.ARTIST_ID = 
a.ARTIST_ID" +
                 ")", node);
     }
 
     protected void assertSQL(String expected, Node node) {
         assertNotNull(node);
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
         assertEquals(expected, visitor.getSQLString());
     }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
index 8e139f182..3a2d93bd5 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
@@ -70,10 +70,10 @@ public class QualifierTranslatorIT {
 
         Node node = qualifierTranslator.translate(query.getWhere());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
 
-        assertEquals(" t0.F_KEY1 = 'PK1' AND t0.F_KEY2 = 'PK2' AND t0.NAME 
LIKE 'test%' AND t0.NAME LIKE '%a%'", visitor.getSQLString());
+        assertEquals(" cft.F_KEY1 = 'PK1' AND cft.F_KEY2 = 'PK2' AND cft.NAME 
LIKE 'test%' AND cft.NAME LIKE '%a%'", visitor.getSQLString());
     }
 
     @Test
@@ -95,10 +95,10 @@ public class QualifierTranslatorIT {
 
         Node node = qualifierTranslator.translate(query.getWhere());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
 
-        assertEquals(" (t0.F_KEY1 = 'PK1' AND t0.F_KEY2 = 'PK2') OR (t0.F_KEY1 
= 'PK3' AND t0.F_KEY2 = 'PK4')", visitor.getSQLString());
+        assertEquals(" (cft.F_KEY1 = 'PK1' AND cft.F_KEY2 = 'PK2') OR 
(cft.F_KEY1 = 'PK3' AND cft.F_KEY2 = 'PK4')", visitor.getSQLString());
     }
 
     @Test
@@ -113,10 +113,10 @@ public class QualifierTranslatorIT {
 
         Node node = qualifierTranslator.translate(query.getWhere());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
 
-        assertEquals(" t0.NAME = - 1", visitor.getSQLString());
+        assertEquals(" cft.NAME = - 1", visitor.getSQLString());
 
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
index e6b3ee695..9cbd9f60e 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorTest.java
@@ -160,7 +160,7 @@ public class QualifierTranslatorTest {
         assertInstanceOf(ValueNode.class, node);
         assertNull(((ValueNode) node).getValue());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
         assertEquals(" NULL", visitor.getSQLString());
     }
@@ -170,7 +170,7 @@ public class QualifierTranslatorTest {
         Node node = translate("-1");
         assertInstanceOf(FunctionNode.class, node);
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
         assertEquals(" - 1", visitor.getSQLString());
     }
@@ -182,7 +182,7 @@ public class QualifierTranslatorTest {
         assertInstanceOf(ValueNode.class, node);
         assertNull(((ValueNode) node).getValue());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         node.visit(visitor);
         assertEquals(" NULL", visitor.getSQLString());
     }
@@ -483,9 +483,9 @@ public class QualifierTranslatorTest {
         assertEquals("AND", ((OpExpressionNode)and).getOp());
         assertEquals(3, and.getChildrenCount());
 
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         and.visit(visitor);
-        assertEquals(" t0.a < 2 AND t0.b IN (5, 6) AND t0.b = 7", 
visitor.getSQLString());
+        assertEquals(" m.a < 2 AND m.b IN (5, 6) AND m.b = 7", 
visitor.getSQLString());
     }
 
     @Test
@@ -570,7 +570,7 @@ public class QualifierTranslatorTest {
     public void translateStringScalar() {
         Expression scalarValue = ExpressionFactory.wrapScalarValue("abc");
         Node translate = translator.translate(scalarValue);
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         translate.visit(visitor);
         assertInstanceOf(ASTScalar.class, scalarValue);
         assertEquals(" 'abc'",visitor.getSQLString());
@@ -580,7 +580,7 @@ public class QualifierTranslatorTest {
     public void translateNumberScalar() {
         Expression scalarValue = ExpressionFactory.wrapScalarValue(123);
         Node translate = translator.translate(scalarValue);
-        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null));
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultSQLAppendable(null), null);
         translate.visit(visitor);
         assertInstanceOf(ASTScalar.class, scalarValue);
         assertEquals(" 123",visitor.getSQLString());
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/DbEntityTest.java 
b/cayenne/src/test/java/org/apache/cayenne/map/DbEntityTest.java
index 13562e36f..77101e3fa 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/DbEntityTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/DbEntityTest.java
@@ -30,6 +30,40 @@ import static org.junit.jupiter.api.Assertions.*;
 
 public class DbEntityTest {
 
+    @Test
+    public void tableAliasBase() {
+        assertEquals("ur", new DbEntity("USER_ROLE").getTableAliasBase());
+        assertEquals("a", new DbEntity("ARTIST").getTableAliasBase());
+        assertEquals("aeg", new 
DbEntity("ARTIST_EXHIBIT_GROUP").getTableAliasBase());
+        // mixed case, no underscores -> first letter of the single part
+        assertEquals("a", new DbEntity("ArtistLazy").getTableAliasBase());
+    }
+
+    @Test
+    public void tableAliasBaseReservedWord() {
+        // "IV_SUB1" -> "is", which is a SQL reserved word, so a numeric 
suffix is appended
+        assertEquals("is1", new DbEntity("IV_SUB1").getTableAliasBase());
+        // "ORDER" -> "o" is fine, but a name abbreviating to a reserved word 
is escaped
+        assertEquals("or1", new DbEntity("OWNER_ROLE").getTableAliasBase());
+    }
+
+    @Test
+    public void tableAliasBaseFallback() {
+        assertEquals("t", new DbEntity().getTableAliasBase());
+        assertEquals("t", new DbEntity("").getTableAliasBase());
+        // does not start with a letter -> fall back to a safe alias
+        assertEquals("t", new DbEntity("1_TABLE").getTableAliasBase());
+    }
+
+    @Test
+    public void tableAliasBaseInvalidatedOnRename() {
+        DbEntity entity = new DbEntity("USER_ROLE");
+        assertEquals("ur", entity.getTableAliasBase());
+
+        entity.setName("PAINTING");
+        assertEquals("p", entity.getTableAliasBase());
+    }
+
     @Test
     public void encodeAsXML() {
         DbEntity entity = new DbEntity("X");

Reply via email to