Hi, dealing with some frustrating issues in re: next.jdbc and duckdb. I keep seeing this exception:
clojure.lang.ExceptionInfo: Unknown dbtype: , and :classname not provided.
; db: #object[next.jdbc.connection$url_PLUS_etc$reify__29370 0x10903cbd "jdbc:duckdb:chadwick"]
; db-spec: {:dbtype "duckdb", :dbname "chadwick"}
I'd appreciate any ideas because I have never encountered such a problem with any other databases before.This sounds like it could be a missing driver issue - do you have the DuckDB JDBC driver on the classpath?
I do indeed
org.duckdb/duckdb_jdbc {:mvn/version "1.0.0"}
Provide more of the stacktrace and also the code you think is throwing that error.
When you say you "keep seeing this exception", do you mean "it works sometimes, but other times I get this exception" or do you mean "I can't get it to work, I only ever see this exception"?
; 2024-07-09T20:53:08.945Z pop-os ERROR [com.slothrop.clj-baseball.backend.duckdb:40] -
; clojure.core/eval core.clj: 3215
; ...
; user/eval80252 REPL Input:
; user/reset user.clj: 27
; ...
; clojure.tools.namespace.repl/refresh repl.clj: 169
; clojure.tools.namespace.repl/refresh repl.clj: 187
; clojure.core/apply core.clj: 667
; ...
; clojure.tools.namespace.repl/refresh-scanned repl.clj: 109
; clojure.tools.namespace.repl/refresh-scanned repl.clj: 141
; ...
;
; user/start user.clj: 20
; ...
; clojure.core/alter-var-root core.clj: 5530
; clojure.core/alter-var-root core.clj: 5535
; ...
; com.stuartsierra.component/eval23945/fn/G component.cljc: 5 (repeats 2 times)
; com.stuartsierra.component.SystemMap/start component.cljc: 181
; com.stuartsierra.component/start-system component.cljc: 163
; com.stuartsierra.component/start-system component.cljc: 165
; ...
; com.stuartsierra.component/update-system component.cljc: 130
; com.stuartsierra.component/update-system component.cljc: 136
; clojure.core/reduce core.clj: 6885
;
; ...
; com.stuartsierra.component/update-system/fn component.cljc: 140
; com.stuartsierra.component/try-action component.cljc: 118
; clojure.core/apply core.clj: 669
; ...
; com.stuartsierra.component/eval23945/fn/G component.cljc: 5 (repeats 2 times)
; com.slothrop.clj-baseball.backend.duckdb.Database/start duckdb.clj: 27
; com.slothrop.clj-baseball.backend.duckdb.Database/fn duckdb.clj: 27
; next.jdbc/execute! jdbc.clj: 254
; next.jdbc.protocols/eval29153/fn/G protocols.clj: 34
; next.jdbc.result-set/eval30133/fn result_set.clj: 1021
; next.jdbc.protocols/eval29091/fn/G protocols.clj: 14
; next.jdbc.connection/eval29377/fn connection.clj:
; 473
; next.jdbc.connection/spec->url+etc connection.clj: 200
; clojure.lang.ExceptionInfo: Unknown dbtype: , and :classname not provided.
; db: #object[next.jdbc.connection$url_PLUS_etc$reify__29370 0x10903cbd "jdbc:duckdb:chadwick"]
; db-spec: {:dbtype "duckdb", :dbname "chadwick"}
I figure this has something to do with my poor understanding of component tbh, bc I can for instance run this
(jdbc/execute! (jdbc/get-datasource db-spec) ["select 42"])
and it works just fineWell, my first comment is going to be "stop using c.t.n.repl/refresh" since that can blow away state in namespaces and not restore it...
Or don't store state in namespaces. ;)
Q: when you start your Component system for the first time, does it work?
no
I mean the system starts fine, but the JDBC code does not work.
Judging by the dbtype: , part, somewhere dbtype, whatever it is, is set to nil or is missing altogether.
Perhaps something expects a map like {:dbtype ..., ...} but your component returns something else. And it becomes apparent only when that something else is used.
I see user.clj and user/reset in that stacktrace which is why I asked about starting your Component system from a clean REPL (and you're also using something that "sanitizes" your stacktrace -- so I'd avoid that too if you can).
yes given my relative lack of familiarity with Component (I started using it bc I have always found Integrant somewhat confusing tbh) I did my best to adhere to Sierra's article on the reloaded workflow, but I'm sure I've borked something
So you'll have to show us your Database record source code with its start/`stop` functions.
In fact, to make things go faster -- is this code on GitHub where we can take a look at the whole thing?
Don't use the reloaded workflow 😐
Since you have problems even right after starting the system, the issue has nothing to do with the reloaded workflow.
(I also disagree with Sean, but we've discussed it elsewhere at length).
If you're just getting started with Component, the Reloaded workflow adds a bunch of footguns. Better to start with just manual start / stop until you understand what you're doing.
Anyways... show us source code...
database source is here https://github.com/bhlieberman/clj-baseball/blob/re-frame/dashboard/src/main/com/slothrop/clj_baseball/backend/duckdb.clj
You're returning the datasource instead of the component: https://github.com/bhlieberman/clj-baseball/blob/re-frame/dashboard/src/main/com/slothrop/clj_baseball/backend/duckdb.clj#L43'
Oh, wait, that isn't a datasource, it's the Component with :db added in... poor naming... OK, looking further...
for context, I also tried to go off your example in the usermanager app which does this
(defrecord Database [db-spec ; configuration
datasource] ; state
component/Lifecycle
(start [this]
(if datasource
this ; already initialized
(let [database (assoc this :datasource (jdbc/get-datasource db-spec))]
;; set up database if necessary
(populate database (:dbtype db-spec))
database)))
(stop [this]
(assoc this :datasource nil))Where's the -main that creates the system-map and calls start?
Heh, remind me to fix the naming in that then... Oof!
since I'm just developing this rn I'm using this code
(defn start-system [opts]
(let [{:keys [port]} opts]
(component/system-map
:server (server/web-server #'server/handler port)
:app (app/->app {})
:db (db/setup-db))))
(def system (start-system {:port 3000}))
(defn start []
(alter-var-root #'system component/start))
which is in dev/user.clj in the repoShouldn't this be (jdbc/execute! (datasource) ...? I.e. the value of :db entry
point_up::skin-tone-2 Yup, I think he's right.
I changed the code to call datasource (to get db) and it started up.
yeah I suspected this originated in my ignorance of Component. So making the component state invokable and using it like (datasource) is doing what exactly? Something like deref'ing it?
Your Database record implements invoke as something that returns db
The component implements IFn so it can be invoked (called).
Nothing to do with Component per se.
But it is a fairly common idiom with Component, I think, for things to be invokable as a way of getting back the state they encapsulate -- or performing some operation on that state (without it "escaping")
@bhlieberman93 If you look at the usermanager code, it passes database -- the Component -- into populate, and each call to jdbc/execute! in there "calls" the db component passed in.
yeah, that makes sense. I think what I was tripped up on is that I was not following the same pattern inside start because I assumed that its being in the Component constructor made the situation different. I guess...who knows what I was thinking 😅
I've create an issue against usermanager to try to clarify that for future learners...