0

I have a simple DAO ns:

(ns   alavita.dao
  (:require
    [clojure.tools.logging    :as     log     ]
    [clojure.java.io          :as     io      ]
  )
  (:import
    [org.jdbi.v3.core Jdbi Handle]
  ))

(defn create
  (^Jdbi [^String url]
    (Jdbi/create url))
  (^Jdbi [^String url ^String username ^String password]
    (Jdbi/create url username password)))

(defn open
  ^Handle [^Jdbi jdbi]
  (.open jdbi))

When trying to use the lib:

alavita.core=> (def c (dao/create "jdbc:sqlite:/tmp/data.db"))
#'alavita.core/c
alavita.core=> (def h (dao/open c))
#'alavita.core/h
alavita.core=> (.execute h "show tables")

IllegalArgumentException No matching method found: execute for class org.jdbi.v3.core.Handle  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:53)

This is kind of weird because h definitely has .execute:

alavita.core=> (cli/all-methods h)
(.attach .begin .close .commit .createBatch .createCall .createQuery .createScript .createUpdate .execute .getConfig .getConnection .getExtensionMethod .getStatementBuilder .getTransactionIsolationLevel .inTransaction .isClosed .isInTransaction .isReadOnly .lambda$attach$3 .lambda$new$0 .lambda$useTransaction$1 .lambda$useTransaction$2 .prepareBatch .release .rollback .rollbackToSavepoint .savepoint .select .setConfig .setConfigThreadLocal .setExtensionMethod .setExtensionMethodThreadLocal .setReadOnly .setStatementBuilder .setTransactionIsolation .useTransaction)

Not sure where it goes sideways.

Types for open and create:

alavita.core=> (type (dao/create "jdbc:sqlite:/tmp/data.db"))
org.jdbi.v3.core.Jdbi
alavita.core=> (type (dao/open c))
org.jdbi.v3.core.Handle

Adding reflection:

alavita.core=> (set! *warn-on-reflection* true)
true
alavita.core=> (.execute h "show tables")
Reflection warning, /private/var/folders/nr/g50ld9t91c555dzv91n43bg40000gn/T/form-init767780595230125901.clj:1:1 - call to method execute can't be resolved (target class is unknown).

IllegalArgumentException No matching method found: execute for class org.jdbi.v3.core.Handle  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:53)
Istvan
  • 7,500
  • 9
  • 59
  • 109
  • Your type hints say that `open` accepts a Jdbi, and returns a Handle, but the error and the fact that it's resorting to reflection suggests that `jdbi` already *is* a Handle. The error mentions `open`, not `.exexute`. – Carcigenicate Dec 01 '17 at 13:59
  • Verify what `Jdbi/create` returns. – Carcigenicate Dec 01 '17 at 14:01
  • Hmm, never used that API, but your usage seems correct after looking over the docs. I'd do some pre/post type checks in `open`. – Carcigenicate Dec 01 '17 at 14:04
  • @Carcigenicate you right i have a copy paste fail. It is actually execute that it has the problem with. I fixed it. – Istvan Dec 01 '17 at 14:08
  • Ahh, I know what this is. It's because `execute` uses var args. Let's see if I remember how to fix this... – Carcigenicate Dec 01 '17 at 14:09
  • Possible duplicate of [How to handle java variable length arguments in clojure?](https://stackoverflow.com/questions/11702184/how-to-handle-java-variable-length-arguments-in-clojure) – Carcigenicate Dec 01 '17 at 14:10
  • It's not that it can't find `execute` on the `Handle`, it can't find an `execute` signature that matches the arguments you've given it. – Carcigenicate Dec 01 '17 at 14:11
  • You are saying execute takes at least 1 argument and potentially more? public int execute(String sql,Object... args) Not sure how can i use it with one in this case than. – Istvan Dec 01 '17 at 14:14
  • `(.execute h "show tables" (into-array []))` should work. This is probably the most unfortunate Java interop case. – Carcigenicate Dec 01 '17 at 14:17

1 Answers1

1

As mentioned in this port, this is an issue with how Java handles var-args. You need to wrap var-args in an array first instead of relying on the var-arg behavior.

I recommend writing a Clojure function to handle this:

(defn execute [^Handle h, ^String sql, & args]
  (.execute h sql (into-array Object args)))

And using that instead.

Istvan
  • 7,500
  • 9
  • 59
  • 109
Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • Funnily enough i just repeled it out.(.execute h "show tables" (into-array [])) – Istvan Dec 01 '17 at 14:25
  • I was running into this problem with this solution: (into-array ["/tmp" 0]) IllegalArgumentException array element type mismatch java.lang.reflect.Array.set (Array.java:-2) – Istvan Dec 01 '17 at 15:14
  • @Istvan Are you sure you want to stick strings and ints in the same array? – Carcigenicate Dec 01 '17 at 15:16
  • How else would you be able to insert into a table with integer and text fields? – Istvan Dec 01 '17 at 15:18
  • 1
    @Istvan If that's what you want, try `(into-array Object ["/temp" 0])`. You need to specify the base class if the elements are different types. – Carcigenicate Dec 01 '17 at 15:19
  • Yep I have updated your answer with that. You actually need to use (into-array Object args) – Istvan Dec 01 '17 at 15:21