Skip to content

Commit e616fc6

Browse files
committed
CAY-2972 Fewer parentheses in generated SQL
1 parent 9b0d8dc commit e616fc6

15 files changed

Lines changed: 122 additions & 53 deletions

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CAY-2963 Replace TypesHandler / types.xml with hardcoded map
2222
CAY-2969 Extender API for "soft" delete
2323
CAY-2970 Tighten deferred value resolution contract on commit
2424
CAY-2971 Remove extra spaces within SQL parenthesis
25+
CAY-2972 Fewer parentheses in generated SQL
2526

2627
Bug Fixes:
2728

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public void appendChildrenSeparator(SQLAppendable buffer, int childIdx) {
4747
}
4848
}
4949

50+
@Override
51+
protected boolean isComparison() {
52+
return true;
53+
}
54+
5055
@Override
5156
public Node copy() {
5257
return new BetweenNode(not);

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ElseNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
*/
2727
public class ElseNode extends Node {
2828

29+
public ElseNode() {
30+
super(NodeType.ELSE);
31+
}
32+
2933
@Override
3034
public SQLAppendable append(SQLAppendable buffer) {
3135
return buffer.appendTokenSeparator().append("ELSE");

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,57 @@ public SQLAppendable append(SQLAppendable buffer) {
4141

4242
@Override
4343
public void appendChildrenStart(SQLAppendable buffer) {
44-
if(parent != null
45-
&& parent.type != NodeType.WHERE
46-
&& parent.type != NodeType.JOIN
47-
&& parent.type != NodeType.UPDATE_SET) {
44+
if (needsParentheses()) {
4845
buffer.appendTokenSeparator().append('(').suppressNextTokenSeparator();
4946
}
5047
}
5148

5249
@Override
5350
public void appendChildrenEnd(SQLAppendable buffer) {
54-
if(parent != null
55-
&& parent.type != NodeType.WHERE
56-
&& parent.type != NodeType.JOIN
57-
&& parent.type != NodeType.UPDATE_SET) {
51+
if (needsParentheses()) {
5852
buffer.append(")");
5953
}
6054
}
6155

56+
protected boolean needsParentheses() {
57+
if (parent == null
58+
|| parent.type == NodeType.WHERE
59+
|| parent.type == NodeType.JOIN
60+
|| parent.type == NodeType.UPDATE_SET
61+
|| parent.type == NodeType.WHEN
62+
|| parent.type == NodeType.THEN
63+
|| parent.type == NodeType.ELSE) {
64+
return false;
65+
}
66+
67+
if (parent instanceof ExpressionNode parentExpr) {
68+
String parentOp = parentExpr.logicalOperator();
69+
if (parentOp != null) {
70+
if (isComparison()) {
71+
return false;
72+
}
73+
74+
return !parentOp.equals(logicalOperator());
75+
}
76+
}
77+
return true;
78+
}
79+
80+
/**
81+
* @return {@code "AND"} or {@code "OR"} if this node is a logical connective, otherwise {@code null}.
82+
*/
83+
protected String logicalOperator() {
84+
return null;
85+
}
86+
87+
/**
88+
* @return true for comparison operators that bind tighter than {@code AND}/{@code OR} and therefore don't need
89+
* parentheses when nested directly under them.
90+
*/
91+
protected boolean isComparison() {
92+
return type == NodeType.EQUALITY || type == NodeType.LIKE;
93+
}
94+
6295
@Override
6396
public String toString() {
6497
return "{ExpressionNode}";

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,8 @@ public enum NodeType {
4040
INSERT_COLUMNS,
4141
INSERT_VALUES,
4242
ORDER_BY,
43-
SELECT
43+
SELECT,
44+
WHEN,
45+
THEN,
46+
ELSE
4447
}

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ public void appendChildrenSeparator(SQLAppendable buffer, int childInd) {
3939
buffer.appendTokenSeparator().append(op);
4040
}
4141

42+
@Override
43+
protected String logicalOperator() {
44+
return "AND".equals(op) || "OR".equals(op) ? op : null;
45+
}
46+
47+
@Override
48+
protected boolean isComparison() {
49+
return switch (op) {
50+
case "<", "<=", ">", ">=" -> true;
51+
default -> false;
52+
};
53+
}
54+
4255
@Override
4356
public Node copy() {
4457
return new OpExpressionNode(op);

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ThenNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
*/
2727
public class ThenNode extends Node {
2828

29+
public ThenNode() {
30+
super(NodeType.THEN);
31+
}
32+
2933
@Override
3034
public SQLAppendable append(SQLAppendable buffer) {
3135
return buffer.appendTokenSeparator().append("THEN");

cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhenNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
*/
2727
public class WhenNode extends Node {
2828

29+
public WhenNode() {
30+
super(NodeType.WHEN);
31+
}
32+
2933
@Override
3034
public SQLAppendable append(SQLAppendable buffer) {
3135
return buffer.appendTokenSeparator().append("WHEN");

cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void deleteWithQualifier() {
4646
.and(column("col3").eq(value(null)))
4747
).build();
4848
assertInstanceOf(DeleteNode.class, node);
49-
assertSQL("DELETE FROM test WHERE ((col1 = 1) AND (col2 = 'test')) AND (col3 IS NULL)", node);
49+
assertSQL("DELETE FROM test WHERE col1 = 1 AND col2 = 'test' AND col3 IS NULL", node);
5050
}
5151

5252
@Test

cayenne/src/test/java/org/apache/cayenne/access/sqlbuilder/SelectBuilderTest.java

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public void selectFromWhereComplex() {
9696
.where(column("a").eq(value(123)).and(column("c").lt(column("d"))));
9797
Node node = builder.build();
9898
assertInstanceOf(SelectNode.class, node);
99-
assertSQL("SELECT a FROM b WHERE (a = 123) AND (c < d)", node);
99+
assertSQL("SELECT a FROM b WHERE a = 123 AND c < d", node);
100100
}
101101

102102
@Test
@@ -112,13 +112,14 @@ public void validSelectCaseWhen() {
112112

113113
Node node = builder.build();
114114
assertInstanceOf(SelectNode.class, node);
115-
assertSQL("SELECT OrderID, Quantity, " +
116-
"CASE " +
117-
"WHEN ((Quantity > 30) AND (Quantity < 100)) THEN 'The quantity from 30 to 100' " +
118-
"WHEN (Quantity = 30) THEN 'The quantity is 30' " +
119-
"ELSE 'The quantity is under 30' " +
120-
"END QuantityText " +
121-
"FROM OrderDetails", node);
115+
assertSQL("""
116+
SELECT OrderID, Quantity, \
117+
CASE \
118+
WHEN Quantity > 30 AND Quantity < 100 THEN 'The quantity from 30 to 100' \
119+
WHEN Quantity = 30 THEN 'The quantity is 30' \
120+
ELSE 'The quantity is under 30' \
121+
END QuantityText \
122+
FROM OrderDetails""", node);
122123
}
123124

124125
@Test
@@ -179,16 +180,17 @@ public void complexQuery() {
179180
.orderBy(count(table("p").column("PAINTING_TITLE")).as("p_count").desc(), column("a_id").asc())
180181
.build();
181182
assertInstanceOf(SelectNode.class, node);
182-
assertSQL("SELECT DISTINCT" +
183-
" a.ARTIST_ID a_id, COUNT(p.PAINTING_TITLE) p_count" +
184-
" FROM ARTIST a" +
185-
" LEFT JOIN PAINTING p ON (a.ARTIST_ID = p.ARTIST_ID) AND (p.ESTIMATED_PRICE > 10)" +
186-
" WHERE (((a.ARTIST_NAME = 'Picasso')" +
187-
" AND EXISTS (SELECT * FROM GALLERY g WHERE g.GALLERY_ID = p.GALLERY_ID))" +
188-
" AND (1 = 1)) OR false" +
189-
" GROUP BY a.ARTIST_ID" +
190-
" HAVING NOT (COUNT(p.PAINTING_TITLE) > 3)" +
191-
" ORDER BY p_count DESC, a_id", node);
183+
assertSQL("""
184+
SELECT DISTINCT \
185+
a.ARTIST_ID a_id, COUNT(p.PAINTING_TITLE) p_count \
186+
FROM ARTIST a \
187+
LEFT JOIN PAINTING p ON a.ARTIST_ID = p.ARTIST_ID AND p.ESTIMATED_PRICE > 10 \
188+
WHERE (a.ARTIST_NAME = 'Picasso' \
189+
AND EXISTS (SELECT * FROM GALLERY g WHERE g.GALLERY_ID = p.GALLERY_ID) \
190+
AND 1 = 1) OR false \
191+
GROUP BY a.ARTIST_ID \
192+
HAVING NOT (COUNT(p.PAINTING_TITLE) > 3) \
193+
ORDER BY p_count DESC, a_id""", node);
192194
}
193195

194196
}

0 commit comments

Comments
 (0)