From d5333bea05306685219f1dacf2ea5eaa865a455c Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 8 May 2025 15:47:53 +0900 Subject: [PATCH 1/6] Converting optional types to corresponding types --- .../intellij/common/sql/PsiClassTypeUtil.kt | 39 ++++++++++++++++++- .../intellij/common/util/ForDirectiveUtil.kt | 35 +++++++++-------- .../SqlParameterCompletionProvider.kt | 3 +- .../DocumentDaoParameterGenerator.kt | 4 +- .../document/generator/DocumentGenerator.kt | 32 +++++++++++---- .../generator/DocumentStaticFieldGenerator.kt | 4 +- .../extension/psi/PsiParameterExtension.kt | 3 +- .../sql/visitor/SqlInspectionVisitor.kt | 1 + 8 files changed, 91 insertions(+), 30 deletions(-) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt index bb3ed630..443a7a1e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt @@ -63,9 +63,46 @@ class PsiClassTypeUtil { type = type.parameters.firstOrNull() count++ } - return type as? PsiClassType + val convertOptional = type?.let { convertOptionalType(it, project) } + return convertOptional as? PsiClassType } return null } + + /** + * Check if daoParamType is an instance of PsiClassType representing Optional or its primitive variants + */ + fun convertOptionalType( + daoParamType: PsiType, + project: Project, + ): PsiType { + if (daoParamType is PsiClassType) { + val resolved = daoParamType.resolve() + val optionalTypeMap = + mapOf( + "java.util.OptionalInt" to "java.lang.Integer", + "java.util.OptionalDouble" to "java.lang.Double", + "java.util.OptionalLong" to "java.lang.Long", + ) + if (resolved != null) { + when (resolved.qualifiedName) { + "java.util.Optional" -> return daoParamType.parameters.firstOrNull() + ?: daoParamType + + else -> + optionalTypeMap[resolved.qualifiedName]?.let { optionalType -> + val newType = + PsiType.getTypeByName( + optionalType, + project, + GlobalSearchScope.allScope(project), + ) + return newType + } + } + } + } + return daoParamType + } } } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt index 877a8295..267eb5ee 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt @@ -259,7 +259,7 @@ class ForDirectiveUtil { val matchParam = daoMethod.findParameter(cleanString(topElementText)) val daoParamType = matchParam?.type ?: return null - fieldAccessTopParentClass = PsiParentClass(daoParamType) + fieldAccessTopParentClass = PsiParentClass(PsiClassTypeUtil.convertOptionalType(daoParamType, project)) } fieldAccessTopParentClass?.let { getFieldAccessLastPropertyClassType( @@ -329,14 +329,15 @@ class ForDirectiveUtil { ): ValidationResult? { var parent = if (isBatchAnnotation) { - val parentType = topParent.type + val parentType = PsiClassTypeUtil.convertOptionalType(topParent.type, project) val nextClassType = parentType as? PsiClassType ?: return null val nestType = nextClassType.parameters.firstOrNull() ?: return null - PsiParentClass(nestType) + PsiParentClass(PsiClassTypeUtil.convertOptionalType(nestType, project)) } else { - topParent + val convertOptional = PsiClassTypeUtil.convertOptionalType(topParent.type, project) + PsiParentClass(convertOptional) } - val parentType = parent.type + val parentType = PsiClassTypeUtil.convertOptionalType(parent.type, project) val classType = parentType as? PsiClassType ?: return null var competeResult: ValidationCompleteResult? = null @@ -353,8 +354,8 @@ class ForDirectiveUtil { // When a List type element is used as the parent, // the original declared type is retained and the referenced type is obtained by nesting. var parentListBaseType: PsiType? = - if (PsiClassTypeUtil.Companion.isIterableType(classType, project)) { - parentType + if (PsiClassTypeUtil.isIterableType(classType, project)) { + PsiClassTypeUtil.convertOptionalType(parentType, project) } else { null } @@ -375,24 +376,25 @@ class ForDirectiveUtil { parent .findField(searchElm) ?.let { match -> + val convertOptional = PsiClassTypeUtil.convertOptionalType(match.type, project) val type = parentListBaseType?.let { - PsiClassTypeUtil.Companion.getParameterType( + PsiClassTypeUtil.getParameterType( project, - match.type, + convertOptional, it, nestIndex, ) } - ?: match.type + ?: convertOptional val classType = type as? PsiClassType if (classType != null && - PsiClassTypeUtil.Companion.isIterableType( + PsiClassTypeUtil.isIterableType( classType, element.project, ) ) { - parentListBaseType = type + parentListBaseType = PsiClassTypeUtil.convertOptionalType(type, project) nestIndex = 0 } findFieldMethod?.invoke(type) @@ -402,19 +404,20 @@ class ForDirectiveUtil { .findMethod(searchElm) ?.let { match -> val returnType = match.returnType ?: return null + val convertOptionalType = PsiClassTypeUtil.convertOptionalType(returnType, project) val methodReturnType = parentListBaseType?.let { - PsiClassTypeUtil.Companion.getParameterType( + PsiClassTypeUtil.getParameterType( project, - returnType, + convertOptionalType, it, nestIndex, ) } - ?: returnType + ?: convertOptionalType val classType = methodReturnType as? PsiClassType if (classType != null && - PsiClassTypeUtil.Companion.isIterableType( + PsiClassTypeUtil.isIterableType( classType, element.project, ) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index 2538e08c..133c2c65 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -35,6 +35,7 @@ import com.intellij.util.ProcessingContext import org.domaframework.doma.intellij.common.dao.findDaoMethod import org.domaframework.doma.intellij.common.psi.PsiParentClass import org.domaframework.doma.intellij.common.psi.PsiPatternUtil +import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil import org.domaframework.doma.intellij.common.sql.cleanString import org.domaframework.doma.intellij.common.sql.directive.DirectiveCompletion import org.domaframework.doma.intellij.common.sql.validator.result.ValidationCompleteResult @@ -385,7 +386,7 @@ class SqlParameterCompletionProvider : CompletionProvider( return null } val immediate = findParam.getIterableClazz(daoMethod.getDomaAnnotationType()) - return immediate.type + return PsiClassTypeUtil.convertOptionalType(immediate.type, originalFile.project) } private fun getRefClazz( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt index 20b5602d..65706b7b 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentDaoParameterGenerator.kt @@ -28,9 +28,9 @@ import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr class DocumentDaoParameterGenerator( val originalElement: PsiElement, - val project: Project, + override val project: Project, val result: MutableList, -) : DocumentGenerator() { +) : DocumentGenerator(project) { override fun generateDocument() { var topParentType: PsiParentClass? = null val selfSkip = isSelfSkip(originalElement) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentGenerator.kt index d4ee088a..2717fb7e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentGenerator.kt @@ -15,12 +15,16 @@ */ package org.domaframework.doma.intellij.document.generator +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import org.domaframework.doma.intellij.common.psi.PsiParentClass +import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil import org.domaframework.doma.intellij.common.sql.foritem.ForItem import org.domaframework.doma.intellij.extension.psi.getForItem -abstract class DocumentGenerator { +abstract class DocumentGenerator( + open val project: Project, +) { abstract fun generateDocument() protected fun isSelfSkip(targetElement: PsiElement): Boolean { @@ -30,8 +34,10 @@ abstract class DocumentGenerator { } protected fun generateTypeLink(parentClass: PsiParentClass?): String { - if (parentClass?.type != null) { - return generateTypeLinkFromCanonicalText(parentClass.type.canonicalText) + val parentClassType = parentClass?.type + if (parentClassType != null) { + val convertOptionalType = PsiClassTypeUtil.convertOptionalType(parentClassType, project) + return generateTypeLinkFromCanonicalText(convertOptionalType.canonicalText) } return "" } @@ -40,22 +46,34 @@ abstract class DocumentGenerator { val regex = Regex("([a-zA-Z0-9_]+\\.)*([a-zA-Z0-9_]+)") val result = StringBuilder() var lastIndex = 0 + val optionalPackage = "java.util.Optional" + val optionalTypeMap = + listOf( + optionalPackage, + "${optionalPackage}Int", + "${optionalPackage}Double", + "${optionalPackage}Long", + ) + var skipCount = 0 for (match in regex.findAll(canonicalText)) { val fullMatch = match.value + val optionalSkip = optionalTypeMap.contains(fullMatch) + if (optionalSkip) skipCount++ + val typeName = match.groups[2]?.value ?: fullMatch val startIndex = match.range.first val endIndex = match.range.last + 1 - if (lastIndex < startIndex) { + if (lastIndex < startIndex && !optionalSkip) { result.append(canonicalText.substring(lastIndex, startIndex)) } - result.append("$typeName") + if (!optionalSkip) result.append("$typeName") lastIndex = endIndex } - if (lastIndex < canonicalText.length) { - result.append(canonicalText.substring(lastIndex)) + if (lastIndex + skipCount < canonicalText.length) { + result.append(canonicalText.substring(lastIndex + skipCount)) } return result.toString() diff --git a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentStaticFieldGenerator.kt b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentStaticFieldGenerator.kt index b0f82b24..88ca054c 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentStaticFieldGenerator.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/document/generator/DocumentStaticFieldGenerator.kt @@ -29,11 +29,11 @@ import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr class DocumentStaticFieldGenerator( val originalElement: PsiElement, - val project: Project, + override val project: Project, val result: MutableList, val staticFieldAccessExpr: SqlElStaticFieldAccessExpr, val file: PsiFile, -) : DocumentGenerator() { +) : DocumentGenerator(project) { override fun generateDocument() { val fieldAccessBlocks = staticFieldAccessExpr.accessElements val staticElement = PsiStaticElement(fieldAccessBlocks, file) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt index a880162f..2f81b2ed 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt @@ -30,7 +30,8 @@ fun PsiParameter.getIterableClazz(useListParam: Boolean): PsiParentClass { val immediate = this.type as? PsiClassType val classType = immediate?.let { PsiClassTypeUtil.getPsiTypeByList(it, this.project, useListParam) } if (classType != null) { - return PsiParentClass(classType) + val convertOptional = PsiClassTypeUtil.convertOptionalType(classType, this.project) + return PsiParentClass(convertOptional) } return PsiParentClass(this.type) } diff --git a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt index 7ac21ba2..47f0b52e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/inspection/sql/visitor/SqlInspectionVisitor.kt @@ -132,6 +132,7 @@ class SqlInspectionVisitor( if (forItem != null) { val result = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks) if (result == null) { + // TODO Add an error message when the type of element used in the For directory is not a List type. errorHighlight(topElement, daoMethod, holder) return } From c16d9aa61dc1bbf112e0f87e1154f7961f552647 Mon Sep 17 00:00:00 2001 From: xterao Date: Thu, 8 May 2025 18:21:58 +0900 Subject: [PATCH 2/6] Add test case support for optional parameters in SQL document generation and completion --- .../intellij/complate/sql/SqlCompleteTest.kt | 27 ++++++++++ .../document/SqlSymbolDocumentTestCase.kt | 50 ++++++++++++++++++- .../inspection/sql/ParameterDefinedTest.kt | 10 ++++ .../doma/example/dao/EmployeeSummaryDao.java | 4 ++ .../doma/example/dao/SqlCompleteTestDao.java | 10 ++++ .../example/dao/document/DocumentTestDao.java | 7 +++ .../dao/inspection/TestDataCheckDao.java | 1 + .../java/doma/example/entity/Project.java | 4 ++ .../optionalDaoParameterFieldAccess.sql | 14 ++++++ .../completeOptionalByForItem.sql | 5 ++ .../completeOptionalDaoParam.sql | 2 + .../completeOptionalStaticProperty.sql | 2 + .../documentForItemOptionalForItem.sql | 18 +++++++ .../documentForItemOptionalProperty.sql | 18 +++++++ 14 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/optionalDaoParameterFieldAccess.sql create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalByForItem.sql create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalDaoParam.sql create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalStaticProperty.sql create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalForItem.sql create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalProperty.sql diff --git a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt index 5cc758ea..e6a5eafa 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt @@ -53,6 +53,9 @@ class SqlCompleteTest : DomaSqlTest() { "$testDapName/completeCallStaticPropertyClass.sql", "$testDapName/completeForItemHasNext.sql", "$testDapName/completeForItemIndex.sql", + "$testDapName/completeOptionalDaoParam.sql", + "$testDapName/completeOptionalStaticProperty.sql", + "$testDapName/completeOptionalByForItem.sql", ) myFixture.enableInspections(SqlBindVariableValidInspector()) } @@ -329,6 +332,30 @@ class SqlCompleteTest : DomaSqlTest() { ) } + fun testCompleteOptionalDaoParam() { + innerDirectiveCompleteTest( + "$testDapName/completeOptionalDaoParam.sql", + listOf("manager", "projectNumber", "getFirstEmployee()"), + listOf("get()", "orElseGet()", "isPresent()"), + ) + } + + fun testCompleteOptionalStaticProperty() { + innerDirectiveCompleteTest( + "$testDapName/completeOptionalStaticProperty.sql", + listOf("userId", "userName", "email", "getUserNameFormat()"), + listOf("get()", "orElseGet()", "isPresent()"), + ) + } + + fun testCompleteOptionalByForItem() { + innerDirectiveCompleteTest( + "$testDapName/completeOptionalByForItem.sql", + listOf("manager", "projectNumber", "getFirstEmployee()"), + listOf("get()", "orElseGet()", "isPresent()"), + ) + } + private fun innerDirectiveCompleteTest( sqlFileName: String, expectedSuggestions: List, diff --git a/src/test/kotlin/org/domaframework/doma/intellij/document/SqlSymbolDocumentTestCase.kt b/src/test/kotlin/org/domaframework/doma/intellij/document/SqlSymbolDocumentTestCase.kt index bfff1e99..a463232c 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/document/SqlSymbolDocumentTestCase.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/document/SqlSymbolDocumentTestCase.kt @@ -15,8 +15,12 @@ */ package org.domaframework.doma.intellij.document +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import org.domaframework.doma.intellij.DomaSqlTest +import org.domaframework.doma.intellij.psi.SqlBlockComment +import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr +import org.domaframework.doma.intellij.psi.SqlElForDirective import org.domaframework.doma.intellij.psi.SqlElIdExpr class SqlSymbolDocumentTestCase : DomaSqlTest() { @@ -37,6 +41,8 @@ class SqlSymbolDocumentTestCase : DomaSqlTest() { addSqlFile("$testPackage/$testDaoName/documentForItemStaticProperty.sql") addSqlFile("$testPackage/$testDaoName/documentForItemHasNext.sql") addSqlFile("$testPackage/$testDaoName/documentForItemIndex.sql") + addSqlFile("$testPackage/$testDaoName/documentForItemOptionalForItem.sql") + addSqlFile("$testPackage/$testDaoName/documentForItemOptionalProperty.sql") } fun testDocumentForItemDaoParam() { @@ -55,6 +61,22 @@ class SqlSymbolDocumentTestCase : DomaSqlTest() { documentationTest(sqlName, result) } + fun testDocumentForItemOptionalForItem() { + val sqlName = "documentForItemOptionalForItem" + val result = + "List<Project> optionalProjects" + + documentationTest(sqlName, result) + } + + fun testDocumentForItemOptionalForItemProperty() { + val sqlName = "documentForItemOptionalProperty" + val result = + "List<Integer> optionalIds" + + documentationFindTextTest(sqlName, "optionalIds", result) + } + fun testDocumentForItemElement() { val sqlName = "documentForItemElement" val result = @@ -146,8 +168,34 @@ class SqlSymbolDocumentTestCase : DomaSqlTest() { if (sqlFile == null) return myFixture.configureFromExistingVirtualFile(sqlFile) - var originalElement: PsiElement = myFixture.findElementByText(originalElementName, SqlElIdExpr::class.java) + var originalElement: PsiElement? = + myFixture.findElementByText(originalElementName, SqlElIdExpr::class.java) + ?: fundForDirectiveDeclarationElement(sqlFile, originalElementName) + assertNotNull("Not Found Element [$originalElementName]", originalElement) + if (originalElement == null) return + val resultDocument = myDocumentationProvider.generateDoc(originalElement, originalElement) assertEquals("Documentation should contain expected text", result, resultDocument) } + + private fun fundForDirectiveDeclarationElement( + sqlFile: VirtualFile, + searchElementName: String, + ): PsiElement? { + myFixture.configureFromExistingVirtualFile(sqlFile) + val topElement = myFixture.findElementByText(searchElementName, PsiElement::class.java) + val forDirectiveBlock = + topElement.children + .firstOrNull { it is SqlBlockComment && it.text.contains(searchElementName) } + + val forDirective = + forDirectiveBlock?.children?.find { it is SqlElForDirective } as? SqlElForDirective + ?: return null + val fieldAccessExpr = forDirective.elExprList[1] as? SqlElFieldAccessExpr + if (fieldAccessExpr == null) { + return forDirective.elExprList.firstOrNull { it.text == searchElementName } + } + + return fieldAccessExpr.elExprList.firstOrNull { it.text == searchElementName } + } } diff --git a/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt index 11979e54..817c55d1 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/inspection/sql/ParameterDefinedTest.kt @@ -38,6 +38,7 @@ class ParameterDefinedTest : DomaSqlTest() { "$testDaoName/bindVariableInFunctionParameters.sql", "$testDaoName/callStaticPropertyPackageName.sql", "$testDaoName/bindVariableForItemHasNextAndIndex.sql", + "$testDaoName/optionalDaoParameterFieldAccess.sql", ) myFixture.enableInspections(SqlBindVariableValidInspector()) } @@ -70,6 +71,15 @@ class ParameterDefinedTest : DomaSqlTest() { myFixture.testHighlighting(false, false, false, sqlFile) } + fun testOptionalDaoParameterFieldAccess() { + val sqlFile = + findSqlFile("$testDaoName/optionalDaoParameterFieldAccess.sql") + assertNotNull("Not Found SQL File", sqlFile) + if (sqlFile == null) return + + myFixture.testHighlighting(false, false, false, sqlFile) + } + fun testAccessStaticProperty() { val sqlFile = findSqlFile("$testDaoName/accessStaticProperty.sql") assertNotNull("Not Found SQL File", sqlFile) diff --git a/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java b/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java index 88337a9c..dd2c3fd5 100644 --- a/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/EmployeeSummaryDao.java @@ -5,6 +5,7 @@ import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.PreparedSql; import org.seasar.doma.jdbc.SelectOptions; +import java.util.*; import java.util.List; import java.util.function.BiFunction; @@ -35,4 +36,7 @@ interface EmployeeSummaryDao { @Select List bindVariableForItemHasNextAndIndex(List employees); + + @Select + Project optionalDaoParameterFieldAccess(Optional project, OptionalInt id, OptionalLong longId, OptionalDouble doubleId); } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java index 05f26160..976e0db6 100644 --- a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.function.BiFunction; +import java.util.Optional; @Dao interface SqlCompleteTestDao { @@ -81,4 +82,13 @@ interface SqlCompleteTestDao { @Select Principal completeForItemIndex(Principal principal); + @Select + Project completeOptionalDaoParam(Optional project); + + @Select + Project completeOptionalStaticProperty(); + + @Select + Project completeOptionalByForItem(List projects); + } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/document/DocumentTestDao.java b/src/test/testData/src/main/java/doma/example/dao/document/DocumentTestDao.java index 61913ea2..1e65fa1e 100644 --- a/src/test/testData/src/main/java/doma/example/dao/document/DocumentTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/document/DocumentTestDao.java @@ -4,6 +4,7 @@ import java.util.HashSet; import doma.example.entity.*; import org.seasar.doma.*; +import java.util.Optional; @Dao public interface DocumentTestDao { @@ -35,4 +36,10 @@ public interface DocumentTestDao { @Select int documentForItemHasNext(Principal principal); + @Select + Project documentForItemOptionalForItem(Optional>> optionalProjects); + + @Select + Project documentForItemOptionalProperty(Optional>> optionalProjects); + } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/dao/inspection/TestDataCheckDao.java b/src/test/testData/src/main/java/doma/example/dao/inspection/TestDataCheckDao.java index c278df79..78f31c31 100644 --- a/src/test/testData/src/main/java/doma/example/dao/inspection/TestDataCheckDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/inspection/TestDataCheckDao.java @@ -29,4 +29,5 @@ interface TestDataCheckDao { @Insert(sqlFile=true) int invalidTestData(Employee employee); + } \ No newline at end of file diff --git a/src/test/testData/src/main/java/doma/example/entity/Project.java b/src/test/testData/src/main/java/doma/example/entity/Project.java index 674d6247..1779e20d 100644 --- a/src/test/testData/src/main/java/doma/example/entity/Project.java +++ b/src/test/testData/src/main/java/doma/example/entity/Project.java @@ -5,6 +5,7 @@ import org.seasar.doma.Id; import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Entity public class Project { @@ -15,6 +16,9 @@ public class Project { private static String status; private Integer rank; + public static Optional>> optionalIds; + public static Optional manager; + // Accessible static fields public static Integr projectNumber; private static String projectCategory; diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/optionalDaoParameterFieldAccess.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/optionalDaoParameterFieldAccess.sql new file mode 100644 index 00000000..cf003f43 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/EmployeeSummaryDao/optionalDaoParameterFieldAccess.sql @@ -0,0 +1,14 @@ +select * from projects +-- project : Optional -> Project +where id = /* project.projectId */0 + and project_name = /* project.flatMap() */'projectName' + and sa_number = /* id.flatMap() */0 + and number = /* id.MAX_VALUE */0 + and as_long = /* longId.getAsLong() */0 + and long = /* longId.doubleValue() */0 + and as_double = /* doubleId.getAsDouble() */0 + and doubles = /* longId.doubleValue() */0 + -- optId : Optional -> Integer + /*%for optId : project.optionalIds */ + OR sub_id = /* optId.MAX_VALUE */0 + /*%end */ \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalByForItem.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalByForItem.sql new file mode 100644 index 00000000..c3829b2d --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalByForItem.sql @@ -0,0 +1,5 @@ +select * from project + where + /*%for project : projects */ + or user_id = /* project. */0 + /*%end */ \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalDaoParam.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalDaoParam.sql new file mode 100644 index 00000000..8313a30c --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalDaoParam.sql @@ -0,0 +1,2 @@ +select * from project + where id = /* project. */0 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalStaticProperty.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalStaticProperty.sql new file mode 100644 index 00000000..9dffef0b --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalStaticProperty.sql @@ -0,0 +1,2 @@ +select * from project + where id = /* @doma.example.entity.Project@manager. */0 \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalForItem.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalForItem.sql new file mode 100644 index 00000000..761069bd --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalForItem.sql @@ -0,0 +1,18 @@ +select * from project +where + -- optionalProjects : Optional>> -> List + -- project : Optional -> Project + /*%for project : optionalProjects */ + -- project.optionalIds : Optional>> -> List + -- id : Optional -> Integer + /*%for id : project.optionalIds */ + project_next_id = /* id */0 + /*%if id_has_next */ + /*# "OR" */ + /*%end */ + /*%end */ + project_id = /* project.projectId */0 + /*%if project_has_next */ + /*# "OR" */ + /*%end */ + /*%end */ \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalProperty.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalProperty.sql new file mode 100644 index 00000000..4981064a --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/document/DocumentTestDao/documentForItemOptionalProperty.sql @@ -0,0 +1,18 @@ +select * from project +where + -- optionalProjects : Optional>> -> List + -- project : Optional -> Project + /*%for project : optionalProjects */ + -- project.optionalIds : Optional>> -> List + -- id : Optional -> Integer + /*%for id : project.optionalIds */ + project_next_id = /* id */0 + /*%if id_has_next */ + /*# "OR" */ + /*%end */ + /*%end */ + project_id = /* project.projectId */0 + /*%if project_has_next */ + /*# "OR" */ + /*%end */ + /*%end */ \ No newline at end of file From 6cb622a80ea70de5b3a37ce87c3db0a70b5a3206 Mon Sep 17 00:00:00 2001 From: xterao <72187180+xterao@users.noreply.github.com> Date: Thu, 8 May 2025 18:29:33 +0900 Subject: [PATCH 3/6] Add a comment to the Optional type conversion branching process. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../doma/intellij/common/sql/PsiClassTypeUtil.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt index 443a7a1e..cb2da97e 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/common/sql/PsiClassTypeUtil.kt @@ -86,9 +86,13 @@ class PsiClassTypeUtil { ) if (resolved != null) { when (resolved.qualifiedName) { + // If the type is java.util.Optional, return its parameter type if available; + // otherwise, return the original daoParamType. "java.util.Optional" -> return daoParamType.parameters.firstOrNull() ?: daoParamType + // For primitive Optional types (e.g., OptionalInt, OptionalDouble), + // map them to their corresponding wrapper types (e.g., Integer, Double). else -> optionalTypeMap[resolved.qualifiedName]?.let { optionalType -> val newType = From 32b2eea936a9d63f13fb2181ddb61db4fe5c6721 Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 9 May 2025 10:43:33 +0900 Subject: [PATCH 4/6] Support for Dao methods of Batch annotations --- .../SqlParameterCompletionProvider.kt | 104 +++++++++++------- .../intellij/complate/sql/SqlCompleteTest.kt | 9 ++ .../doma/example/dao/SqlCompleteTestDao.java | 3 + .../completeOptionalBatchAnnotation.sql | 11 ++ 4 files changed, 89 insertions(+), 38 deletions(-) create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalBatchAnnotation.sql diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index 133c2c65..7d9d2f7c 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -20,6 +20,7 @@ import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.VariableLookupItem +import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement @@ -33,6 +34,7 @@ import com.intellij.psi.util.elementType import com.intellij.psi.util.prevLeafs import com.intellij.util.ProcessingContext import org.domaframework.doma.intellij.common.dao.findDaoMethod +import org.domaframework.doma.intellij.common.psi.PsiDaoMethod import org.domaframework.doma.intellij.common.psi.PsiParentClass import org.domaframework.doma.intellij.common.psi.PsiPatternUtil import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil @@ -271,20 +273,22 @@ class SqlParameterCompletionProvider : CompletionProvider( originalFile: PsiFile, result: CompletionResultSet, ) { + val daoMethod = findDaoMethod(originalFile) val searchText = cleanString(getSearchElementText(position)) var topElementType: PsiType? = null - if (elements.isEmpty()) { - getElementTypeByFieldAccess(originalFile, elements, result) + if (elements.isEmpty() && daoMethod != null) { + getElementTypeByFieldAccess(originalFile, elements, daoMethod, result) return } val top = elements.first() - val topText = cleanString(getSearchElementText(top)) val prevWord = PsiPatternUtil.getBindSearchWord(originalFile, elements.last(), " ") if (prevWord.startsWith("@") && prevWord.endsWith("@")) { - setStaticFieldAccess(top, prevWord, topText, result) + setCompletionStaticFieldAccess(top, prevWord, topText, result) return } + + var isBatchAnnotation = false if (top.parent !is PsiFile && top.parent?.parent !is PsiDirectory) { val staticDirective = top.findNodeParent(SqlTypes.EL_STATIC_FIELD_ACCESS_EXPR) staticDirective?.let { @@ -292,45 +296,26 @@ class SqlParameterCompletionProvider : CompletionProvider( } } + if (daoMethod == null) return + val project = originalFile.project + val psiDaoMethod = PsiDaoMethod(project, daoMethod) if (topElementType == null) { - if (isFieldAccessByForItem(top, elements, searchText, result)) return + isBatchAnnotation = psiDaoMethod.daoType.isBatchAnnotation() + if (isFieldAccessByForItem(top, elements, searchText, isBatchAnnotation, result)) return topElementType = - getElementTypeByFieldAccess(originalFile, elements, result) ?: return + getElementTypeByFieldAccess(originalFile, elements, daoMethod, result) ?: return } - var psiParentClass = PsiParentClass(topElementType) - // FieldAccess Completion - ForDirectiveUtil.getFieldAccessLastPropertyClassType( + setCompletionFieldAccess( + topElementType, + originalFile.project, + isBatchAnnotation, elements, - top.project, - psiParentClass, - shortName = "", - dropLastIndex = 1, - complete = { lastType -> - val searchWord = cleanString(getSearchElementText(position)) - setFieldsAndMethodsCompletionResultSet( - lastType.searchField(searchWord)?.toTypedArray() ?: emptyArray(), - lastType.searchMethod(searchWord)?.toTypedArray() ?: emptyArray(), - result, - ) - }, + searchText, + result, ) } - private fun setStaticFieldAccess( - top: PsiElement, - prevWord: String, - topText: String, - result: CompletionResultSet, - ) { - val clazz = getRefClazz(top) { prevWord.replace("@", "") } ?: return - val matchFields = clazz.searchStaticField(topText) - val matchMethod = clazz.searchStaticMethod(topText) - - // When you enter here, it is the top element, so return static fields and methods. - setFieldsAndMethodsCompletionResultSet(matchFields, matchMethod, result) - } - private fun getSearchElementText(elm: PsiElement?): String = if (elm is SqlElIdExpr || elm.elementType == SqlTypes.EL_IDENTIFIER) { elm?.text ?: "" @@ -369,9 +354,9 @@ class SqlParameterCompletionProvider : CompletionProvider( private fun getElementTypeByFieldAccess( originalFile: PsiFile, elements: List, + daoMethod: PsiMethod, result: CompletionResultSet, ): PsiType? { - val daoMethod = findDaoMethod(originalFile) ?: return null val topText = cleanString(getSearchElementText(elements.firstOrNull())) val matchParams = daoMethod.searchParameter(topText) val findParam = matchParams.find { it.name == topText } @@ -417,10 +402,10 @@ class SqlParameterCompletionProvider : CompletionProvider( private fun isFieldAccessByForItem( top: PsiElement, elements: List, - positionText: String, + searchWord: String, + isBatchAnnotation: Boolean = false, result: CompletionResultSet, ): Boolean { - val searchWord = cleanString(positionText) val project = top.project val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(top) ForDirectiveUtil.findForItem(top, forDirectives = forDirectiveBlocks) ?: return false @@ -439,6 +424,7 @@ class SqlParameterCompletionProvider : CompletionProvider( elements, project, topClassType, + isBatchAnnotation = isBatchAnnotation, shortName = "", dropLastIndex = 1, complete = { lastType -> @@ -451,4 +437,46 @@ class SqlParameterCompletionProvider : CompletionProvider( ) return result is ValidationCompleteResult } + + private fun setCompletionFieldAccess( + topElementType: PsiType, + project: Project, + isBatchAnnotation: Boolean, + elements: List, + searchWord: String, + result: CompletionResultSet, + ) { + var psiParentClass = PsiParentClass(topElementType) + + // FieldAccess Completion + ForDirectiveUtil.getFieldAccessLastPropertyClassType( + elements, + project, + psiParentClass, + isBatchAnnotation = isBatchAnnotation, + shortName = "", + dropLastIndex = 1, + complete = { lastType -> + setFieldsAndMethodsCompletionResultSet( + lastType.searchField(searchWord)?.toTypedArray() ?: emptyArray(), + lastType.searchMethod(searchWord)?.toTypedArray() ?: emptyArray(), + result, + ) + }, + ) + } + + private fun setCompletionStaticFieldAccess( + top: PsiElement, + prevWord: String, + topText: String, + result: CompletionResultSet, + ) { + val clazz = getRefClazz(top) { prevWord.replace("@", "") } ?: return + val matchFields = clazz.searchStaticField(topText) + val matchMethod = clazz.searchStaticMethod(topText) + + // When you enter here, it is the top element, so return static fields and methods. + setFieldsAndMethodsCompletionResultSet(matchFields, matchMethod, result) + } } diff --git a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt index e6a5eafa..d45fe4cf 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt @@ -56,6 +56,7 @@ class SqlCompleteTest : DomaSqlTest() { "$testDapName/completeOptionalDaoParam.sql", "$testDapName/completeOptionalStaticProperty.sql", "$testDapName/completeOptionalByForItem.sql", + "$testDapName/completeOptionalBatchAnnotation.sql", ) myFixture.enableInspections(SqlBindVariableValidInspector()) } @@ -356,6 +357,14 @@ class SqlCompleteTest : DomaSqlTest() { ) } + fun testCompleteOptionalBatchAnnotation() { + innerDirectiveCompleteTest( + "$testDapName/completeOptionalBatchAnnotation.sql", + listOf("optionalIds"), + listOf("get()", "orElseGet()", "isPresent()", "projectId"), + ) + } + private fun innerDirectiveCompleteTest( sqlFileName: String, expectedSuggestions: List, diff --git a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java index 976e0db6..c27bc94a 100644 --- a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java @@ -91,4 +91,7 @@ interface SqlCompleteTestDao { @Select Project completeOptionalByForItem(List projects); + @BatchDelete(sqlFile = true) + int completeOptionalBatchAnnotation(Optional>> projects); + } \ No newline at end of file diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalBatchAnnotation.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalBatchAnnotation.sql new file mode 100644 index 00000000..ce4c37f4 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeOptionalBatchAnnotation.sql @@ -0,0 +1,11 @@ +DELETE FROM project + WHERE rank > 10 + -- projects : Optional>> -> Project + AND id = /* projects.projectId */ + -- projects.optionalIds : Optional>> -> List +/*%for id : projects.option */ + opt_id = /* id */0 + /*%if id_has_next */ + /*# "OR" */ + /*%end */ +/*%end */ From 329df8734e11054bdbdcdf24bbb4caa18a9f20cb Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 9 May 2025 10:57:49 +0900 Subject: [PATCH 5/6] Update README to clarify handling of Optional types in code completion --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 66b00eac..70435264 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ The plugin also provides quick fixes for Dao methods where the required SQL file ![inspection.png](images/inspection.png) - Check the class name and package name for static property calls ![inspectionPackageName.png](images/inspectionPackageName.png) +- Optional types are recognized as their element type (e.g. Optional is treated as String). ## Completion Adds code completion functionality to support indexing of Doma directives and bind variables @@ -52,6 +53,7 @@ Adds code completion functionality to support indexing of Doma directives and bi - Suggest Doma directives - Directives such as Condition, Loop, Population are suggested after “%” - Suggest built-in functions after “@” +- Optional types are recognized as their element type (e.g. Optional is treated as String). ## Refactoring Along with the Dao name change, we will refactor the SQL file directory and file name. From 053c7a4cb815400b76d1440f608cbbe32eb7121f Mon Sep 17 00:00:00 2001 From: xterao Date: Fri, 9 May 2025 13:48:24 +0900 Subject: [PATCH 6/6] Add support for batch annotation with SQL completion --- .../provider/SqlParameterCompletionProvider.kt | 6 ++---- .../extension/psi/PsiParameterExtension.kt | 18 ------------------ .../intellij/complate/sql/SqlCompleteTest.kt | 16 ++++++++++++++++ .../doma/example/dao/SqlCompleteTestDao.java | 3 +++ .../SqlCompleteTestDao/completeBatchInsert.sql | 5 +++++ 5 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeBatchInsert.sql diff --git a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt index 7d9d2f7c..55a8b2cc 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt @@ -48,8 +48,6 @@ import org.domaframework.doma.intellij.extension.psi.findNodeParent import org.domaframework.doma.intellij.extension.psi.findSelfBlocks import org.domaframework.doma.intellij.extension.psi.findStaticField import org.domaframework.doma.intellij.extension.psi.findStaticMethod -import org.domaframework.doma.intellij.extension.psi.getDomaAnnotationType -import org.domaframework.doma.intellij.extension.psi.getIterableClazz import org.domaframework.doma.intellij.extension.psi.isNotWhiteSpace import org.domaframework.doma.intellij.extension.psi.searchParameter import org.domaframework.doma.intellij.extension.psi.searchStaticField @@ -370,8 +368,8 @@ class SqlParameterCompletionProvider : CompletionProvider( if (findParam == null) { return null } - val immediate = findParam.getIterableClazz(daoMethod.getDomaAnnotationType()) - return PsiClassTypeUtil.convertOptionalType(immediate.type, originalFile.project) + val immediate = findParam.type + return PsiClassTypeUtil.convertOptionalType(immediate, originalFile.project) } private fun getRefClazz( diff --git a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt index 2f81b2ed..db2ba96d 100644 --- a/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt +++ b/src/main/kotlin/org/domaframework/doma/intellij/extension/psi/PsiParameterExtension.kt @@ -17,24 +17,6 @@ package org.domaframework.doma.intellij.extension.psi import com.intellij.psi.PsiClassType import com.intellij.psi.PsiParameter -import org.domaframework.doma.intellij.common.psi.PsiParentClass -import org.domaframework.doma.intellij.common.sql.PsiClassTypeUtil - -/** - * For List type, if the annotation type is Batch type, - * return the content type. - */ -fun PsiParameter.getIterableClazz(annotationType: DomaAnnotationType): PsiParentClass = getIterableClazz(annotationType.isBatchAnnotation()) - -fun PsiParameter.getIterableClazz(useListParam: Boolean): PsiParentClass { - val immediate = this.type as? PsiClassType - val classType = immediate?.let { PsiClassTypeUtil.getPsiTypeByList(it, this.project, useListParam) } - if (classType != null) { - val convertOptional = PsiClassTypeUtil.convertOptionalType(classType, this.project) - return PsiParentClass(convertOptional) - } - return PsiParentClass(this.type) -} val PsiParameter.isFunctionClazz: Boolean get() = diff --git a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt index d45fe4cf..cc9463e3 100644 --- a/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt +++ b/src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt @@ -34,6 +34,7 @@ class SqlCompleteTest : DomaSqlTest() { "$testDapName/completeInstancePropertyFromDaoArgumentClass.sql", "$testDapName/completeJavaPackageClass.sql", "$testDapName/completeDirective.sql", + "$testDapName/completeBatchInsert.sql", "$testDapName/completeStaticPropertyFromStaticPropertyCall.sql", "$testDapName/completePropertyAfterStaticPropertyCall.sql", "$testDapName/completeBuiltinFunction.sql", @@ -161,6 +162,21 @@ class SqlCompleteTest : DomaSqlTest() { ) } + fun testCompleteBatchInsert() { + innerDirectiveCompleteTest( + "$testDapName/completeBatchInsert.sql", + listOf( + "employeeId", + "employeeName", + ), + listOf( + "userId", + "get()", + "size()", + ), + ) + } + fun testCompleteStaticPropertyFromStaticPropertyCall() { innerDirectiveCompleteTest( "$testDapName/completeStaticPropertyFromStaticPropertyCall.sql", diff --git a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java index c27bc94a..d7330bd9 100644 --- a/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java +++ b/src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java @@ -25,6 +25,9 @@ interface SqlCompleteTestDao { @Update(sqlFile = true) int completeDirective(Employee employee); + @BatchInsert(sqlFile = true) + int completeBatchInsert(List employees); + @Select Project completeStaticPropertyFromStaticPropertyCall(ProjectDetail detail); diff --git a/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeBatchInsert.sql b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeBatchInsert.sql new file mode 100644 index 00000000..1f82a2f9 --- /dev/null +++ b/src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completeBatchInsert.sql @@ -0,0 +1,5 @@ +INSERT INTO employee + (id + , name) + VALUES ( /* employees.userId */0 + , /* employees.emplo */'name')