Skip to content

Commit 0c19ca6

Browse files
authored
Merge pull request #431 from domaframework/fix/sql-format-order-by-group
Fix SQL formatter issues with ORDER BY/GROUP BY clauses and improve block indentation handling
2 parents 725ec6c + c37d509 commit 0c19ca6

26 files changed

+462
-87
lines changed

src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class OracleFunctionToken {
3838
"length",
3939
"lengthb",
4040
"length4",
41+
"listagg",
4142
"cast",
4243
"numtodsinterval",
4344
"numtoyminterval",

src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,32 @@ public class SqlFunctionToken {
2525
static {
2626
TOKENS.addAll(
2727
Set.of(
28-
"coalesce",
29-
"row_number",
30-
"row_count",
31-
"sum",
3228
"avg",
29+
"btrim",
30+
"coalesce",
3331
"count",
34-
"max",
35-
"min",
32+
"current_date",
33+
"current_timestamp",
34+
"dense_rank",
35+
"filter",
3636
"log",
37-
"substring",
38-
"trim",
3937
"ltrim",
40-
"rtlim",
41-
"trim_array",
42-
"btrim",
43-
"replace",
44-
"regexp_replace",
38+
"max",
39+
"min",
40+
"mod",
41+
"now",
4542
"over",
46-
"rank",
4743
"percent_rank",
48-
"dense_rank",
49-
"current_date",
50-
"current_timestamp",
51-
"now",
52-
"mod"));
44+
"rank",
45+
"regexp_replace",
46+
"replace",
47+
"row_count",
48+
"row_number",
49+
"rtlim",
50+
"substring",
51+
"sum",
52+
"trim",
53+
"trim_array"));
5354
}
5455

5556
public static Set<String> getTokens() {

src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public class SqlKeywordTokenUtil {
107107
"then",
108108
"to",
109109
"truncate",
110+
"unbounded",
110111
"union",
111112
"unique",
112113
"update",
@@ -115,7 +116,8 @@ public class SqlKeywordTokenUtil {
115116
"view",
116117
"when",
117118
"where",
118-
"with"));
119+
"with",
120+
"within"));
119121
}
120122

