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 6ac0a487..a4cdc696 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 @@ -112,9 +112,9 @@ open class SqlBlock( * * @example * ```sql - * WHERE - * /*%if status == "pending" */ - * status = 'pending' + * WHERE -- grand + * /*%if status == "pending" */ -- parent + * status = 'pending' -- child * ``` */ protected fun isElementAfterConditionLoopDirective(): Boolean = @@ -123,15 +123,27 @@ open class SqlBlock( (parent.parentBlock is SqlNewGroupBlock || parent.parentBlock is SqlElConditionLoopCommentBlock) } == true + protected fun isElementAfterConditionLoopEnd(): Boolean = + ( + prevBlocks + .lastOrNull() + ?.childBlocks + ?.firstOrNull() as? SqlElConditionLoopCommentBlock + )?.conditionEnd != null + protected fun isFirstChildConditionLoopDirective(): Boolean = childBlocks.firstOrNull() is SqlElConditionLoopCommentBlock fun getChildBlocksDropLast( dropIndex: Int = 1, skipCommentBlock: Boolean = true, + skipConditionLoopCommentBlock: Boolean = true, ): List { - val children = childBlocks.dropLast(dropIndex) + var children = childBlocks.dropLast(dropIndex) if (skipCommentBlock) { - return children.filter { it !is SqlDefaultCommentBlock } + children = children.filter { it !is SqlDefaultCommentBlock } + } + if (skipConditionLoopCommentBlock) { + children = children.filter { it !is SqlElConditionLoopCommentBlock } } return children } @@ -175,7 +187,8 @@ open class SqlBlock( private fun shouldSaveSpaceForConditionLoop(): Boolean = isConditionLoopDirectiveRegisteredBeforeParent() || isElementAfterConditionLoopDirective() || - isFirstChildConditionLoopDirective() + isFirstChildConditionLoopDirective() || + isElementAfterConditionLoopEnd() private fun shouldSaveSpaceForNewGroup(parent: SqlNewGroupBlock): Boolean { val prevWord = prevBlocks.lastOrNull { it !is SqlCommentBlock } @@ -202,6 +215,9 @@ open class SqlBlock( lastPrevBlock.node.psi.startOffset > parent.node.psi.startOffset } + protected fun getLastBlockHasConditionLoopDirective(): SqlElConditionLoopCommentBlock? = + (prevBlocks.lastOrNull()?.childBlocks?.firstOrNull() as? SqlElConditionLoopCommentBlock) + /** * Creates the indentation length for the block. * diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt index 4bb701da..2b0cd5bf 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/SqlKeywordBlock.kt @@ -17,7 +17,6 @@ package org.domaframework.doma.intellij.formatter.block import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock -import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock @@ -96,13 +95,7 @@ open class SqlKeywordBlock( } else -> { - parentBlock?.let { parent -> - if (parent is SqlElConditionLoopCommentBlock) { - parent.indent.groupIndentLen - } else { - 1 - } - } ?: 1 + parentBlock?.indent?.groupIndentLen ?: 1 } } } 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 bc7e0924..9a8bb731 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 @@ -67,6 +67,9 @@ class SqlElConditionLoopCommentBlock( } companion object { + private const val DIRECTIVE_INDENT_STEP = 2 + private const val DEFAULT_INDENT_OFFSET = 1 + private val LINE_BREAK_PARENT_TYPES = listOf( SqlSubGroupBlock::class, @@ -121,7 +124,7 @@ class SqlElConditionLoopCommentBlock( childBlocks.forEach { child -> if (child is SqlElConditionLoopCommentBlock && child.conditionType.isStartDirective()) { // If the child is a condition loop directive, align its indentation with the parent directive - child.indent.indentLen = indent.indentLen.plus(2) + child.indent.indentLen = indent.indentLen.plus(DIRECTIVE_INDENT_STEP) } else if (child is SqlLineCommentBlock) { if (PsiTreeUtil.prevLeaf(child.node.psi, false)?.text?.contains(StringUtil.LINE_SEPARATE) == true) { child.indent.indentLen = indent.groupIndentLen @@ -201,37 +204,7 @@ class SqlElConditionLoopCommentBlock( } val openConditionLoopDirectiveCount = getOpenDirectiveCount(parent) when (parent) { - is SqlSubGroupBlock -> { - val parentGroupIndentLen = parent.indent.groupIndentLen - val grand = parent.parentBlock - grand?.let { grand -> - if (grand is SqlCreateKeywordGroupBlock) { - val grandIndentLen = grand.indent.groupIndentLen - return grandIndentLen.plus(parentGroupIndentLen).minus(1) - } - if (grand is SqlInsertQueryGroupBlock) { - return parentGroupIndentLen - } - if (grand is SqlColumnRawGroupBlock) { - val grandIndentLen = grand.indent.groupIndentLen - var prevTextLen = 1 - parent.prevChildren?.dropLast(1)?.forEach { prev -> - prevTextLen = prevTextLen.plus(prev.getNodeText().length) - } - return grandIndentLen.plus(prevTextLen) - } - } - return if (TypeUtil.isExpectedClassType( - SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, - parent, - ) || - parent is SqlWithCommonTableGroupBlock - ) { - parentGroupIndentLen.plus(openConditionLoopDirectiveCount * 2) - } else { - parentGroupIndentLen.plus(openConditionLoopDirectiveCount * 2).plus(1) - } - } + is SqlSubGroupBlock -> return calculateSubGroupBlockIndent(parent, openConditionLoopDirectiveCount) is SqlElConditionLoopCommentBlock -> { if (conditionType.isEnd()) { @@ -241,26 +214,26 @@ class SqlElConditionLoopCommentBlock( } else if (conditionType.isElse()) { return parent.indent.indentLen } else { - return parent.indent.indentLen.plus(2) + return parent.indent.indentLen.plus(DIRECTIVE_INDENT_STEP) } } is SqlKeywordGroupBlock -> { // At this point, it's not possible to determine whether the parent keyword group appears before or after this block based solely on the parent-child relationship. // Therefore, determine the position directly using the text offset. - return if (parent.node.startOffset < - node.startOffset - ) { - // The child branch applies in cases where a conditional directive is included as a child of this block. - val questOffset = if (parent is SqlWithQueryGroupBlock) 0 else 1 - parent.indent.groupIndentLen - .plus(openConditionLoopDirectiveCount * 2) - .plus(questOffset) - } else { - parent.indent.indentLen.plus(openConditionLoopDirectiveCount * 2) + if (isBeforeParentBlock()) { + return parent.indent.indentLen + openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP + } + getLastBlockHasConditionLoopDirective()?.let { lastBlock -> + if (lastBlock.conditionEnd != null) { + return lastBlock.indent.indentLen + } } + return parent.indent.groupIndentLen + + openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP + + if (parent !is SqlWithQueryGroupBlock) 1 else 0 } - else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * 2) + else -> return parent.indent.indentLen.plus(openConditionLoopDirectiveCount * DIRECTIVE_INDENT_STEP) } } return 0 @@ -285,4 +258,57 @@ class SqlElConditionLoopCommentBlock( val diffCount = startDirectives.minus(endDirectives) return if (diffCount > 0) diffCount.minus(1) else 0 } + + /** + * Determine if this conditional loop directive block is positioned before its parent block. + */ + fun isBeforeParentBlock(): Boolean { + parentBlock?.let { parent -> + return parent.node.startOffset > node.startOffset + } + return false + } + + fun checkConditionLoopDirectiveParentBlock(block: SqlBlock): Boolean = isBeforeParentBlock() && parentBlock == block + + private fun calculateSubGroupBlockIndent( + parent: SqlSubGroupBlock, + openDirectiveCount: Int, + ): Int { + val parentGroupIndentLen = parent.indent.groupIndentLen + val grand = parent.parentBlock + + grand?.let { grandParent -> + when (grandParent) { + is SqlCreateKeywordGroupBlock -> { + val grandIndentLen = grandParent.indent.groupIndentLen + return grandIndentLen.plus(parentGroupIndentLen).minus(DEFAULT_INDENT_OFFSET) + } + is SqlInsertQueryGroupBlock -> return parentGroupIndentLen + is SqlColumnRawGroupBlock -> { + val grandIndentLen = grandParent.indent.groupIndentLen + val prevTextLen = calculatePreviousTextLength(parent) + return grandIndentLen.plus(prevTextLen) + } + } + } + + return if (shouldNotIndent(parent)) { + parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP) + } else { + parentGroupIndentLen.plus(openDirectiveCount * DIRECTIVE_INDENT_STEP).plus(DEFAULT_INDENT_OFFSET) + } + } + + private fun calculatePreviousTextLength(parent: SqlSubGroupBlock): Int { + var prevTextLen = DEFAULT_INDENT_OFFSET + parent.prevChildren?.dropLast(1)?.forEach { prev -> + prevTextLen = prevTextLen.plus(prev.getNodeText().length) + } + return prevTextLen + } + + private fun shouldNotIndent(parent: SqlSubGroupBlock): Boolean = + TypeUtil.isExpectedClassType(SqlRightPatternBlock.NOT_INDENT_EXPECTED_TYPES, parent) || + parent is SqlWithCommonTableGroupBlock } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt index 7541feb0..795a6c52 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlJoinGroupBlock.kt @@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt index 12cab407..18277162 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/condition/SqlConditionKeywordGroupBlock.kt @@ -18,7 +18,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.condition import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlSecondOptionKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt new file mode 100644 index 00000000..a02db661 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlInGroupBlock.kt @@ -0,0 +1,86 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed 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.domaframework.doma.intellij.formatter.block.group.keyword.option + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock +import org.domaframework.doma.intellij.formatter.util.IndentType +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext +import org.domaframework.doma.intellij.psi.SqlTypes + +class SqlInGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlKeywordGroupBlock( + node, + IndentType.OPTIONS, + context, + ) { + override fun setParentGroupBlock(lastGroup: SqlBlock?) { + super.setParentGroupBlock(lastGroup) + indent.indentLen = createBlockIndentLen() + indent.groupIndentLen = createGroupIndentLen() + } + + override fun createBlockIndentLen(): Int { + parentBlock?.let { parent -> + if (parent is SqlElConditionLoopCommentBlock && + parent.checkConditionLoopDirectiveParentBlock(this) + ) { + return parent.indent.indentLen + } + val prevChildren = this.prevBlocks + val children = prevChildren.filter { it !is SqlDefaultCommentBlock } + val firstChild = children.firstOrNull() + val sumChildren = + if (firstChild is SqlElConditionLoopCommentBlock) { + children.drop(1).dropLastWhile { it == this } + } else { + children + } + + val dotCount = sumChildren.count { it.node.elementType == SqlTypes.DOT } + val parentText = (parent as? SqlElConditionLoopCommentBlock)?.parentBlock?.getNodeText()?.length ?: 0 + + return sumChildren + .sumOf { prev -> + prev + .getChildrenTextLen() + .plus(prev.getNodeText().length.plus(1)) + }.minus(dotCount * 2) + .plus(parent.indent.groupIndentLen) + .plus(parentText) + .plus(1) + } + return 0 + } + + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) + + override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { + if (lastGroup is SqlElConditionLoopCommentBlock) { + return if (lastGroup.conditionType.isElse()) { + false + } else { + !lastGroup.isBeforeParentBlock() + } + } + return false + } +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt similarity index 95% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt index f7bdfc7d..ceeeae7f 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlLateralGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlLateralGroupBlock.kt @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block.group.keyword +package org.domaframework.doma.intellij.formatter.block.group.keyword.option import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt similarity index 96% rename from src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt rename to src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt index 2ec0be52..88b8e91b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/SqlSecondOptionKeywordGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/option/SqlSecondOptionKeywordGroupBlock.kt @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.domaframework.doma.intellij.formatter.block.group.keyword +package org.domaframework.doma.intellij.formatter.block.group.keyword.option import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlKeywordBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlSubQueryGroupBlock import org.domaframework.doma.intellij.formatter.util.IndentType diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt new file mode 100644 index 00000000..7e925044 --- /dev/null +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/second/SqlWhereGroupBlock.kt @@ -0,0 +1,26 @@ +/* + * Copyright Doma Tools Authors + * + * Licensed 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.domaframework.doma.intellij.formatter.block.group.keyword.second + +import com.intellij.lang.ASTNode +import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext + +class SqlWhereGroupBlock( + node: ASTNode, + context: SqlBlockFormattingContext, +) : SqlSecondKeywordBlock(node, context) { + override fun createGroupIndentLen(): Int = indent.indentLen.plus(getNodeText().length) +} diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt index 3fdf8018..1c5d0801 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlJoinQueriesGroupBlock.kt @@ -17,6 +17,7 @@ package org.domaframework.doma.intellij.formatter.block.group.keyword.top import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock +import org.domaframework.doma.intellij.formatter.block.SqlFileBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithQuerySubGroupBlock import org.domaframework.doma.intellij.formatter.util.SqlBlockFormattingContext @@ -41,8 +42,8 @@ class SqlJoinQueriesGroupBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> return when (parent) { - is SqlWithQuerySubGroupBlock, - -> parent.indent.groupIndentLen + is SqlFileBlock -> 0 + is SqlWithQuerySubGroupBlock -> parent.indent.groupIndentLen is SqlElConditionLoopCommentBlock -> createIndentLenInConditionLoopDirective(parent) else -> parent.indent.groupIndentLen.plus(1) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt index f14ead1a..d388aa1b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/top/SqlTopQueryGroupBlock.kt @@ -41,7 +41,7 @@ abstract class SqlTopQueryGroupBlock( SqlWithQuerySubGroupBlock::class, SqlElConditionLoopCommentBlock::class, ) - private val offset = 0 + private const val OFFSET = 0 } override fun setParentGroupBlock(lastGroup: SqlBlock?) { @@ -55,7 +55,7 @@ abstract class SqlTopQueryGroupBlock( override fun createBlockIndentLen(): Int { parentBlock?.let { parent -> - if (parent.indent.indentLevel == IndentType.FILE) return offset + if (parent.indent.indentLevel == IndentType.FILE) return OFFSET if (parent is SqlElConditionLoopCommentBlock) { return createIndentLenInConditionLoopDirective(parent) } @@ -84,7 +84,7 @@ abstract class SqlTopQueryGroupBlock( // align with the indent of that parent's parent prevGroupBlock?.let { prev -> if (prev.indent.indentLevel >= indent.indentLevel) { - p.indent.indentLen = prev.parentBlock?.indent?.indentLen ?: offset + p.indent.indentLen = prev.parentBlock?.indent?.indentLen ?: OFFSET } } } else { diff --git a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt index a82a62e2..a89e35fc 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/formatter/block/group/keyword/with/SqlWithCommonTableGroupBlock.kt @@ -77,7 +77,7 @@ class SqlWithCommonTableGroupBlock( parentBlock?.let { parent -> val prevBlock = parent - .getChildBlocksDropLast() + .getChildBlocksDropLast(skipConditionLoopCommentBlock = false) .lastOrNull() return if (prevBlock is SqlElConditionLoopCommentBlock) 4 else 0 } 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 faf0e30e..4cac053e 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 @@ -21,14 +21,14 @@ import com.intellij.lang.ASTNode import com.intellij.psi.formatter.common.AbstractBlock 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.SqlCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.SqlNewGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlFromGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithColumnGroupBlock @@ -122,11 +122,17 @@ abstract class SqlSubGroupBlock( override fun isSaveSpace(lastGroup: SqlBlock?): Boolean { lastGroup?.let { lastBlock -> if (lastBlock is SqlJoinQueriesGroupBlock) return true - val prevBlock = prevChildren?.dropLast(1)?.lastOrNull() - if (prevBlock is SqlKeywordBlock) { - if (prevBlock.getNodeText() == "in") return false + if (lastGroup is SqlInGroupBlock) return false + if (lastGroup is SqlElConditionLoopCommentBlock) { + return lastGroup.checkConditionLoopDirectiveParentBlock(this) || + lastGroup.conditionType.isElse() + } + val grand = lastBlock.parentBlock + if (grand is SqlElConditionLoopCommentBlock) { + if (grand.conditionType.isElse()) { + return false + } } - if (lastGroup is SqlElConditionLoopCommentBlock) return true return TypeUtil.isExpectedClassType(NEW_LINE_EXPECTED_TYPES, lastBlock.parentBlock) } 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 7f5b0793..faea6ab8 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 @@ -21,6 +21,7 @@ import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlDefaultCommentBlock import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlJoinGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.with.SqlWithCommonTableGroupBlock @@ -51,11 +52,25 @@ open class SqlSubQueryGroupBlock( override fun createBlockIndentLen(): Int = parentBlock?.let { parent -> return when (parent) { - is SqlElConditionLoopCommentBlock, is SqlWithQuerySubGroupBlock -> return parent.indent.groupIndentLen + is SqlElConditionLoopCommentBlock -> { + return if (parent.isBeforeParentBlock()) { + parent.parentBlock + ?.indent + ?.groupIndentLen + ?.plus(1) ?: 1 + } else { + parent.indent.indentLen + } + } + is SqlWithQuerySubGroupBlock -> return parent.indent.groupIndentLen is SqlJoinQueriesGroupBlock -> return parent.indent.indentLen is SqlJoinGroupBlock -> return parent.indent.groupIndentLen.plus(1) else -> { - val children = prevChildren?.filter { it !is SqlDefaultCommentBlock } + val children = + prevChildren?.filter { + it !is SqlDefaultCommentBlock && + (parent as? SqlKeywordGroupBlock)?.topKeywordBlocks?.contains(it) == false + } // 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 = 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 d799e122..e4106390 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 @@ -25,10 +25,11 @@ import org.domaframework.doma.intellij.formatter.block.conflict.SqlDoGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateViewGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlReturningGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlTopQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.update.SqlUpdateQueryGroupBlock @@ -62,6 +63,7 @@ class SqlBlockRelationBuilder( SqlInlineSecondGroupBlock::class, SqlColumnDefinitionRawGroupBlock::class, SqlLateralGroupBlock::class, + SqlInGroupBlock::class, ) private val TOP_LEVEL_EXPECTED_TYPES = @@ -136,11 +138,27 @@ class SqlBlockRelationBuilder( val context = SetParentContext(childBlock, blockBuilder) if (lastGroupBlock is SqlElConditionLoopCommentBlock) { handleConditionLoopParent(lastGroupBlock, context, childBlock) - } else if (childBlock.indent.indentLevel == IndentType.TOP) { + return + } + if (childBlock.indent.indentLevel == IndentType.TOP) { handleTopLevelKeyword(lastGroupBlock, childBlock, context) - } else { - handleNonTopLevelKeyword(lastGroupBlock, lastIndentLevel, childBlock, context) + return + } + if (lastGroupBlock !is SqlSubGroupBlock) { + if (lastIndentLevel > childBlock.indent.indentLevel) { + val findLastGroup = + blockBuilder.getGroupTopNodeIndexHistory().findLast { + it.indent.indentLevel < childBlock.indent.indentLevel || + it is + SqlElConditionLoopCommentBlock + } + if (findLastGroup is SqlElConditionLoopCommentBlock) { + handleConditionLoopParent(findLastGroup, context, childBlock) + return + } + } } + handleNonTopLevelKeyword(lastGroupBlock, lastIndentLevel, childBlock, context) } private fun handleConditionLoopParent( 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 5b7505fd..c04812d5 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 @@ -18,10 +18,12 @@ package org.domaframework.doma.intellij.formatter.handler import com.intellij.lang.ASTNode import org.domaframework.doma.intellij.formatter.block.SqlBlock import org.domaframework.doma.intellij.formatter.block.SqlCommaBlock +import org.domaframework.doma.intellij.formatter.block.comment.SqlElConditionLoopCommentBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictClauseBlock import org.domaframework.doma.intellij.formatter.block.conflict.SqlConflictExpressionSubGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionalExpressionGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlReturningGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.second.SqlValuesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.subgroup.SqlFunctionParamBlock @@ -31,10 +33,8 @@ 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 IN_KEYWORD = "in" private const val RETURNING_KEYWORD = "returning" /** @@ -75,13 +75,7 @@ object NotQueryGroupHandler { /** * Checks if the last group has an 'IN' keyword as its last option keyword. */ - private fun hasInKeyword(lastGroup: SqlBlock?): Boolean { - val lastKeyword = - lastGroup - ?.childBlocks - ?.lastOrNull { SqlKeywordUtil.isOptionSqlKeyword(it.getNodeText()) } - return lastKeyword?.getNodeText()?.lowercase() == IN_KEYWORD - } + private fun hasInKeyword(lastGroup: SqlBlock?): Boolean = lastGroup is SqlInGroupBlock /** * Creates a conditional expression group block. @@ -94,7 +88,20 @@ object NotQueryGroupHandler { /** * Checks if the last group has a word block context that requires function or alias handling. */ - private fun hasFunctionOrAliasContext(lastGroup: SqlBlock?): Boolean = lastGroup?.childBlocks?.lastOrNull() is SqlWordBlock + private fun hasFunctionOrAliasContext(lastGroup: SqlBlock?): Boolean { + val lastChild = lastGroup?.childBlocks?.lastOrNull() + if (lastChild != null) { + when (lastChild) { + is SqlElConditionLoopCommentBlock -> { + if (lastChild.isBeforeParentBlock()) { + return lastChild.parentBlock is SqlWordBlock + } + } + else -> return lastGroup.childBlocks.lastOrNull() is SqlWordBlock + } + } + return false + } /** * 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/util/SqlBlockGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/formatter/util/SqlBlockGenerator.kt index a6dbb10d..3ea84ce3 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 @@ -37,8 +37,6 @@ import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnBlo import org.domaframework.doma.intellij.formatter.block.group.column.SqlColumnDefinitionRawGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlJoinGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlKeywordGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlLateralGroupBlock -import org.domaframework.doma.intellij.formatter.block.group.keyword.SqlSecondOptionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.condition.SqlConditionKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateKeywordGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlCreateTableColumnDefinitionGroupBlock @@ -47,9 +45,13 @@ import org.domaframework.doma.intellij.formatter.block.group.keyword.create.SqlC import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.inline.SqlInlineSecondGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.insert.SqlInsertQueryGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlInGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlLateralGroupBlock +import org.domaframework.doma.intellij.formatter.block.group.keyword.option.SqlSecondOptionKeywordGroupBlock 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.second.SqlWhereGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlDeleteQueryGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlJoinQueriesGroupBlock import org.domaframework.doma.intellij.formatter.block.group.keyword.top.SqlSelectQueryGroupBlock @@ -140,6 +142,12 @@ class SqlBlockGenerator( if (keywordText == "lateral") { return SqlLateralGroupBlock(child, sqlBlockFormattingCtx) } + if (keywordText == "in") { + return SqlInGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } } IndentType.CONFLICT -> { @@ -264,6 +272,12 @@ class SqlBlockGenerator( sqlBlockFormattingCtx, ) } + "where" -> { + SqlWhereGroupBlock( + child, + sqlBlockFormattingCtx, + ) + } "values" -> SqlValuesGroupBlock( 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 81d46c9a..cc07e945 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/formatter/SqlFormatterTest.kt @@ -230,8 +230,24 @@ class SqlFormatterTest : BasePlatformTestCase() { formatSqlFile("WithOptional.sql", "WithOptional$formatDataPrefix.sql") } - fun testCalculationDirectivesFormatter() { - formatSqlFile("CalculationDirectives.sql", "CalculationDirectives$formatDataPrefix.sql") + fun testConditionalInClauseFormatter() { + formatSqlFile("ConditionalInClause.sql", "ConditionalInClause$formatDataPrefix.sql") + } + + fun testConditionalJoinClauseFormatter() { + formatSqlFile("ConditionalJoinClause.sql", "ConditionalJoinClause$formatDataPrefix.sql") + } + + fun testConditionalWhereClauseFormatter() { + formatSqlFile("ConditionalWhereClause.sql", "ConditionalWhereClause$formatDataPrefix.sql") + } + + fun testConditionalUnionFormatter() { + formatSqlFile("ConditionalUnion.sql", "ConditionalUnion$formatDataPrefix.sql") + } + + fun testConditionalSubqueryFormatter() { + formatSqlFile("ConditionalSubquery.sql", "ConditionalSubquery$formatDataPrefix.sql") } private fun formatSqlFile( diff --git a/src/test/testData/sql/formatter/ConditionalInClause.sql b/src/test/testData/sql/formatter/ConditionalInClause.sql new file mode 100644 index 00000000..e5e58b09 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalInClause.sql @@ -0,0 +1,12 @@ +SELECT id, name, department_id +FROM employees +WHERE +/*%if departmentIds != null */ +department_id IN /* departmentIds */(1, 2, 3) +/*%end*/ +/*%if statusList != null */ +AND status IN /* statusList */('active', 'pending', 'inactive') +/*%end*/ +/*%if categoryIds != null && categoryIds.size() > 0 */ +AND category_id IN /* categoryIds */(100, 200, 300, 400, 500) +/*%end*/ \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalInClause_format.sql b/src/test/testData/sql/formatter/ConditionalInClause_format.sql new file mode 100644 index 00000000..aadb9e53 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalInClause_format.sql @@ -0,0 +1,14 @@ +SELECT id + , name + , department_id + FROM employees + WHERE + /*%if departmentIds != null */ + department_id IN /* departmentIds */(1, 2, 3) + /*%end*/ + /*%if statusList != null */ + AND status IN /* statusList */('active', 'pending', 'inactive') + /*%end*/ + /*%if categoryIds != null && categoryIds.size() > 0 */ + AND category_id IN /* categoryIds */(100, 200, 300, 400, 500) + /*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalJoinClause.sql b/src/test/testData/sql/formatter/ConditionalJoinClause.sql new file mode 100644 index 00000000..427926c8 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalJoinClause.sql @@ -0,0 +1,15 @@ +SELECT e.*, d.department_name, p.project_name +FROM employees e +/*%if includeDepartments */ +INNER JOIN departments d ON e.department_id = d.id +/*%end*/ +/*%if includeProjects */ +LEFT JOIN projects p ON e.project_id = p.id +/*%elseif includeActiveProjects */ +INNER JOIN projects p ON e.project_id = p.id AND p.status = 'active' +/*%end*/ +/*%if includeSkills */ +LEFT JOIN employee_skills es ON e.id = es.employee_id +INNER JOIN skills s ON es.skill_id = s.id +/*%end*/ +WHERE e.active = true \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql b/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql new file mode 100644 index 00000000..3dd1b211 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalJoinClause_format.sql @@ -0,0 +1,23 @@ +SELECT e.* + , d.department_name + , p.project_name + FROM employees e + /*%if includeDepartments */ + INNER JOIN departments d + ON e.department_id = d.id + /*%end*/ + /*%if includeProjects */ + LEFT JOIN projects p + ON e.project_id = p.id + /*%elseif includeActiveProjects */ + INNER JOIN projects p + ON e.project_id = p.id + AND p.status = 'active' + /*%end*/ + /*%if includeSkills */ + LEFT JOIN employee_skills es + ON e.id = es.employee_id + INNER JOIN skills s + ON es.skill_id = s.id + /*%end*/ + WHERE e.active = true diff --git a/src/test/testData/sql/formatter/ConditionalSubquery.sql b/src/test/testData/sql/formatter/ConditionalSubquery.sql new file mode 100644 index 00000000..d6a203d2 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalSubquery.sql @@ -0,0 +1,41 @@ +SELECT e.id +, e.name +, +/*%if includeSkillCount */ +( SELECT COUNT(DISTINCT es.skill_id) +FROM employee_skills es +/*%if onlyActiveSkills */ +INNER JOIN skills sk ON es.skill_id = sk.id +WHERE es.employee_id = e.id +AND sk.is_active = true +/*%if skillCategories != null && skillCategories.size() > 0 */ +AND sk.category IN /* skillCategories */('technical', 'management') +/*%end*/ +/*%else*/ +WHERE es.employee_id = e.id +/*%end*/ ), +/*%end*/ +d.department_name +FROM employees e +INNER JOIN departments d ON e.department_id = d.id + WHERE/*%if filterByHighPerformers */ +ids IN ( SELECT id + FROM performance_reviews pr +WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) +/*%else*/ +e.is_active = true +/*%end*/ +/*%if excludeDepartments != null && excludeDepartments.size() > 0 */ +AND e.department_id NOT IN ( SELECT d2.id +FROM departments d2 +WHERE d2.name IN /* excludeDepartments */('HR', 'Finance') +/*%if onlyActiveDepartments */ +AND d2.is_active = true +/*%end*/ ) +/*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalSubquery_format.sql b/src/test/testData/sql/formatter/ConditionalSubquery_format.sql new file mode 100644 index 00000000..d6f9e444 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalSubquery_format.sql @@ -0,0 +1,44 @@ +SELECT e.id + , e.name + , /*%if includeSkillCount */ + ( SELECT COUNT(DISTINCT es.skill_id) + FROM employee_skills es + /*%if onlyActiveSkills */ + INNER JOIN skills sk + ON es.skill_id = sk.id + WHERE es.employee_id = e.id + AND sk.is_active = true + /*%if skillCategories != null && skillCategories.size() > 0 */ + AND sk.category IN /* skillCategories */('technical', 'management') + /*%end*/ + /*%else*/ + WHERE es.employee_id = e.id + /*%end*/ ) + , + /*%end*/ + d.department_name + FROM employees e + INNER JOIN departments d + ON e.department_id = d.id + WHERE + /*%if filterByHighPerformers */ + ids IN ( SELECT id + FROM performance_reviews pr + WHERE pr.employee_id = e.id + /*%if reviewPeriod != null */ + AND pr.review_date BETWEEN /* reviewPeriod.startDate */'2023-01-01' AND /* reviewPeriod.endDate */'2023-12-31' + /*%end*/ + /*%if minScore != null */ + AND pr.performance_score >= /* minScore */4.0 + /*%end*/ ) + /*%else*/ + e.is_active = true + /*%end*/ + /*%if excludeDepartments != null && excludeDepartments.size() > 0 */ + AND e.department_id NOT IN ( SELECT d2.id + FROM departments d2 + WHERE d2.name IN /* excludeDepartments */('HR', 'Finance') + /*%if onlyActiveDepartments */ + AND d2.is_active = true + /*%end*/ ) + /*%end*/ diff --git a/src/test/testData/sql/formatter/ConditionalUnion.sql b/src/test/testData/sql/formatter/ConditionalUnion.sql new file mode 100644 index 00000000..d2bf6252 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalUnion.sql @@ -0,0 +1,31 @@ +/*%if includeEmployees */ +SELECT 'Employee' as type, e.id, e.name, e.email +FROM employees e +WHERE e.is_active = true +/*%if departmentId != null */ +AND e.department_id = /* departmentId */1 +/*%end*/ +/*%end*/ +/*%if includeEmployees && includeContractors */ +UNION ALL +/*%end*/ +/*%if includeContractors */ +SELECT 'Contractor' as type, c.id, c.name, c.email +FROM contractors c +WHERE c.contract_end_date >= CURRENT_DATE +/*%if projectId != null */ +AND c.project_id = /* projectId */10 +/*%end*/ +/*%end*/ +/*%if (includeEmployees || includeContractors) && includeVendors */ +UNION ALL +/*%end*/ +/*%if includeVendors */ +SELECT 'Vendor' as type, v.id, v.company_name as name, v.contact_email as email +FROM vendors v +WHERE v.status = 'active' +/*%if vendorType != null */ +AND v.vendor_type IN /* vendorType */('supplier', 'service') +/*%end*/ +/*%end*/ +ORDER BY type, name \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalUnion_format.sql b/src/test/testData/sql/formatter/ConditionalUnion_format.sql new file mode 100644 index 00000000..8ebe0e71 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalUnion_format.sql @@ -0,0 +1,41 @@ +/*%if includeEmployees */ +SELECT 'Employee' AS type + , e.id + , e.name + , e.email + FROM employees e + WHERE e.is_active = true + /*%if departmentId != null */ + AND e.department_id = /* departmentId */1 + /*%end*/ +/*%end*/ +/*%if includeEmployees && includeContractors */ +UNION ALL +/*%end*/ +/*%if includeContractors */ +SELECT 'Contractor' AS type + , c.id + , c.name + , c.email + FROM contractors c + WHERE c.contract_end_date >= CURRENT_DATE + /*%if projectId != null */ + AND c.project_id = /* projectId */10 + /*%end*/ +/*%end*/ +/*%if (includeEmployees || includeContractors) && includeVendors */ +UNION ALL +/*%end*/ +/*%if includeVendors */ +SELECT 'Vendor' AS type + , v.id + , v.company_name AS name + , v.contact_email AS email + FROM vendors v + WHERE v.status = 'active' + /*%if vendorType != null */ + AND v.vendor_type IN /* vendorType */('supplier', 'service') + /*%end*/ +/*%end*/ +ORDER BY type + , name diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause.sql b/src/test/testData/sql/formatter/ConditionalWhereClause.sql new file mode 100644 index 00000000..605265f2 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalWhereClause.sql @@ -0,0 +1,25 @@ +SELECT * FROM products p +WHERE 1 = 1 +/*%if productName != null */ +AND p.name LIKE /* productName */'%laptop%' +/*%end*/ +/*%if minPrice != null */ +AND p.price >= /* minPrice */1000 +/*%end*/ +/*%if maxPrice != null */ +AND p.price <= /* maxPrice */5000 +/*%end*/ +/*%if categoryIds != null && categoryIds.size() > 0 */ +AND p.category_id IN /* categoryIds */(1, 2, 3) +/*%end*/ +/*%if status != null */ +/*%if status == "available" */ +AND p.stock_quantitys IN /* quantitys */(0, 1, 2) or p.is_active = true +/*%elseif status == "outofstock" */ +OR p.is_active = true +AND p.stock_quantitys IN /* quantitys */(0, 1, 2) +/*%else*/ +AND p.is_active = false +/*%end*/ +/*%end*/ +ORDER BY p.created_at DESC \ No newline at end of file diff --git a/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql new file mode 100644 index 00000000..53958211 --- /dev/null +++ b/src/test/testData/sql/formatter/ConditionalWhereClause_format.sql @@ -0,0 +1,27 @@ +SELECT * + FROM products p + WHERE 1 = 1 + /*%if productName != null */ + AND p.name LIKE /* productName */'%laptop%' + /*%end*/ + /*%if minPrice != null */ + AND p.price >= /* minPrice */1000 + /*%end*/ + /*%if maxPrice != null */ + AND p.price <= /* maxPrice */5000 + /*%end*/ + /*%if categoryIds != null && categoryIds.size() > 0 */ + AND p.category_id IN /* categoryIds */(1, 2, 3) + /*%end*/ + /*%if status != null */ + /*%if status == "available" */ + AND p.stock_quantitys IN /* quantitys */(0, 1, 2) + OR p.is_active = true + /*%elseif status == "outofstock" */ + OR p.is_active = true + AND p.stock_quantitys IN /* quantitys */(0, 1, 2) + /*%else*/ + AND p.is_active = false + /*%end*/ + /*%end*/ + ORDER BY p.created_at DESC diff --git a/src/test/testData/sql/formatter/NestedConditionalDirective.sql b/src/test/testData/sql/formatter/NestedConditionalDirective.sql new file mode 100644 index 00000000..a0e9a26a --- /dev/null +++ b/src/test/testData/sql/formatter/NestedConditionalDirective.sql @@ -0,0 +1,54 @@ +SELECT e.*, d.department_name, s.salary_amount +FROM employees e +/*%if departmentFilter != null */ +INNER JOIN departments d ON e.department_id = d.id +/*%end*/ +/*%if includeSalary */ +LEFT JOIN salaries s ON e.id = s.employee_id +/*%if salaryFilter != null */ +AND s.effective_date = ( +SELECT MAX(s2.effective_date) +FROM salaries s2 +WHERE s2.employee_id = e.id +/*%if salaryFilter.maxDate != null */ +AND s2.effective_date <= /* salaryFilter.maxDate */'2024-01-01' +/*%end*/ +) +/*%end*/ +/*%end*/ +WHERE +/*%if departmentFilter != null */ +/*%if departmentFilter.includeActive */ +d.is_active = true +/*%if departmentFilter.locations != null && departmentFilter.locations.size() > 0 */ +AND d.location IN /* departmentFilter.locations */('Tokyo', 'Osaka') +/*%end*/ +/*%else*/ +d.is_active = false +/*%end*/ +/*%if employeeFilter != null */ +AND +/*%if employeeFilter.minExperience != null */ +e.experience_years >= /* employeeFilter.minExperience */5 +/*%if employeeFilter.maxExperience != null */ +AND e.experience_years <= /* employeeFilter.maxExperience */10 +/*%end*/ +/*%end*/ +/*%if employeeFilter.skills != null && employeeFilter.skills.size() > 0 */ +AND EXISTS ( +SELECT 1 FROM employee_skills es +WHERE es.employee_id = e.id +/*%if employeeFilter.skillMatchType == "all" */ +AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) +GROUP BY es.employee_id +HAVING COUNT(DISTINCT es.skill_id) = /* employeeFilter.skills.size() */3 +/*%else*/ +AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) +/*%end*/ +) +/*%end*/ +/*%end*/ +/*%else*/ +e.department_id IS NULL +/*%end*/ +ORDER BY e.created_at DESC \ No newline at end of file diff --git a/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql b/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql new file mode 100644 index 00000000..0d565f92 --- /dev/null +++ b/src/test/testData/sql/formatter/NestedConditionalDirective_format.sql @@ -0,0 +1,56 @@ +SELECT e.* + , d.department_name + , s.salary_amount + FROM employees e + /*%if departmentFilter != null */ + INNER JOIN departments d + ON e.department_id = d.id + /*%end*/ + /*%if includeSalary */ + LEFT JOIN salaries s + ON e.id = s.employee_id + /*%if salaryFilter != null */ + AND s.effective_date = ( SELECT MAX(s2.effective_date) + FROM salaries s2 + WHERE s2.employee_id = e.id + /*%if salaryFilter.maxDate != null */ + AND s2.effective_date <= /* salaryFilter.maxDate */'2024-01-01' + /*%end*/) + /*%end*/ + /*%end*/ + WHERE + /*%if departmentFilter != null */ + /*%if departmentFilter.includeActive */ + d.is_active = true + /*%if departmentFilter.locations != null && departmentFilter.locations.size() > 0 */ + AND d.location IN /* departmentFilter.locations */('Tokyo', 'Osaka') + /*%end*/ + /*%else*/ + d.is_active = false + /*%end*/ + /*%if employeeFilter != null */ + AND + /*%if employeeFilter.minExperience != null */ + e.experience_years >= /* employeeFilter.minExperience */5 + /*%if employeeFilter.maxExperience != null */ + AND e.experience_years <= /* employeeFilter.maxExperience */10 + /*%end*/ + /*%end*/ + /*%if employeeFilter.skills != null && employeeFilter.skills.size() > 0 */ + AND EXISTS + ( SELECT 1 + FROM employee_skills es + WHERE es.employee_id = e.id + /*%if employeeFilter.skillMatchType == "all" */ + AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) + GROUP BY es.employee_id + HAVING COUNT(DISTINCT es.skill_id) = /* employeeFilter.skills.size() */3 + /*%else*/ + AND es.skill_id IN /* employeeFilter.skills */(1, 2, 3) + /*%end*/) + /*%end*/ + /*%end*/ + /*%else*/ + e.department_id IS NULL + /*%end*/ + ORDER BY e.created_at DESC diff --git a/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql b/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql index 21c7e4a3..7adfd6db 100644 --- a/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql +++ b/src/test/testData/sql/formatter/UseDirectiveWithQuery.sql @@ -2,7 +2,7 @@ WITH /*%for cte : cteDefinitions */ /* cte.name */user_stats AS ( -/* cte.query */SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id +/*# cte.query */SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id ) /*%if cte_has_next */,/*%end*/ /*%end*/ diff --git a/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql b/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql index fb6ce358..0c480d94 100644 --- a/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql +++ b/src/test/testData/sql/formatter/UseDirectiveWithQuery_format.sql @@ -2,7 +2,7 @@ WITH /*%for cte : cteDefinitions */ /* cte.name */ user_stats AS ( - /* cte.query */ + /*# cte.query */ SELECT user_id , COUNT(*) AS order_count FROM orders