Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@ElementCollection of @Embeddable that contains @DbArray - NoSuchElementException when empty collection passed (via MultiValueWrapper constructor) #2477

Open
npokr opened this issue Jan 2, 2022 · 4 comments

Comments

@npokr
Copy link

npokr commented Jan 2, 2022

Expected behavior

test passed

Actual behavior

java.util.NoSuchElementException
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1000)
at io.ebeaninternal.server.persist.MultiValueWrapper.(MultiValueWrapper.java:20)
at io.ebeaninternal.api.BindParams.setParameter(BindParams.java:173)
at io.ebeaninternal.server.core.DefaultSqlUpdate.setParameter(DefaultSqlUpdate.java:313)
at io.ebeaninternal.server.core.DefaultSqlUpdate.setParameter(DefaultSqlUpdate.java:284)
at io.ebeaninternal.server.deploy.BeanDescriptor.bindElementValue(BeanDescriptor.java:733)
at io.ebeaninternal.server.deploy.BeanDescriptorElementEmbedded.bindElementValue(BeanDescriptorElementEmbedded.java:55)
at io.ebeaninternal.server.deploy.BeanPropertyAssocMany.bindElementValue(BeanPropertyAssocMany.java:1029)
at io.ebeaninternal.server.persist.SaveManyElementCollection.saveCollection(SaveManyElementCollection.java:50)
at io.ebeaninternal.server.persist.SaveManyElementCollection.save(SaveManyElementCollection.java:34)
at io.ebeaninternal.server.persist.DefaultPersister.saveMany(DefaultPersister.java:882)
at io.ebeaninternal.server.persist.DefaultPersister.saveAssocMany(DefaultPersister.java:876)
at io.ebeaninternal.server.persist.DefaultPersister.update(DefaultPersister.java:503)
at io.ebeaninternal.server.persist.DefaultPersister.update(DefaultPersister.java:381)
at io.ebeaninternal.server.core.DefaultServer.update(DefaultServer.java:1650)
at io.ebeaninternal.server.core.DefaultServer.update(DefaultServer.java:1645)
at io.ebean.DB.update(DB.java:438)

Steps to reproduce

Insert empty list into @DBArray postresql column in @ElementCollection detail.
Entities:

@Entity
class TestArrayMaster(
    @ElementCollection
    @CollectionTable(name = "test_array_detail", joinColumns = [JoinColumn(name = "master_id")])
    var details: MutableList<TestArrayDetail> = mutableListOf()
) {
    @Id
    var id = 0
}

@Embeddable
class TestArrayDetail(
    @DbArray
    val vals: List<String>?
)

SQL:

CREATE TABLE test_array_master(
    id serial PRIMARY KEY
);

CREATE TABLE test_array_detail (
    master_id int REFERENCES test_array_master ON DELETE CASCADE,
    vals text[]
);

test:

   @Test
    fun testArrayInsert() {
        val t = TestArrayMaster().apply {
            details = mutableListOf(TestArrayDetail(mutableListOf()))
            DB.insert(this)
        }
    }
@rbygrave
Copy link
Member

The stack trace says at io.ebean.DB.update(DB.java:438) BUT ... the test code performs an insert via DB.insert(this) ?

So there is a disconnect here. It probably would be best to provide an explicit and complete failing test case in Java.

@npokr
Copy link
Author

npokr commented Jan 14, 2022

OK, in Java entities look like

package ru.nn;

import io.ebean.annotation.DbArray;

import javax.persistence.*;
import java.util.List;

@Entity
public class TestArrayMaster {
    @Embeddable
    public static class TestArrayDetail {
        @DbArray
        List<String> vals;

        public TestArrayDetail(List<String> vals) {
            this.vals = vals;
        }
    }

    @Id
    int id = 0;

    @ElementCollection
    @CollectionTable(name = "test_array_detail", joinColumns = {@JoinColumn(name = "master_id")})
    List<TestArrayDetail> details;

    public TestArrayMaster(List<TestArrayDetail> details) {
        this.details = details;
    }
}

