@ElementCollection of @Embeddable that contains @DbArray - NoSuchElementException when empty collection passed (via MultiValueWrapper constructor)
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.
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)
}
}
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.
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)
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.
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