121123
public static Set<String> getTokens() {

src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.domaframework.doma.intellij.extension.psi.isDomain
2828
import org.domaframework.doma.intellij.extension.psi.isEntity
2929
import org.domaframework.doma.intellij.formatter.block.SqlBlock
3030
import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock
31+
import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock
3132
import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock
3233
import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock
3334
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock
@@ -38,6 +39,7 @@ object TypeUtil {
3839
listOf(
3940
SqlSubGroupBlock::class,
4041
SqlCommaBlock::class,
42+
SqlColumnRawGroupBlock::class,
4143
SqlWithQuerySubGroupBlock::class,
4244
SqlCreateViewGroupBlock::class,
4345
)

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ open class SqlBlock(
8181
private fun calculateChildTextLength(child: SqlBlock): Int {
8282
val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock }
8383

84+
// True only on the first loop iteration when the current element is the first child.
85+
// If the subgroup is empty, return the length of “)”;
86+
// otherwise DEFAULT_TEXT_LENGTH_INCREMENT already adds a space, so “)” needs no extra length.
8487
return when {
8588
nonCommentChildren.isNotEmpty() -> child.getChildrenTextLen() + child.getNodeText().length
86-
isExcludedFromTextLength(child) -> 0
89+
isExcludedFromTextLength(child) -> if (childBlocks.firstOrNull() == child) child.getNodeText().length else 0
8790
else -> child.getNodeText().length + DEFAULT_TEXT_LENGTH_INCREMENT
8891
}
8992
}
@@ -371,24 +374,50 @@ open class SqlBlock(
371374
0
372375
}
373376

377+
var prevBlock: SqlBlock? = null
374378
return children
375379
.filter { it !is SqlDefaultCommentBlock && it !is SqlElConditionLoopCommentBlock }
376380
.sumOf { prev ->
377-
prev
378-
.getChildrenTextLen()
379-
.plus(
380-
if (prev.node.elementType == SqlTypes.DOT ||
381-
prev.node.elementType == SqlTypes.RIGHT_PAREN
382-
) {
383-
0
384-
} else {
385-
prev.getNodeText().length.plus(1)
386-
},
387-
)
381+
val sum =
382+
prev
383+
.getChildrenTextLen()
384+
.plus(
385+
if (prev.node.elementType == SqlTypes.DOT ||
386+
prev.node.elementType == SqlTypes.RIGHT_PAREN
387+
) {
388+
0
389+
} else if (prev.isOperationSymbol() && prevBlock?.isOperationSymbol() == true) {
390+
// When operators appear consecutively, the first symbol includes the text length for the last space.
391+
// Subsequent symbols add only their own symbol length.
392+
prev.getNodeText().length
393+
} else {
394+
prev.getNodeText().length.plus(1)
395+
},
396+
)
397+
prevBlock = prev
398+
return@sumOf sum
388399
}.plus(parent.indent.groupIndentLen)
389400
.plus(directiveParentIndent)
390401
}
391402

403+
fun isOperationSymbol(): Boolean =
404+
node.elementType in
405+
listOf(
406+
SqlTypes.PLUS,
407+
SqlTypes.MINUS,
408+
SqlTypes.ASTERISK,
409+
SqlTypes.AT_SIGN,
410+
SqlTypes.SLASH,
411+
SqlTypes.HASH,
412+
SqlTypes.LE,
413+
SqlTypes.LT,
414+
SqlTypes.EL_EQ,
415+
SqlTypes.EL_NE,
416+
SqlTypes.GE,
417+
SqlTypes.GT,
418+
SqlTypes.OTHER,
419+
)
420+
392421
/**
393422
* Returns the child indentation for the block.
394423
*

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,10 @@ open class SqlFileBlock(
495495
}
496496

497497
if (childBlock1 is SqlSubGroupBlock) {
498-
if (childBlock2 is SqlSubGroupBlock) {
498+
if (childBlock2 is SqlSubGroupBlock || childBlock1 is SqlFunctionParamBlock) {
499499
return SqlCustomSpacingBuilder.nonSpacing
500500
}
501+
501502
if (childBlock1 is SqlInsertValueGroupBlock ||
502503
childBlock1 is SqlUpdateValueGroupBlock
503504
) {
@@ -586,7 +587,15 @@ open class SqlFileBlock(
586587
}
587588
}
588589

589-
is SqlDataTypeParamBlock, is SqlFunctionParamBlock -> return SqlCustomSpacingBuilder.nonSpacing
590+
is SqlFunctionParamBlock -> {
591+
return if (childBlock1?.node?.elementType in listOf(SqlTypes.FUNCTION_NAME, SqlTypes.WORD)) {
592+
SqlCustomSpacingBuilder.nonSpacing
593+
} else {
594+
SqlCustomSpacingBuilder.normalSpacing
595+
}
596+
}
597+
598+
is SqlDataTypeParamBlock -> return SqlCustomSpacingBuilder.nonSpacing
590599
}
591600
}
592601

@@ -647,7 +656,8 @@ open class SqlFileBlock(
647656
childBlock1 is SqlOtherBlock && childBlock2 is SqlElSymbolBlock ||
648657
childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElAtSignBlock ||
649658
childBlock1 is SqlOtherBlock && childBlock2 is SqlOtherBlock ||
650-
childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock
659+
childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock ||
660+
childBlock1?.isOperationSymbol() == true && childBlock2.isOperationSymbol()
651661

652662
override fun isLeaf(): Boolean = false
653663

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ open class SqlRightPatternBlock(
210210
val firstChild =
211211
parent.getChildBlocksDropLast().firstOrNull()
212212
if (firstChild is SqlKeywordGroupBlock) {
213+
// For subgroups other than function parameters, if the first element is a keyword group, add a line break before the closing parenthesis except at the top level.
214+
// For subgroups created by WITHIN GROUP (), do not add a line break.
213215
val lineBreak =
214216
firstChild.indent.indentLevel != IndentType.TOP &&
215217
!isExpectedClassType(NOT_NEW_LINE_EXPECTED_TYPES, parent)

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.S
2626
import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertColumnGroupBlock
2727
import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertValueGroupBlock
2828
import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock
29+
import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlSecondKeywordBlock
2930
import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock
3031
import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateColumnGroupBlock
3132
import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateSetGroupBlock
@@ -143,7 +144,21 @@ open class SqlCommaBlock(
143144
val parentIndent = firstChild?.indent ?: parent.indent
144145
parentIndent.groupIndentLen.plus(1)
145146
}
146-
else -> parent.indent.groupIndentLen.plus(1)
147+
else -> {
148+
// No indent after ORDER BY within function parameters
149+
val grand = parent.parentBlock
150+
val conditionParent =
151+
if (grand is SqlElConditionLoopCommentBlock) {
152+
grand.parentBlock
153+
} else {
154+
grand
155+
}
156+
if (parent is SqlSecondKeywordBlock && conditionParent is SqlFunctionParamBlock) {
157+
0
158+
} else {
159+
parent.indent.groupIndentLen.plus(1)
160+
}
161+
}
147162
}
148163
}
149164
}
@@ -153,7 +168,21 @@ open class SqlCommaBlock(
153168
override fun createGroupIndentLen(): Int = indent.indentLen.plus(1)
154169

155170
override fun isSaveSpace(lastGroup: SqlBlock?): Boolean {
156-
if (parentBlock is SqlConditionalExpressionGroupBlock) return false
157-
return TypeUtil.isExpectedClassType(EXPECTED_TYPES, parentBlock)
171+
parentBlock?.let { parent ->
172+
if (parent is SqlConditionalExpressionGroupBlock) return false
173+
// Don't allow line breaks after ORDER BY within function parameters
174+
val grand = parent.parentBlock
175+
val conditionParent =
176+
if (grand is SqlElConditionLoopCommentBlock) {
177+
grand.parentBlock
178+
} else {
179+
grand
180+
}
181+
if (conditionParent is SqlFunctionParamBlock) {
182+
return false
183+
}
184+
return TypeUtil.isExpectedClassType(EXPECTED_TYPES, parent)
185+
}
186+
return false
158187
}
159188
}

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ class SqlElConditionLoopCommentBlock(
7474
listOf(
7575
SqlSubGroupBlock::class,
7676
SqlColumnRawGroupBlock::class,
77-
SqlElConditionLoopCommentBlock::class,
7877
)
7978
}
8079

@@ -173,10 +172,14 @@ class SqlElConditionLoopCommentBlock(
173172
if (conditionType.isEnd() || conditionType.isElse()) {
174173
return true
175174
}
175+
if (lastGroup is SqlElConditionLoopCommentBlock) {
176+
return true
177+
}
178+
val firstChild = lastGroup?.childBlocks?.firstOrNull()
176179
if (TypeUtil.isExpectedClassType(LINE_BREAK_PARENT_TYPES, lastGroup)) {
177-
return lastGroup?.childBlocks?.dropLast(1)?.isNotEmpty() == true || lastGroup is SqlElConditionLoopCommentBlock
180+
return firstChild != null && firstChild != this
178181
}
179-
return lastGroup?.childBlocks?.firstOrNull() != this
182+
return firstChild == null || firstChild != this
180183
}
181184

182185
/**

src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlKeywordGroupBlock.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoo
2424
import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock
2525
import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlSelectQueryGroupBlock
2626
import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock
27+
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock
2728
import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock
2829
import org.domaframework.doma.intellij.formatter.util.IndentType
2930
import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext
@@ -178,8 +179,16 @@ open class SqlKeywordGroupBlock(
178179
override fun createGroupIndentLen(): Int = indent.indentLen.plus(topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }.minus(1))
179180

180181
override fun isSaveSpace(lastGroup: SqlBlock?): Boolean {
181-
val prevWord = prevBlocks.lastOrNull()
182+
val conditionLastGroup =
183+
if (parentBlock is SqlElConditionLoopCommentBlock) {
184+
parentBlock?.parentBlock
185+
} else {
186+
lastGroup
187+
}
188+
val prevWord = prevBlocks.findLast { it is SqlKeywordBlock || it is SqlKeywordGroupBlock }
182189
return !SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), prevWord?.getNodeText() ?: "") &&
183-
!SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), lastGroup?.getNodeText() ?: "")
190+
!SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), conditionLastGroup?.getNodeText() ?: "") &&
191+
!SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), lastGroup?.getNodeText() ?: "") &&
192+
lastGroup !is SqlFunctionParamBlock
184193
}
185194
}

0 commit comments

Comments
 (0)