Skip to content

Add possibility to mark @Repository param as simple #155

Open
mitasov-ra opened this issue Jun 14, 2023 · 6 comments
Open

Add possibility to mark @Repository param as simple #155

mitasov-ra opened this issue Jun 14, 2023 · 6 comments
Assignees
Labels
module: database Related to module - database

Comments

@mitasov-ra
Copy link
Contributor

mitasov-ra commented Jun 14, 2023

I have table test with JSONB field data.

The following code works perfectly fine. Repository uses JdbcParameterColumnMapper<Test.Data> to map Test.Data to JSON string.

public record Test(Data data) {
  public record Data(String foo) {}
}

@Repository
public interface TestRepo extends JdbcRepository {
  @Query("""
    INSERT INTO test (data)
    VALUES (:test.data::jsonb)
    """)
  void insert(Test data);
}

@Component
public class TestDataJdbcParameterColumnMapper implements
    JdbcParameterColumnMapper<Test.Data> {

  private final JsonWriter<Test.Data> writer;

  public TestDataJdbcParameterColumnMapper(JsonWriter<Test.Data> writer) {
    this.writer = writer;
  }

  @Override
  public void set(PreparedStatement stmt, int index, @Nullable Test.Data value)
      throws SQLException {

    try {
      stmt.setString(index, new String(writer.toByteArray(value), StandardCharsets.UTF_8));
    } catch (IOException e) {
      throw new SQLException(e);
    }
  }
}

But when I add the following method:

@Query("""
UPDATE test
      SET data = :data::jsonb
""")
void replaceData(Test.Data data);

Kora treats Data object as EntityParameter and shows the error:

Parameter usage was not found in query: data

I tried to add @Mapping(TestDataJdbcParameterColumnMapper.class) to the data parameter, but it didn't help.

Seems like there's no way to tell Kora that Data should be processed as SimpleParameter. It would be nice to add such annotation.

@mitasov-ra
Copy link
Contributor Author

mitasov-ra commented Jun 14, 2023

The working workaround for me:

public interface TestRepo extends JdbcRepository {
  @Query("""
    INSERT INTO test (data)
    VALUES (:test.data::jsonb)
    """)
  void insert(Test data);

  void replaceData(Test.Data data);
}

@Repository
public abstract class ATestRepo implements TestRepo {

  private final JsonWriter<Test.Data> writer;

  public TestRepo(JsonWriter<Test.Data> writer) {
    this.writer = writer;
  }
  
  @Query("""
      UPDATE test
      SET data = :data::jsonb
      """)
  public abstract void replaceDataInternal(String data);
  
  @Override
  public void replaceData(Test.Data data) {
    replaceDataInternal(new String(writer.toByteArray(data), StandardCharsets.UTF_8))
  }
}

@GoodforGod
Copy link
Contributor

GoodforGod commented Jul 10, 2023

Given such classes:

@KoraApp
interface MyApp : JdbcDatabaseModule, ConfigModule, JsonCommonModule {

    class MyRoot(val myRepo: MyRepo) : Lifecycle {
        override fun init(): Mono<Void> {
            return Mono.empty()
        }

        override fun release(): Mono<Void> {
            return Mono.empty()
        }
    }

    @Root
    fun myRoot(myRepo: MyRepo): MyRoot {
        return MyRoot(myRepo)
    }

    @Tag(Json::class)
    fun <T> myMapper(writer: JsonWriter<T>) : JdbcParameterColumnMapper<T> {
        return JdbcParameterColumnMapper { stmt, index, value ->
            try {
                stmt.setString(index, String(writer.toByteArray(value), StandardCharsets.UTF_8))
            } catch (e: IOException) {
                throw RuntimeException(e)
            }
        }
    }
}

@Repository
interface MyRepo : JdbcRepository {

    @Json
    data class Foo(@Tag(Json::class) val bar: Bar) {
        @Json
        data class Bar(val s: String)
    }

    @Query(
        """
      INSERT INTO foo (bar)
      VALUES
      (:foo.bar::jsonb)
    """
    )
    fun insert(foo: Foo)

    @Query(
        """
      UPDATE foo
      SET bar = :bar::jsonb
    """
    )
    fun updateBar(@Tag(Json::class) bar: Foo.Bar)
}

Everything works as expected, can you confirm @mitasov-ra this on your side and I will close issue as resolved?

@GoodforGod
Copy link
Contributor

Issue with NPE is not reproducible in 0.12.0

@GoodforGod
Copy link
Contributor

@mitasov-ra any progress on testing this out?

@mitasov-ra
Copy link
Contributor Author

Atm I'm waiting for new release with fix for #187

@mitasov-ra
Copy link
Contributor Author

Just've checked, NPE from #166 still reproduces if I add @Tag(Json::class) on any data class property.

e: [ksp] java.lang.NullPointerException
	at ru.tinkoff.kora.ksp.common.FieldFactory.get(FieldFactory.kt:26)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.StatementSetterGenerator.generate(StatementSetterGenerator.kt:96)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.JdbcRepositoryGenerator.generate(JdbcRepositoryGenerator.kt:87)
	at ru.tinkoff.kora.database.symbol.processor.jdbc.JdbcRepositoryGenerator.generate(JdbcRepositoryGenerator.kt:54)
	at ru.tinkoff.kora.database.symbol.processor.RepositoryBuilder.build(RepositoryBuilder.kt:50)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.processClass(RepositorySymbolProcessor.kt:45)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.access$processClass(RepositorySymbolProcessor.kt:15)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor$processRound$1.invoke(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor$processRound$1.invoke(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.ksp.common.KspCommonUtilsKt$visitClass$1.visitClassDeclaration(KspCommonUtils.kt:210)
	at com.google.devtools.ksp.symbol.impl.kotlin.KSClassDeclarationImpl.accept(KSClassDeclarationImpl.kt:136)
	at ru.tinkoff.kora.ksp.common.KspCommonUtilsKt.visitClass(KspCommonUtils.kt:206)
	at ru.tinkoff.kora.database.symbol.processor.RepositorySymbolProcessor.processRound(RepositorySymbolProcessor.kt:26)
	at ru.tinkoff.kora.ksp.common.BaseSymbolProcessor.process(BaseSymbolProcessor.kt:87)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:291)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:289)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:394)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:289)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:257)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:42)
	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:248)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:88)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:168)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1486)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

Versions

JVM: corretto-17.0.6
Kotlin: 1.8.22
KSP: 1.8.22-1.0.11
Kora: 1.13.1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
module: database Related to module - database
Projects
None yet
Development

No branches or pull requests

2 participants