and test class looks like

package ru.nn;

import io.ebean.DB;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.Collections;

@SpringBootTest
class TestArrayMasterTest {
    @Test
    void testArrayInsert() {
        var t = new TestArrayMaster(Arrays.asList(new TestArrayMaster.TestArrayDetail(Collections.emptyList())));
        DB.insert(t);
    }
}

Full stack trace is

java.util.NoSuchElementException
	at java.base/java.util.Collections$EmptyIterator.next(Collections.java:4210)
	at io.ebeaninternal.server.persist.MultiValueWrapper.<init>(MultiValueWrapper.java:20)
	at io.ebeaninternal.api.BindParams.setParameter(BindParams.java:173)
	at io.ebeaninternal.server.core.DefaultSqlUpdate.setParameter(DefaultSqlUpdate.java:313)
	at io.ebeaninternal.server.core.DefaultSqlUpdate.setParameter(DefaultSqlUpdate.java:284)
	at io.ebeaninternal.server.deploy.BeanDescriptor.bindElementValue(BeanDescriptor.java:733)
	at io.ebeaninternal.server.deploy.BeanDescriptorElementEmbedded.bindElementValue(BeanDescriptorElementEmbedded.java:55)
	at io.ebeaninternal.server.deploy.BeanPropertyAssocMany.bindElementValue(BeanPropertyAssocMany.java:1029)
	at io.ebeaninternal.server.persist.SaveManyElementCollection.saveCollection(SaveManyElementCollection.java:50)
	at io.ebeaninternal.server.persist.SaveManyElementCollection.save(SaveManyElementCollection.java:34)
	at io.ebeaninternal.server.persist.DefaultPersister.saveMany(DefaultPersister.java:882)
	at io.ebeaninternal.server.persist.DefaultPersister.saveAssocMany(DefaultPersister.java:876)
	at io.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:473)
	at io.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:419)
	at io.ebeaninternal.server.core.DefaultServer.insert(DefaultServer.java:1687)
	at io.ebeaninternal.server.core.DefaultServer.insert(DefaultServer.java:1679)
	at io.ebean.DB.insert(DB.java:381)
	at ru.nn.TestArrayMasterTest.testArrayInsert(TestArrayMasterTest.java:15)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

@rbygrave rbygrave changed the title MultiValueWrapper constructor throws NoSuchElementException when empty collection passed @Embeddable with @DbArray - NoSuchElementException when empty collection passed (via MultiValueWrapper constructor) Jan 20, 2022
@rbygrave rbygrave changed the title @Embeddable with @DbArray - NoSuchElementException when empty collection passed (via MultiValueWrapper constructor) @ElementCollection of @Embeddable that contains @DbArray - NoSuchElementException when empty collection passed (via MultiValueWrapper constructor) Jan 20, 2022
@rbygrave
Copy link
Member

This issue is specific to @ElementCollection of @Embeddable that contains @DbArray. The issue internally is that element collections don't use our BeanDescriptor based insert, update, delete persister but instead are SqlUpdate based - so they make good use of the internal ScalarType ... which has the nice cross database platform handling of @DbArray.

That is, before fixing this we probably need to try to adjust the internals such that element collection persisting uses BeanDescriptor based insert, update, delete persister.

@AntoineDuComptoirDesPharmacies

Hi,

Same issue using DB.sqlUpdate and setArrayParameter (which use MulValueWrapper under the hood).
For example :

public class TableName {
[...]
    @DbArray
    @Column(nullable = false)
    private Set<Enum> columnName;
[...]
String updateStr = "UPDATE table_name SET column_name=:column_name WHERE id=:id"
SqlUpdate update = DB.sqlUpdate(updateStr);
update.setArrayParameter("column_name", Collections.emptySet());

Our current workaround :

String updateStr = "UPDATE table_name SET column_name=ARRAY[:column_name]::varchar[] WHERE id=:id"
SqlUpdate update = DB.sqlUpdate(updateStr);
update.setParameter("column_name", Collections.emptySet());

Yours faithfully,
LCDP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants