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

Support arrays in BindIn, besides Collection #473 #474

Merged
merged 1 commit into from
Sep 15, 2016
Merged

Support arrays in BindIn, besides Collection #473 #474

merged 1 commit into from
Sep 15, 2016

Conversation

leaumar
Copy link

@leaumar leaumar commented Sep 4, 2016

if (arg instanceof Collection) {
return (Collection<?>) arg;
}
if (arg instanceof Object[]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably like to see this the other way around: convert collections to an array (using Collection.toArray()) and bind from the array. You can use java.lang.reflect.Array.getLength(array) and Array.get(array, index) and then you don't have to have all this code testing for different array types.

Also, there's a checkstyle error failing the build, because your code editor is using tabs instead of spaces for indentation. This project uses spaces.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

@qualidafial sorry, I only meant the pull request as a rough draft that needs to be fine-tuned before fully merging, not as a ready-to-go submission... There's a million aspects to how a piece of code is written, like logging, exception catching and throwing, message style, considerations like which situations to check first, or like you said indeed maybe to use an array instead of a list entirely, etc. I can't possibly know how you guys like the code to be at every level, just wanted to write this as a quick POC

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Isn't using reflection kinda bad btw? It's slow and hacky, while the type check may be verbose but it's fast and clean

@qualidafial
Copy link
Member

Java reflection hasn't been "slow" for years. Certainly it's slower than static code, but not by the crippling factor that it used to be. Moreover as seldom as @BindIn is used in practice, this case is not likely to become a performance bottleneck.

If you can demonstrate that use of reflection here adversely affects performance in real-world scenarios, I'd be happy to revisit. But absent any evidence of performance issues, I'd rather use code that is maintainable and easy to understand.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Fair enough, I'll patch it up some more. I'm not saying it'll be perfect though, someone who's more in on things in jdbi's specific domain should finetune it thoroughly anyway

@qualidafial
Copy link
Member

qualidafial commented Sep 6, 2016

Just a heads up: unless a library is using compile-time annotation processors (e.g. Dagger DI), most libraries that make use of annotations are doing so through reflection. Nearly all annotation-driven APIs I'm aware of do their magic through reflection--JDBI included.

@stevenschlansker
Copy link
Member

That's a little different though. Accessing annotations via reflection is once per statement, whereas mapping like this will be once per value converted. So it will be many orders of magnitude more common. I still lean towards the readable version first, then we can iterate and improve it as we find bottlenecks, but it's quite reasonable to consider the performance cost. And thanks @TheRealMarnes for giving this a go, don't worry at all if you are iteratively improving it -- that's what code review is for :)

return Arrays.asList((boolean[]) arg);
}
if (arg instanceof byte[]) {
return Arrays.asList((byte[]) arg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this construct actually works? I believe this is actually creating a List<byte[]> which is not what you want.

@stevenschlansker
Copy link
Member

Needs some test cases. I suspect that adding tests will reveal that the Arrays.asList approach won't work -- it won't do the dirty work of boxing primitives or the like.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

I'm going to put the casting/reflection utility method in a utility class so it can be cleanly tested. Where do you guys want it to go (package and/or class)?

@stevenschlansker
Copy link
Member

I think there's a .util package that might naturally host it. Other than that, no particular preference, just come up with a good name for it :)

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Dunno, that package seems to be (and the package.html even says so) meant for utilities for the end users. This utility is something that should stay out of the end user's way. Maybe I should make a util.internal package?

@stevenschlansker
Copy link
Member

Or just a different class in the same package -- that way you can make it package-private and not visible to end users.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Not the cleanest from a project files pov, but that does make it completely invisible to users. I like that idea more :P

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

For some reason I can't get the code to compile locally, in intellij or maven, because of missing dependencies (org.skife.jdbi.rewriter package among others). I'll just push and see if jenkins starts building it or anything. I'm not very familiar with github or this project


if (obj.getClass().isArray()) {
final int length = Array.getLength(obj);
final Object[] out = new Object[length];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only need to copy the array if obj.getClass().getComponentType().isPrimitive(). Otherwise you can just cast to Object[]

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Thanks for the catch, I'd forgotten to re-add that

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Come to think of it, why not support Iterable<?>?

@stevenschlansker
Copy link
Member

Supporting Iterable makes a lot of sense. You could even consider supporting Iterator too, although this starts to get more complex :)

Your build errors due to missing rewriter package is likely because IntelliJ doesn't know to build the ANTLR grammars -- this is handled in the generate-sources Maven phase: https://github.com/jdbi/jdbi/blob/master/pom.xml#L321-L332 So I'm a little surprised that Maven doesn't work -- what command do you invoke? what error do you get?

Eclipse knows how to figure this out (with a plugin) but unfortunately I don't know how to get IntelliJ set up right.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-toolchains-plugin:1.1:toolchain (default) on project jdbi: Cannot find matching toolchain definitions for the following toolchain types:
[ERROR] jdk [ vendor='sun' version='1.6' ]
[ERROR] Please make sure you define the required toolchains in your ~/.m2/toolchains.xml file.

Yay... but wait, maven says java 6, the travis file says jdk7? Maybe the jdk version numbering is a little wonky and it's not like I've looked it up, but shouldn't it be the same?

@stevenschlansker
Copy link
Member

Ah, yeah this needs to be documented better. You have to configure the Maven Toolchains on your dev box:
http://maven.apache.org/plugins/maven-toolchains-plugin/usage.html
http://maven.apache.org/plugins/maven-toolchains-plugin/toolchains/jdk.html

Travis only has openjdk7 type, but it actually has Java 6 still -- and jdbi v2 is based on Java 6, so we use Toolchains to make sure you use the right one:
https://github.com/jdbi/jdbi/blob/master/src/build/travis-toolchains.xml

@arteam
Copy link
Member

arteam commented Sep 6, 2016

Yes, that is a rather confusing message. You need to set up Maven
toolchains locally to point to a JDK which can compile Java 6 code. I think
we moved this functionality to a specific profile in JDBI3, just for the
reason a to simplify the development setup for the people who want to
contribute.
Am 06.09.2016 22:01 schrieb "TheRealMarnes" notifications@github.com:

[ERROR] Failed to execute goal org.apache.maven.plugins:
maven-toolchains-plugin:1.1:toolchain (default) on project jdbi: Cannot
find matching toolchain definitions for the following toolchain types:
[ERROR] jdk [ vendor='sun' version='1.6' ]
[ERROR] Please make sure you define the required toolchains in your
~/.m2/toolchains.xml file.

Yay... but wait, maven says java 6, the travis file says jdk7? Maybe the
jdk version numbering is a little wonky and it's not like I've looked it
up, but shouldn't it be the same?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#474 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABo1gM9Eqbn9x_5o9G_kLCl_obNiM3hlks5qncaRgaJpZM4J0qBO
.

@stevenschlansker
Copy link
Member

stevenschlansker commented Sep 6, 2016

Also worth noting -- if you don't have Java 6 installed, you don't actually have to go through the trouble of installing it. You can point your jdk6 toolchain at a Java 8 just fine. The downside is if you accidentally use any new features, you won't notice on a local build - but Travis will catch any errors you make.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Yeah, pointing it at my jdk8 is what I'm gonna go with...

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Ok, now I run into runtime type exceptions when I run a full test of @bindin (with varargs for starters)... That'll be for another time :/

@stevenschlansker
Copy link
Member

stevenschlansker commented Sep 6, 2016

Please leave a note of any combinations you find that don't work! We may not fix them all up for jdbi v2, but we are reworking a lot of this for v3, so we'd appreciate failing tests as issues or even a PR for the v3 branch 😄

Feel free to focus on getting just your use case working, and we can sort the rest out later.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

For some reason I can't get a screenshot attached here... Anyway, I've pushed the test class for BindIn, it passes on Iterable but fails on varargs and Iterator. The error is

java.lang.ClassCastException: [I cannot be cast to java.lang.Iterable
at org.skife.jdbi.v2.unstable.BindIn$BindingFactory$1.bind(BindIn.java:98)

@qualidafial
Copy link
Member

Something's broken in Github's UI so I can't leave a comment on the specific line, but in BindIn.java:

  • Line 64: the contents of argArray are never used--just the length. The conversion to array is wasted here--it would be better to have an Objects.size(arrayOrIterable) method
  • Lines 97-107: This is where the rubber hits the road for @BindIn: this is where you need to iterate the iterable / array and bind each value to the named parameter introduced in BindIn.CustomizerFactory.createForParameter. Currently the code is still assuming the object is an Iterable, but not checking for arrays.

@qualidafial
Copy link
Member

qualidafial commented Sep 6, 2016

Something's broken in Github's UI so I can't leave a comment on the specific line, but in BindIn.java:

  • Line 64: the contents of argArray are never used--just the length. The conversion to array is wasted here--it would be better to have an Objects.size(arrayOrIterable) method
  • Lines 97-107: This is where the rubber hits the road for @BindIn: this is where you need to iterate the iterable / array and bind each value to the named parameters introduced in BindIn.CustomizerFactory.createForParameter. Currently the code is still assuming the object is an Iterable, but not checking for arrays.

@leaumar
Copy link
Author

leaumar commented Sep 6, 2016

Isn't a cast just a manual override of the reference type that doesn't actually involve any real work (like a true conversion would)? I.e. isn't it harmless and wouldn't some utility method, possibly provided by the jdk, need to do the same thing anyway?

@qualidafial
Copy link
Member

Since @BindIn is just defining what <ids> gets turned into, you could use a mock Query and verify that Query.define("ids", expected) gets called.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Ah, right, I hadn't noticed a mock system was available, I'll take a look tonight, thanks

Edit: I forgot to say it actually seems like the code itself isn't doing what I had hoped. testSomethingByIterableHandleVoidWithEmptyList and testSomethingByIterableHandleVoidWithNull fail because the query returns 0 results when it should return 3 (I pass an empty argument -> should become null keyword -> should return the 3 rows where name = null)

Strangely it doesn't select the row with name = 'null' either. I'm guessing the argument actually becomes an empty string.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

The binding methods on SQLStatement are final... it's making mocking pretty difficult, but I might get there still.

@stevenschlansker
Copy link
Member

If you need to make them not final, that's an OK change to make.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Alright, thanks, it was starting to look like that would be the only option... strict class design has its drawbacks :P

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Well, I'm not quite happy with the EmptyHandling.NULL tests, but I don't think it can get any better. They verify that null is given as argument value to the query, and that's the closest I could get. After that, the most useful thing I could find was the parameterized query with ? and its bound arguments (the null value). I think after that point we can really only cross our fingers and presume the jdbc driver (or whatever) translates a null parameter to the null sql keyword internally, I don't think you can actually get the resulting sql string.

Edit: oh right... the failing tests prove that that's not the case...

@stevenschlansker
Copy link
Member

Don't worry about 100% unit test coverage. As long as there's some sort of functional test making sure the feature behaves as specified that's likely good enough.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Well yeah, but in this case we have a test that clearly shows a feature not working as intended. I'm honestly not sure what to do with that now. The tests show null is definitely bound to the query and debug view shows jdbc will execute a query with ? bound to null, yet the query doesn't seem to turn out as I expect. Could it be h2 doesn't like "x in (null)" too much?

@stevenschlansker
Copy link
Member

Oh, I misunderstood the problem. It's entirely possible the behavior of that construct varies between h2 and pg. I can take a bit of a look later tonight possibly if you haven't figure it out by then.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Well, I did just boot up one of my own projects that runs an h2 db with mock data, and this happened:
vivaldi_2016-09-13_23-47-07

So it seems it's a problem with h2. Maybe it's just a thing that isn't just required for postgres, but also only works on postgres.

@stevenschlansker
Copy link
Member

I think that looks correct? x IN (null) should select no rows, even if some values are null, because null != null

@stevenschlansker
Copy link
Member

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Then I guess the tests actually mean it's working up to specs?...

I suppose what it really needs is an integration test with postgres instead of h2.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Shall I just fix and break down the null handling test in light of this lesson, push it as such, and consider BindIn finally done then? You guys can figure out a way to test the null handling some other time then, and in the meantime mark EmptyHandling.NULL as experimental/untested for the postgres peeps...

@stevenschlansker
Copy link
Member

What exactly doesn't work right? With EmptyHandling.NULL, if you pass in say a new String[0], the behavior I expect is that it binds as x IN (null) and returns no rows. Which part of this is broken?


final List<Something> out = s.get(new ArrayList<Object>());

Assert.assertEquals(4, out.size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect out to be empty here, yes?

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Well I guess up to there it's working indeed. The thing I'd still like to verify is that it in fact does output the null keyword in the query so that it ends up being "x in (null)", but like I said earlier I don't think we can test for that...

Yeah sorry, my thoughts are a mess right now anyway. I guess everything is in order?

{
final SomethingByIterableHandleNull s = handle.attach(SomethingByIterableHandleNull.class);

final List<Something> out = s.get(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be considered a programming error -- should we really assume null is empty list instead of a coding error?

@stevenschlansker
Copy link
Member

Looking at the failing test, I think the test is wrong. An empty input should match no rows no matter what data is in the table.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

Looking at the failing test, I think the test is wrong. An empty input should match no rows no matter what data is in the table.

Yes, I'm changing that test atm. I mean the functionality itself is apparently working as intended.

@qualidafial
Copy link
Member

The thing I'd still like to verify is that it in fact does output the null keyword in the query so that it ends up being "x in (null)", but like I said earlier I don't think we can test for that...

You could Mockito.spy on the statement rewriter, and check that the rewritten query is as expected.

I feel this is getting out of hand though. I'm good to just merge this.

@stevenschlansker
Copy link
Member

Yes, I think the existing test (once fixed) is sufficient. No need for spying.

@leaumar
Copy link
Author

leaumar commented Sep 13, 2016

agreed

@stevenschlansker stevenschlansker merged commit 823c7b7 into jdbi:master Sep 15, 2016
@stevenschlansker
Copy link
Member

Merged, thanks!

@leaumar leaumar deleted the bindin-arrays-473 branch September 15, 2016 13:53
@leaumar
Copy link
Author

leaumar commented Sep 23, 2016

Just for my information, around when are you planning to release 2.77? I kinda need the bindin fix in a project I'm working on (hence how I found the bug and why I did any of this to begin with)

@stevenschlansker
Copy link
Member

@TheRealMarnes sorry was on vacation and lost track of it, just did the release :)

@leaumar
Copy link
Author

leaumar commented Sep 27, 2016

Awesome, thanks

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

Successfully merging this pull request may close these issues.

None yet

4 participants