diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java index f81408fd..aa1cbff3 100644 --- a/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java +++ b/src/main/java/org/domaframework/doma/intellij/tokens/OracleFunctionToken.java @@ -38,6 +38,7 @@ public class OracleFunctionToken { "length", "lengthb", "length4", + "listagg", "cast", "numtodsinterval", "numtoyminterval", diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java b/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java index ed434b02..9f96872d 100644 --- a/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java +++ b/src/main/java/org/domaframework/doma/intellij/tokens/SqlFunctionToken.java @@ -25,31 +25,32 @@ public class SqlFunctionToken { static { TOKENS.addAll( Set.of( - "coalesce", - "row_number", - "row_count", - "sum", "avg", + "btrim", + "coalesce", "count", - "max", - "min", + "current_date", + "current_timestamp", + "dense_rank", + "filter", "log", - "substring", - "trim", "ltrim", - "rtlim", - "trim_array", - "btrim", - "replace", - "regexp_replace", + "max", + "min", + "mod", + "now", "over", - "rank", "percent_rank", - "dense_rank", - "current_date", - "current_timestamp", - "now", - "mod")); + "rank", + "regexp_replace", + "replace", + "row_count", + "row_number", + "rtlim", + "substring", + "sum", + "trim", + "trim_array")); } public static Set getTokens() { diff --git a/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java b/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java index a005c18e..fe592e00 100644 --- a/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java +++ b/src/main/java/org/domaframework/doma/intellij/tokens/SqlKeywordTokenUtil.java @@ -107,6 +107,7 @@ public class SqlKeywordTokenUtil { "then", "to", "truncate", + "unbounded", "union", "unique", "update", @@ -115,7 +116,8 @@ public class SqlKeywordTokenUtil { "view", "when", "where", - "with")); + "with", + "within")); } public static Set getTokens() { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt index 691df5b8..7a82a7a6 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/TypeUtil.kt @@ -28,6 +28,7 @@ import org.domaframework.doma.intellij.extension.psi.isDomain import org.domaframework.doma.intellij.extension.psi.isEntity import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comma.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock @@ -38,6 +39,7 @@ object TypeUtil { listOf( SqlSubGroupBlock::class, SqlCommaBlock::class, + SqlColumnRawGroupBlock::class, SqlWithQuerySubGroupBlock::class, SqlCreateViewGroupBlock::class, ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt index 8baa2450..20cbfca0 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlBlock.kt @@ -81,9 +81,12 @@ open class SqlBlock( private fun calculateChildTextLength(child: SqlBlock): Int { val nonCommentChildren = child.childBlocks.filterNot { it is SqlDefaultCommentBlock } + // True only on the first loop iteration when the current element is the first child. + // If the subgroup is empty, return the length of “)”; + // otherwise DEFAULT_TEXT_LENGTH_INCREMENT already adds a space, so “)” needs no extra length. return when { nonCommentChildren.isNotEmpty() -> child.getChildrenTextLen() + child.getNodeText().length - isExcludedFromTextLength(child) -> 0 + isExcludedFromTextLength(child) -> if (childBlocks.firstOrNull() == child) child.getNodeText().length else 0 else -> child.getNodeText().length + DEFAULT_TEXT_LENGTH_INCREMENT } } @@ -371,24 +374,50 @@ open class SqlBlock( 0 } + var prevBlock: SqlBlock? = null return children .filter { it !is SqlDefaultCommentBlock && it !is SqlElConditionLoopCommentBlock } .sumOf { prev -> - prev - .getChildrenTextLen() - .plus( - if (prev.node.elementType == SqlTypes.DOT || - prev.node.elementType == SqlTypes.RIGHT_PAREN - ) { - 0 - } else { - prev.getNodeText().length.plus(1) - }, - ) + val sum = + prev + .getChildrenTextLen() + .plus( + if (prev.node.elementType == SqlTypes.DOT || + prev.node.elementType == SqlTypes.RIGHT_PAREN + ) { + 0 + } else if (prev.isOperationSymbol() && prevBlock?.isOperationSymbol() == true) { + // When operators appear consecutively, the first symbol includes the text length for the last space. + // Subsequent symbols add only their own symbol length. + prev.getNodeText().length + } else { + prev.getNodeText().length.plus(1) + }, + ) + prevBlock = prev + return@sumOf sum }.plus(parent.indent.groupIndentLen) .plus(directiveParentIndent) } + fun isOperationSymbol(): Boolean = + node.elementType in + listOf( + SqlTypes.PLUS, + SqlTypes.MINUS, + SqlTypes.ASTERISK, + SqlTypes.AT_SIGN, + SqlTypes.SLASH, + SqlTypes.HASH, + SqlTypes.LE, + SqlTypes.LT, + SqlTypes.EL_EQ, + SqlTypes.EL_NE, + SqlTypes.GE, + SqlTypes.GT, + SqlTypes.OTHER, + ) + /** * Returns the child indentation for the block. * diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt index 2ba0ad40..385b9590 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlFileBlock.kt @@ -495,9 +495,10 @@ open class SqlFileBlock( } if (childBlock1 is SqlSubGroupBlock) { - if (childBlock2 is SqlSubGroupBlock) { + if (childBlock2 is SqlSubGroupBlock || childBlock1 is SqlFunctionParamBlock) { return SqlCustomSpacingBuilder.nonSpacing } + if (childBlock1 is SqlInsertValueGroupBlock || childBlock1 is SqlUpdateValueGroupBlock ) { @@ -586,7 +587,15 @@ open class SqlFileBlock( } } - is SqlDataTypeParamBlock, is SqlFunctionParamBlock -> return SqlCustomSpacingBuilder.nonSpacing + is SqlFunctionParamBlock -> { + return if (childBlock1?.node?.elementType in listOf(SqlTypes.FUNCTION_NAME, SqlTypes.WORD)) { + SqlCustomSpacingBuilder.nonSpacing + } else { + SqlCustomSpacingBuilder.normalSpacing + } + } + + is SqlDataTypeParamBlock -> return SqlCustomSpacingBuilder.nonSpacing } } @@ -647,7 +656,8 @@ open class SqlFileBlock( childBlock1 is SqlOtherBlock && childBlock2 is SqlElSymbolBlock || childBlock1 is SqlElSymbolBlock && childBlock2 is SqlElAtSignBlock || childBlock1 is SqlOtherBlock && childBlock2 is SqlOtherBlock || - childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock + childBlock1 is SqlElSymbolBlock && childBlock2 is SqlOtherBlock || + childBlock1?.isOperationSymbol() == true && childBlock2.isOperationSymbol() override fun isLeaf(): Boolean = false diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt index 2b2a3b65..79eee5cd 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlRightPatternBlock.kt @@ -210,6 +210,8 @@ open class SqlRightPatternBlock( val firstChild = parent.getChildBlocksDropLast().firstOrNull() if (firstChild is SqlKeywordGroupBlock) { + // 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. + // For subgroups created by WITHIN GROUP (), do not add a line break. val lineBreak = firstChild.indent.indentLevel != IndentType.TOP && !isExpectedClassType(NOT_NEW_LINE_EXPECTED_TYPES, parent) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt index 79129f91..0357f9fd 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comma/SqlCommaBlock.kt @@ -26,6 +26,7 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.S import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertColumnGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertValueGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlSecondKeywordBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateColumnGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateSetGroupBlock @@ -143,7 +144,21 @@ open class SqlCommaBlock( val parentIndent = firstChild?.indent ?: parent.indent parentIndent.groupIndentLen.plus(1) } - else -> parent.indent.groupIndentLen.plus(1) + else -> { + // No indent after ORDER BY within function parameters + val grand = parent.parentBlock + val conditionParent = + if (grand is SqlElConditionLoopCommentBlock) { + grand.parentBlock + } else { + grand + } + if (parent is SqlSecondKeywordBlock && conditionParent is SqlFunctionParamBlock) { + 0 + } else { + parent.indent.groupIndentLen.plus(1) + } + } } } } @@ -153,7 +168,21 @@ open class SqlCommaBlock( override fun createGroupIndentLen(): Int = indent.indentLen.plus(1) override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { - if (parentBlock is SqlConditionalExpressionGroupBlock) return false - return TypeUtil.isExpectedClassType(EXPECTED_TYPES, parentBlock) + parentBlock?.let { parent -> + if (parent is SqlConditionalExpressionGroupBlock) return false + // Don't allow line breaks after ORDER BY within function parameters + val grand = parent.parentBlock + val conditionParent = + if (grand is SqlElConditionLoopCommentBlock) { + grand.parentBlock + } else { + grand + } + if (conditionParent is SqlFunctionParamBlock) { + return false + } + return TypeUtil.isExpectedClassType(EXPECTED_TYPES, parent) + } + return false } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt index 17c206b7..4da449eb 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/comment/SqlElConditionLoopCommentBlock.kt @@ -74,7 +74,6 @@ class SqlElConditionLoopCommentBlock( listOf( SqlSubGroupBlock::class, SqlColumnRawGroupBlock::class, - SqlElConditionLoopCommentBlock::class, ) } @@ -173,10 +172,14 @@ class SqlElConditionLoopCommentBlock( if (conditionType.isEnd() || conditionType.isElse()) { return true } + if (lastGroup is SqlElConditionLoopCommentBlock) { + return true + } + val firstChild = lastGroup?.childBlocks?.firstOrNull() if (TypeUtil.isExpectedClassType(LINE_BREAK_PARENT_TYPES, lastGroup)) { - return lastGroup?.childBlocks?.dropLast(1)?.isNotEmpty() == true || lastGroup is SqlElConditionLoopCommentBlock + return firstChild != null && firstChild != this } - return lastGroup?.childBlocks?.firstOrNull() != this + return firstChild == null || firstChild != this } /** diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlKeywordGroupBlock.kt index efc413c4..375a3caa 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlKeywordGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlKeywordGroupBlock.kt @@ -24,6 +24,7 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoo import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlSelectQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -178,8 +179,16 @@ open class SqlKeywordGroupBlock( override fun createGroupIndentLen(): Int = indent.indentLen.plus(topKeywordBlocks.sumOf { it.getNodeText().length.plus(1) }.minus(1)) override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { - val prevWord = prevBlocks.lastOrNull() + val conditionLastGroup = + if (parentBlock is SqlElConditionLoopCommentBlock) { + parentBlock?.parentBlock + } else { + lastGroup + } + val prevWord = prevBlocks.findLast { it is SqlKeywordBlock || it is SqlKeywordGroupBlock } return !SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), prevWord?.getNodeText() ?: "") && - !SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), lastGroup?.getNodeText() ?: "") + !SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), conditionLastGroup?.getNodeText() ?: "") && + !SqlKeywordUtil.isSetLineKeyword(this.getNodeText(), lastGroup?.getNodeText() ?: "") && + lastGroup !is SqlFunctionParamBlock } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt index 018f1308..1780ed64 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlSecondKeywordBlock.kt @@ -16,15 +16,16 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.second import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.common.util.TypeUtil import org.domaframework.doma.intellij.formatter.block.SqlBlock -import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock +import org.domaframework.doma.intellij.formatter.block.SqlRightPatternBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext -import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil open class SqlSecondKeywordBlock( node: ASTNode, @@ -44,7 +45,13 @@ open class SqlSecondKeywordBlock( return if (parent.indent.indentLevel == IndentType.FILE) { offset } else if (parent is SqlSubGroupBlock) { - groupLen.plus(1) + val space = + if (TypeUtil.isExpectedClassType(SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, parent)) { + 0 + } else { + 1 + } + groupLen.plus(space) } else if (parent is SqlElConditionLoopCommentBlock) { groupLen } else { @@ -55,12 +62,20 @@ open class SqlSecondKeywordBlock( } override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { - lastGroup?.let { last -> - val prevKeyword = last.childBlocks.findLast { it is SqlKeywordBlock } - prevKeyword?.let { prev -> - return !SqlKeywordUtil.isSetLineKeyword(getNodeText(), prev.getNodeText()) && last !is SqlFunctionParamBlock + parentBlock?.let { parent -> + val conditionParent = + if (parent is SqlElConditionLoopCommentBlock) { + parent.parentBlock + } else { + parent + } + if (conditionParent is SqlFunctionParamBlock) { + val firstKeywordParam = + conditionParent.childBlocks.firstOrNull { it is SqlNewGroupBlock } + return firstKeywordParam != null && firstKeywordParam != this + } else { + return super.isSaveSpace(lastGroup) } - return !SqlKeywordUtil.isSetLineKeyword(getNodeText(), last.getNodeText()) } return true } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt index 69497384..87698059 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubGroupBlock.kt @@ -128,7 +128,14 @@ abstract class SqlSubGroupBlock( return false } } - return TypeUtil.isExpectedClassType(NEW_LINE_EXPECTED_TYPES, lastBlock.parentBlock) + val lastParent = lastBlock.parentBlock + val expectedParent = + if (lastParent is SqlElConditionLoopCommentBlock) { + lastParent.parentBlock?.parentBlock + } else { + lastBlock.parentBlock + } + return TypeUtil.isExpectedClassType(NEW_LINE_EXPECTED_TYPES, expectedParent) } return false } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt index 19ec3387..7cc61e03 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/subgroup/SqlSubQueryGroupBlock.kt @@ -66,14 +66,14 @@ open class SqlSubQueryGroupBlock( is SqlJoinQueriesGroupBlock -> return parent.indent.indentLen is SqlJoinGroupBlock -> return parent.indent.groupIndentLen.plus(1) else -> { - val children = prevChildren?.filter { shouldIncludeChildBlock(it, parent) } + val children = prevChildren?.filter { shouldIncludeChildBlock(it, parent) }?.dropLast(1) // Retrieve the list of child blocks excluding the conditional directive that appears immediately before this block, // as it is already included as a child block. val sumChildren = if (children?.firstOrNull() is SqlElConditionLoopCommentBlock) { - children.drop(1).dropLast(1) + children.drop(1) } else { - children?.dropLast(1) ?: emptyList() + children ?: emptyList() } return sumChildren .sumOf { prev -> diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt index 036b309f..a73fae12 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/word/SqlFunctionGroupBlock.kt @@ -22,7 +22,8 @@ import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoo import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext -import org.domaframework.doma.intellij.psi.SqlTypes +import kotlin.collections.emptyList +import kotlin.collections.toList class SqlFunctionGroupBlock( node: ASTNode, @@ -38,7 +39,7 @@ class SqlFunctionGroupBlock( indent.groupIndentLen = createGroupIndentLen() } - override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen ?: 0 + override fun createBlockIndentLen(): Int = parentBlock?.indent?.groupIndentLen?.plus(1) ?: 0 override fun createGroupIndentLen(): Int { val baseIndent = diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt index 8d4591c0..6f3e9bb9 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/builder/SqlBlockRelationBuilder.kt @@ -36,7 +36,9 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlTopQ import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock +import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock import org.domaframework.doma.intellij.formatter.block.word.SqlFunctionGroupBlock import org.domaframework.doma.intellij.formatter.handler.UpdateClauseHandler import org.domaframework.doma.intellij.formatter.util.IndentType @@ -234,20 +236,71 @@ class SqlBlockRelationBuilder( } else -> { setParentGroups(context) { history -> - history.lastOrNull { it.indent.indentLevel < childBlock.indent.indentLevel } + getLastGroupKeywordText(history, childBlock) ?: history.lastOrNull() } } } } + /** + * Searches for the most recent group block with a lower indent level than the current block + * and returns it as a candidate for the parent block. + * + * @note + * If the most recent group block is a comma, it checks its parent and grandparent blocks + * to determine the appropriate parent block. + * + * @example + * OVER(ORDER BY e.id, e.manager_id, created_at + * ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) + */ + private fun getLastGroupKeywordText( + history: MutableList, + childBlock: SqlBlock, + ): SqlBlock? { + val lastGroupBlock = + history.lastOrNull { + it.indent.indentLevel < childBlock.indent.indentLevel || + // Add [SqlColumnRawGroupBlock] as a parent block candidate + // to support cases like WITHIN GROUP used in column lines. + TypeUtil.isTopLevelExpectedType(it) + } + if (lastGroupBlock == null) return lastGroupBlock + + if (lastGroupBlock.indent.indentLevel > childBlock.indent.indentLevel && lastGroupBlock !is SqlSubGroupBlock) { + val lastParent = lastGroupBlock.parentBlock + val lastGroupParentLevel = lastParent?.indent?.indentLevel ?: IndentType.NONE + val lastKeyword = + lastGroupBlock.childBlocks + .lastOrNull { + it is SqlKeywordBlock || it is SqlKeywordGroupBlock + }?.getNodeText() ?: "" + val setKeyword = SqlKeywordUtil.isSetLineKeyword(childBlock.getNodeText(), lastKeyword) + if (lastGroupParentLevel < childBlock.indent.indentLevel) { + return if (setKeyword) { + lastGroupBlock + } else { + lastParent + } + } + return lastParent?.parentBlock + } + return lastGroupBlock + } + private fun handleSameLevelKeyword( lastGroupBlock: SqlBlock, childBlock: SqlKeywordGroupBlock, context: SetParentContext, ) { val prevKeyword = lastGroupBlock.childBlocks.findLast { it is SqlKeywordBlock } - if (prevKeyword != null && SqlKeywordUtil.Companion.isSetLineKeyword(childBlock.getNodeText(), prevKeyword.getNodeText())) { - updateGroupBlockLastGroupParentAddGroup(lastGroupBlock, childBlock) + if (prevKeyword != null && + SqlKeywordUtil.isSetLineKeyword( + childBlock.getNodeText(), + prevKeyword.getNodeText(), + ) + ) { + updateGroupBlockLastGroupParentAddGroup(prevKeyword, childBlock) return } @@ -462,7 +515,8 @@ class SqlBlockRelationBuilder( return@setParentGroups history[paramIndex] } - if (blockBuilder.getGroupTopNodeIndexHistory()[paramIndex] is SqlWithQuerySubGroupBlock) { + val parentSubGroup = blockBuilder.getGroupTopNodeIndexHistory()[paramIndex] + if (parentSubGroup is SqlWithQuerySubGroupBlock) { val withCommonBlockIndex = blockBuilder.getGroupTopNodeIndex { block -> block is SqlWithCommonTableGroupBlock @@ -470,9 +524,27 @@ class SqlBlockRelationBuilder( if (withCommonBlockIndex >= 0) { blockBuilder.clearSubListGroupTopNodeIndexHistory(withCommonBlockIndex) } - } else { - blockBuilder.clearSubListGroupTopNodeIndexHistory(paramIndex) + return + } + if (parentSubGroup is SqlFunctionParamBlock) { + // If the parent is a function parameter group, remove up to the parent function name and keyword group. + val parent = blockBuilder.getGroupTopNodeIndexHistory()[paramIndex].parentBlock + val searchFunctionName = + if (parent is SqlElConditionLoopCommentBlock) { + parent.parentBlock + } else { + parent + } + val functionParent = + blockBuilder.getGroupTopNodeIndex { + it == searchFunctionName + } + if (functionParent >= 0) { + blockBuilder.clearSubListGroupTopNodeIndexHistory(functionParent) + return + } } + blockBuilder.clearSubListGroupTopNodeIndexHistory(paramIndex) } } @@ -481,7 +553,7 @@ class SqlBlockRelationBuilder( childBlock: SqlSubGroupBlock, ) { val prevBlock = lastGroupBlock.childBlocks.lastOrNull() - if (prevBlock is SqlFunctionGroupBlock) { + if (prevBlock is SqlFunctionGroupBlock || prevBlock is SqlAliasBlock) { setParentGroups( SetParentContext( childBlock, @@ -503,31 +575,64 @@ class SqlBlockRelationBuilder( getParentGroup: (MutableList) -> SqlBlock?, ) { val parentGroup = - getParentGroup(context.blockBuilder.getGroupTopNodeIndexHistory() as MutableList) - + getParentGroup( + context.blockBuilder.getGroupTopNodeIndexHistory() as MutableList, + ) val targetChildBlock = context.childBlock - if (targetChildBlock is SqlDefaultCommentBlock) return + if (shouldSkipParentSetting(targetChildBlock)) return + + assignParentGroup(targetChildBlock, parentGroup) + registerAsNewGroupIfNeeded(targetChildBlock, context.blockBuilder) + updateBlockIndents(targetChildBlock, context.blockBuilder) + } - // The parent block for SqlElConditionLoopCommentBlock will be set later - if (targetChildBlock is SqlElConditionLoopCommentBlock && targetChildBlock.conditionType.isStartDirective()) { - targetChildBlock.tempParentBlock = parentGroup - if ((parentGroup is SqlElConditionLoopCommentBlock || parentGroup is SqlSubGroupBlock) && parentGroup.parentBlock != null) { - targetChildBlock.setParentGroupBlock(parentGroup) + private fun shouldSkipParentSetting(block: SqlBlock) = block is SqlDefaultCommentBlock + + private fun assignParentGroup( + targetChildBlock: SqlBlock, + parentGroup: SqlBlock?, + ) { + when { + isStartDirectiveConditionLoop(targetChildBlock) -> { + val conditionLoop = targetChildBlock as SqlElConditionLoopCommentBlock + conditionLoop.tempParentBlock = parentGroup + if (shouldSetParentImmediately(parentGroup)) { + conditionLoop.setParentGroupBlock(parentGroup) + } } - } else { - targetChildBlock.setParentGroupBlock(parentGroup) + else -> targetChildBlock.setParentGroupBlock(parentGroup) } + } - if (isNewGroup(targetChildBlock, context.blockBuilder) || - TypeUtil.isExpectedClassType(NEW_GROUP_EXPECTED_TYPES, targetChildBlock) - ) { - context.blockBuilder.addGroupTopNodeIndexHistory(targetChildBlock) + private fun isStartDirectiveConditionLoop(block: SqlBlock) = + block is SqlElConditionLoopCommentBlock && block.conditionType.isStartDirective() + + private fun shouldSetParentImmediately(parentGroup: SqlBlock?) = + (parentGroup is SqlElConditionLoopCommentBlock || parentGroup is SqlSubGroupBlock) && + parentGroup.parentBlock != null + + private fun registerAsNewGroupIfNeeded( + targetChildBlock: SqlBlock, + blockBuilder: SqlBlockBuilder, + ) { + if (shouldRegisterAsNewGroup(targetChildBlock, blockBuilder)) { + blockBuilder.addGroupTopNodeIndexHistory(targetChildBlock) } + } - context.blockBuilder.updateCommentBlockIndent(targetChildBlock) - // Set parent-child relationship and indent for preceding comment at beginning of block group - context.blockBuilder.updateConditionLoopBlockIndent(targetChildBlock) + private fun shouldRegisterAsNewGroup( + block: SqlBlock, + blockBuilder: SqlBlockBuilder, + ) = isNewGroup(block, blockBuilder) || + TypeUtil.isExpectedClassType(NEW_GROUP_EXPECTED_TYPES, block) + + private fun updateBlockIndents( + targetChildBlock: SqlBlock, + blockBuilder: SqlBlockBuilder, + ) { + blockBuilder.updateCommentBlockIndent(targetChildBlock) + blockBuilder.updateConditionLoopBlockIndent(targetChildBlock) } /** @@ -562,7 +667,7 @@ class SqlBlockRelationBuilder( } val isSetLineGroup = - SqlKeywordUtil.Companion.isSetLineKeyword( + SqlKeywordUtil.isSetLineKeyword( childBlock.getNodeText(), lastKeywordText, ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt index 199fd1bc..9188d0ee 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/handler/NotQueryGroupHandler.kt @@ -34,6 +34,7 @@ import org.domaframework.doma.intellij.formatter.block.word.SqlAliasBlock import org.domaframework.doma.intellij.formatter.block.word.SqlTableBlock import org.domaframework.doma.intellij.formatter.block.word.SqlWordBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.formatter.util.SqlKeywordUtil object NotQueryGroupHandler { private const val RETURNING_KEYWORD = "returning" @@ -53,6 +54,7 @@ object NotQueryGroupHandler { hasFunctionOrAliasContext(lastGroup) -> createFunctionOrValueBlock(lastGroup, child, sqlBlockFormattingCtx) lastGroup is SqlConflictClauseBlock -> SqlConflictExpressionSubGroupBlock(child, sqlBlockFormattingCtx) hasValuesContext(lastGroup) -> SqlValuesParamGroupBlock(child, sqlBlockFormattingCtx) + hasParameterKeyword(lastGroup) -> SqlFunctionParamBlock(child, sqlBlockFormattingCtx) else -> null } @@ -106,6 +108,8 @@ object NotQueryGroupHandler { return false } + private fun hasParameterKeyword(lastGroup: SqlBlock?): Boolean = SqlKeywordUtil.hasFilterParam(lastGroup?.getNodeText() ?: "") + /** * Creates either a function parameter block or values parameter block based on the previous child type. */ diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt index 73a97043..bc1fcf65 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/processor/SqlFormatPreProcessor.kt @@ -224,7 +224,9 @@ class SqlFormatPreProcessor : PreFormatProcessor { private fun getKeywordNewText(element: PsiElement): String { val keywordText = element.text.lowercase() val upperText = getUpperText(element) - return if (SqlKeywordUtil.getIndentType(keywordText).isNewLineGroup()) { + return if (SqlKeywordUtil.getIndentType(keywordText).isNewLineGroup() || + element.prevSibling.elementType == SqlTypes.BLOCK_COMMENT + ) { val prevElement = element.prevSibling getNewLineString(prevElement, upperText) } else { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index 4b002d4b..b03736f8 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt @@ -455,6 +455,11 @@ class SqlBlockGenerator( child, sqlBlockFormattingCtx, ) + + lastGroup is SqlInsertQueryGroupBlock -> return SqlTableBlock( + child, + sqlBlockFormattingCtx, + ) } } @@ -478,6 +483,8 @@ class SqlBlockGenerator( if (lastGroup is SqlFromGroupBlock || lastGroup?.parentBlock is SqlFromGroupBlock) { return SqlAliasBlock(child, sqlBlockFormattingCtx) } + val functionNameBlock = getFunctionName(child, sqlBlockFormattingCtx) + if (functionNameBlock != null) return functionNameBlock return SqlWordBlock(child, sqlBlockFormattingCtx) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt index a6814e14..229cdcb6 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlKeywordUtil.kt @@ -131,6 +131,7 @@ class SqlKeywordUtil { "group", "having", "order", + "rows", ) fun isSelectSecondOptionKeyword(keyword: String): Boolean = SELECT_SECOND_OPTION_KEYWORD.contains(keyword.lowercase()) @@ -301,6 +302,11 @@ class SqlKeywordUtil { fun isExistsKeyword(keyword: String): Boolean = EXISTS_KEYWORDS.contains(keyword.lowercase()) + private val HAS_FILTER_PARAM = + setOf("group", "in", "over", "into", "values", "filter", "references", "using") + + fun hasFilterParam(keyword: String): Boolean = HAS_FILTER_PARAM.contains(keyword.lowercase()) + private val SET_LINE_KEYWORDS = mapOf( "into" to setOf("insert"), @@ -313,8 +319,9 @@ class SqlKeywordUtil { "join" to setOf("outer", "inner", "left", "right"), "outer" to setOf("left", "right"), "inner" to setOf("left", "right"), - "by" to setOf("group", "order", "first"), - "and" to setOf("between"), + "group" to setOf("within"), + "by" to setOf("group", "order", "first", "partition"), + "and" to setOf("between", "preceding"), "if" to setOf("table", "create"), "exists" to setOf("if", "where"), "conflict" to setOf("on"), @@ -322,7 +329,7 @@ class SqlKeywordUtil { "constraint" to setOf("on"), "update" to setOf("do"), "set" to setOf("by", "cycle"), - "order" to setOf("partition", "by"), + "order" to setOf("partition"), "select" to setOf("if", "exists"), ) diff --git a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt index ce035893..c57447e1 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -270,6 +270,14 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("FunctionNameColumn.sql", "FunctionNameColumn$formatDataPrefix.sql") } + fun testOrderByGroupFormatter() { + formatSqlFile("OrderByGroup.sql", "OrderByGroup$formatDataPrefix.sql") + } + + fun testOrderByGroupWithConditionDirectiveFormatter() { + formatSqlFile("OrderByGroupWithConditionDirective.sql", "OrderByGroupWithConditionDirective$formatDataPrefix.sql") + } + private fun formatSqlFile( beforeFile: String, afterFile: String, diff --git a/src/test/testData/sql/formatter/DeleteWithSubQuery.sql b/src/test/testData/sql/formatter/DeleteWithSubQuery.sql index b79297f0..ea30fa3f 100644 --- a/src/test/testData/sql/formatter/DeleteWithSubQuery.sql +++ b/src/test/testData/sql/formatter/DeleteWithSubQuery.sql @@ -8,4 +8,7 @@ DELETE FROM user_session s FROM user u WHERE u.id = /* id */1 AND u.session_id = u.id -AND u.time_stamp < /* current */'2099-12-31 00:00:00') \ No newline at end of file +AND u.time_stamp < /* current */'2099-12-31 00:00:00') + AND s.user_number IN (SELECT u.number + FROM user u + WHERE u.status = /* status */'active' ) \ No newline at end of file diff --git a/src/test/testData/sql/formatter/DeleteWithSubQuery_format.sql b/src/test/testData/sql/formatter/DeleteWithSubQuery_format.sql index 9786eb04..b1c14a62 100644 --- a/src/test/testData/sql/formatter/DeleteWithSubQuery_format.sql +++ b/src/test/testData/sql/formatter/DeleteWithSubQuery_format.sql @@ -9,3 +9,6 @@ DELETE FROM user_session s WHERE u.id = /* id */1 AND u.session_id = u.id AND u.time_stamp < /* current */'2099-12-31 00:00:00' ) + AND s.user_number IN ( SELECT u.number + FROM user u + WHERE u.status = /* status */'active' ) diff --git a/src/test/testData/sql/formatter/OrderByGroup.sql b/src/test/testData/sql/formatter/OrderByGroup.sql new file mode 100644 index 00000000..5a1f2357 --- /dev/null +++ b/src/test/testData/sql/formatter/OrderByGroup.sql @@ -0,0 +1,25 @@ +SELECT e.id + , e.name + , ROW_NUMBER() OVER(ORDER BY e.manager_id DESC) AS row_num + , RANK() OVER(PARTITION BY department_id +ORDER BY e.manager_id DESC +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank_num + , DENSE_RANK() OVER(PARTITION BY department_id +ORDER BY e.id ASC, e.manager_id ASC, created_at DESC +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS dense_rank_num + , SUM(amount) FILTER(WHERE status = 'active') AS dept_salary_avg + , COUNT(*) OVER(ORDER BY e.id, e.manager_id, created_at +ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) + , FIRST_VALUE(salary) IGNORE NULLS OVER(PARTITION BY department_id +ORDER BY e.id ASC, e.manager_id ASC, created_at DESC) + , LAST_VALUE() OVER(ORDER BY e.manager_id ASC +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS bottom_salary + , COUNT(*) FILTER(WHERE gender = 'F') AS female_count + , COUNT(*) FILTER(WHERE gender = 'F' AND id > 10) AS female_count + , LISTAGG(e.name + , ', ') WITHIN GROUP(ORDER BY name DESC) + FROM employees e + WHERE e.status = 1 + ORDER BY e.id + , e + , manager_id diff --git a/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective.sql b/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective.sql new file mode 100644 index 00000000..69945a1f --- /dev/null +++ b/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective.sql @@ -0,0 +1,29 @@ +SELECT e.id + -- with condition + , ROW_NUMBER() /*%if order */OVER(ORDER BY e.manager_id DESC)/*%end*/ AS row_num + , RANK() OVER(PARTITION BY department_id +/*%if order */ORDER BY e.manager_id DESC/*%end */ +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank_num + , DENSE_RANK() OVER(PARTITION BY department_id + /*%if order */ +ORDER BY e.id ASC, e.manager_id ASC, created_at DESC + /*%else */ +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING/*%end */) AS dense_rank_num + , SUM(amount)/*%if filter */ FILTER(WHERE status = 'active')/*%end*/ AS dept_salary_avg + , COUNT(*) OVER(ORDER BY e.id, e.manager_id, created_at + /*%if rows */ +ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING/*%end*/) + , COUNT(*) FILTER(WHERE gender = 'F' AND id > 10) AS female_count + , FIRST_VALUE(salary) IGNORE NULLS OVER(PARTITION BY department_id +ORDER BY e.id ASC, e.manager_id ASC, created_at DESC) + , LAST_VALUE() OVER(ORDER BY e.manager_id ASC + /*%if rows */ +ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING +/*%end*/) AS bottom_salary + , LISTAGG(e.name + , ', ') /*%if filter */WITHIN GROUP(ORDER BY name DESC)/*%end*/ + FROM employees e + WHERE e.status = 1 + ORDER BY e.id + , e + , manager_id diff --git a/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective_format.sql b/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective_format.sql new file mode 100644 index 00000000..1f4fe159 --- /dev/null +++ b/src/test/testData/sql/formatter/OrderByGroupWithConditionDirective_format.sql @@ -0,0 +1,45 @@ +SELECT e.id + -- with condition + , ROW_NUMBER() + /*%if order */ + OVER(ORDER BY e.manager_id DESC) + /*%end*/ + AS row_num + , RANK() OVER(PARTITION BY department_id + /*%if order */ + ORDER BY e.manager_id DESC + /*%end */ + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank_num + , DENSE_RANK() OVER(PARTITION BY department_id + /*%if order */ + ORDER BY e.id ASC, e.manager_id ASC, created_at DESC + /*%else */ + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + /*%end */) AS dense_rank_num + , SUM(amount) + /*%if filter */ + FILTER(WHERE status = 'active') + /*%end*/ + AS dept_salary_avg + , COUNT(*) OVER(ORDER BY e.id, e.manager_id, created_at + /*%if rows */ + ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING + /*%end*/) + , COUNT(*) FILTER(WHERE gender = 'F' + AND id > 10) AS female_count + , FIRST_VALUE(salary) IGNORE NULLS OVER(PARTITION BY department_id + ORDER BY e.id ASC, e.manager_id ASC, created_at DESC) + , LAST_VALUE() OVER(ORDER BY e.manager_id ASC + /*%if rows */ + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + /*%end*/) AS bottom_salary + , LISTAGG(e.name + , ', ') + /*%if filter */ + WITHIN GROUP (ORDER BY name DESC) + /*%end*/ + FROM employees e + WHERE e.status = 1 + ORDER BY e.id + , e + , manager_id diff --git a/src/test/testData/sql/formatter/OrderByGroup_format.sql b/src/test/testData/sql/formatter/OrderByGroup_format.sql new file mode 100644 index 00000000..1b46f275 --- /dev/null +++ b/src/test/testData/sql/formatter/OrderByGroup_format.sql @@ -0,0 +1,26 @@ +SELECT e.id + , e.name + , ROW_NUMBER() OVER(ORDER BY e.manager_id DESC) AS row_num + , RANK() OVER(PARTITION BY department_id + ORDER BY e.manager_id DESC + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS rank_num + , DENSE_RANK() OVER(PARTITION BY department_id + ORDER BY e.id ASC, e.manager_id ASC, created_at DESC + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS dense_rank_num + , SUM(amount) FILTER(WHERE status = 'active') AS dept_salary_avg + , COUNT(*) OVER(ORDER BY e.id, e.manager_id, created_at + ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) + , FIRST_VALUE(salary) IGNORE NULLS OVER(PARTITION BY department_id + ORDER BY e.id ASC, e.manager_id ASC, created_at DESC) + , LAST_VALUE() OVER(ORDER BY e.manager_id ASC + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS bottom_salary + , COUNT(*) FILTER(WHERE gender = 'F') AS female_count + , COUNT(*) FILTER(WHERE gender = 'F' + AND id > 10) AS female_count + , LISTAGG(e.name + , ', ') WITHIN GROUP (ORDER BY name DESC) + FROM employees e + WHERE e.status = 1 + ORDER BY e.id + , e + , manager_id