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

AbstractArgumentFactory fails to match generic types in 2.25.0 #2026

Closed
peteruhnak opened this issue May 13, 2022 · 2 comments · Fixed by #2117
Closed

AbstractArgumentFactory fails to match generic types in 2.25.0 #2026

peteruhnak opened this issue May 13, 2022 · 2 comments · Fixed by #2117

Comments

@peteruhnak
Copy link

I am upgrading from jdbi 2.78 to 3.25.0, but it seems the new AbstractArgumentFactory no longer matches when provided with generic type.

I saw this ticket, but maybe it regressed again? #1914

Works in 2.78

package org.example.jdbi;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.Argument;
import org.skife.jdbi.v2.tweak.ArgumentFactory;

import static org.assertj.core.api.Assertions.assertThat;

class GenericArgumentFactoryTest {

    private DBI jdbi;

    @BeforeEach
    public void setUp() {
        this.jdbi = new DBI("jdbc:mysql://localhost:3306/test_dev");
    }

    @Test
    public void testBuild() {
        this.jdbi.registerArgumentFactory(new ThingGenericArgumentFactory());
        final String actual = this.jdbi.withHandle(
                h -> h.createQuery("SELECT CAST(:thing AS CHAR)")
                      .bind("thing", new StringThing("someKey"))
                      .mapTo(String.class)
                      .first());
        assertThat(actual).isEqualTo("someKey");
    }

    public static class ThingGenericArgumentFactory implements ArgumentFactory<Thing<String>> {

        @Override
        public boolean accepts(final Class<?> expectedType, final Object value, final StatementContext ctx) {
            return value instanceof Thing;
        }

        @Override
        public Argument build(final Class<?> expectedType, final Thing<String> value, final StatementContext ctx) {
            return ((position, statement, ctx1) -> statement.setString(position, value.getKey()));
        }
    }

    public interface Thing<T> {
        T getKey();
    }

    public static class StringThing implements Thing<String> {

        private final String key;

        public StringThing(final String key) {
            this.key = key;
        }

        @Override
        public String getKey() {
            return this.key;
        }
    }

}

Fails in jdbi 3.25.0

package org.example.jdbi;

import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.argument.AbstractArgumentFactory;
import org.jdbi.v3.core.argument.Argument;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.sql.Types;

import static org.assertj.core.api.Assertions.assertThat;

class GenericArgumentFactoryTest {

    private Jdbi jdbi;

    @BeforeEach
    public void setUp() {
        this.jdbi = Jdbi.create("jdbc:mysql://localhost:3306/test_dev");
    }

    @Test
    public void testBuild() {
        this.jdbi.registerArgument(new ThingGenericArgumentFactory());
        final String actual = this.jdbi.withHandle(
                h -> h.createQuery("SELECT CAST(:thing AS CHAR)")
                      .bind("thing", new StringThing("someKey"))
                      .mapTo(String.class)
                      .first());
        assertThat(actual).isEqualTo("someKey");
    }

    public static class ThingGenericArgumentFactory extends AbstractArgumentFactory<Thing<String>> {

        protected ThingGenericArgumentFactory() {
            super(Types.VARCHAR);
        }

        @Override
        protected Argument build(final Thing<String> value, final ConfigRegistry config) {
            return ((position, statement, ctx1) -> statement.setString(position, value.getKey()));
        }
    }

    public interface Thing<T> {
        T getKey();
    }

    public static class StringThing implements Thing<String> {

        private final String key;

        public StringThing(final String key) {
            this.key = key;
        }

        @Override
        public String getKey() {
            return this.key;
        }
    }

}

with the following error

org.jdbi.v3.core.statement.UnableToCreateStatementException: 
No argument factory registered for 'org.example.jdbi.GenericArgumentFactoryTest$StringThing@66ea1466' 
of qualified type org.example.jdbi.GenericArgumentFactoryTest$StringThing 
[statement:"SELECT CAST(:thing AS CHAR)", arguments:{positional:{}, named:{thing:org.example.jdbi.GenericArgumentFactoryTest$StringThing@66ea1466}, finder:[]}]
@hgschmie
Copy link
Contributor

This looks like an actual bug. We have tons of code that checks whether the arguments behave correctly, however there is no check that the factory actually matches an argument.

This test should pass:

--- a/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java
+++ b/core/src/test/java/org/jdbi/v3/core/argument/TestAbstractArgumentFactory.java
@@ -17,6 +17,8 @@ import java.lang.reflect.Type;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Types;
+import java.util.Optional;
+import java.util.function.Function;

 import org.jdbi.v3.core.config.ConfigRegistry;
 import org.jdbi.v3.core.generic.GenericType;
@@ -140,4 +142,18 @@ public class TestAbstractArgumentFactory {
         argument.apply(2, statement, ctx);
         verify(statement).setNull(2, Types.VARCHAR);
     }
+
+    static class StringBox extends Box<String> {
+        StringBox(String value) {
+            super(value);
+        }
+    }
+
+    @Test
+    public void testArgumentFactorySelection() {
+        ArgumentFactory.Preparable preparable = new BoxOfStringArgumentFactory();
+        Optional<Function<Object, Argument>> argument = preparable.prepare(StringBox.class , new ConfigRegistry());
+
+        assertThat(argument).isPresent();
+    }
 }

What the code does is assuming that the argument factory either wraps a class (then it tests for assignability) or "something else", then it checks for equality.

The latter part is the problem. In this case, it wraps a ParameterizedType (Box<String> or Thing<String> and the thing passed in is a concrete implementation (Foo extends Box<String> / Foo implements Thing<String>). And clearly testing for equality is not good enough here.

hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 29, 2022
hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 29, 2022
hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 30, 2022
The AbstractArgumentFactory needs to be able to deal with supertypes when
being parameterized with Non-Classes (e.g. parameterized types).

We had no tests to actually try that. This fixes jdbi#2026.
@hgschmie
Copy link
Contributor

This is fixed by #2117 which will go into the next release. This is not a regression, #1914 was about bean property resolution, this problem is caused by the lookup mechanism to find the right argument factory for the generic type.

hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 30, 2022
The AbstractArgumentFactory needs to be able to deal with supertypes when
being parameterized with Non-Classes (e.g. parameterized types).

We had no tests to actually try that. This fixes jdbi#2026.
hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 30, 2022
The AbstractArgumentFactory needs to be able to deal with supertypes when
being parameterized with Non-Classes (e.g. parameterized types).

We had no tests to actually try that. This fixes jdbi#2026.
hgschmie added a commit to hgschmie/jdbi that referenced this issue Sep 30, 2022
The AbstractArgumentFactory needs to be able to deal with supertypes when
being parameterized with Non-Classes (e.g. parameterized types).

We had no tests to actually try that. This fixes jdbi#2026.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

3 participants