Fork me on GitHub
#sql
<
2019-11-13
>
seancorfield03:11:22

@vale Are you around? (Hopefully, since you were just commenting on GitHub)

seancorfield03:11:09

I'm curious to know what database/driver you're using and what SQL column type, such that .setObject() with a java.util.Date object is not handled correctly.

seancorfield03:11:48

https://stackoverflow.com/questions/25536969/spring-jdbc-postgres-sql-java-8-conversion-from-to-localdate shows that error for Java Time types (not java.util.Date) and it sounds like recent PostgreSQL drivers should handle that automatically...?

valerauko03:11:34

it's postgresql (official docker image 10.10), dependencies:

[org.postgresql/postgresql "42.2.8"]
                 [seancorfield/next.jdbc "1.0.9"]

valerauko03:11:22

with a datasource at app.db.core/datasource, the following happens:

user=> (next.jdbc/execute! app.db.core/datasource ["create temporary table temp_table (id serial primary key, deadline timestamp not null)"])
[#:next.jdbc{:update-count 0}]
user=> (next.jdbc/execute! app.db.core/datasource ["INSERT INTO temp_table (deadline) VALUES (?) RETURNING *" #inst "2019-11-20T00:00:00.000-00:00"])
Execution error at user/eval29002$fn (form-init4154366754578911482.clj:6).
PSQLException Can't infer the SQL type to use for an instance of java.util.Date. Use setObject() with an explicit Types value to specify the type to use.
	org.postgresql.jdbc.PgPreparedStatement.setObject (PgPreparedStatement.java:955)
	com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setObject (HikariProxyPreparedStatement.java:-1)
	next.jdbc.prepare/eval1872/fn--1873 (prepare.clj:36)
	next.jdbc.prepare/eval1872/fn--1873 (prepare.clj:-1)
	next.jdbc.prepare/eval1847/fn--1848/G--1838--1857 (prepare.clj:23)
	next.jdbc.prepare/set-parameters (prepare.clj:49)
	next.jdbc.prepare/set-parameters (prepare.clj:42)
	next.jdbc.prepare/create (prepare.clj:133)
	next.jdbc.prepare/create (prepare.clj:77)
	next.jdbc.result-set/eval2286/fn--2294 (result_set.clj:575)
	next.jdbc.protocols/eval1626/fn--1657/G--1617--1666 (protocols.clj:33)
	next.jdbc/execute! (jdbc.clj:188)

valerauko03:11:09

i'm using [org.clojure/clojure "1.10.1"] on the clojure:lein-alpine docker image

seancorfield03:11:11

Interesting. Could you try it with a plain datasource instead of the HikariCP datasource? (so jdbc/get-datasource instead of conn/->pool)

valerauko03:11:20

same result

PSQLException Can't infer the SQL type to use for an instance of java.util.Date. Use setObject() with an explicit Types value to specify the type to use.
	org.postgresql.jdbc.PgPreparedStatement.setObject (PgPreparedStatement.java:955)
	next.jdbc.prepare/eval1872/fn--1873 (prepare.clj:36)

seancorfield03:11:12

Weird. That looks so basic and I've never seen that fail on any other database. I swear PostgreSQL is the weirdest thing out there.

seancorfield03:11:37

Let me see if I can create a repro based on what you've shown above...

valerauko04:11:11

On a related note, do you have any opinions about handling enums in postgres? It's a chore that i have to manually cast to the enum type because it attempts to insert as varchar

seancorfield04:11:37

My opinion on that is that MySQL totally gets that right: you pass a string, it inserts it just fine into an enum 🙂

seancorfield04:11:11

I have a repro of your failure. That is very weird.

seancorfield04:11:05

(can I just say, I hate PostgreSQL? This kind of stuff just plain works in every other database...)

🙃 4
valerauko04:11:55

i'm sorry i have a preference for it haha

valerauko04:11:07

but i agree that's really weird

seancorfield04:11:12

OK, well, I'm going to commit this (breaking) test and see if I can figure out WTF is "wrong" with PostgreSQL (since this works flawlessly on other DBs), and I need to see whether this works on clojure.java.jdbc...

valerauko07:11:28

another issue:

valerauko07:11:49

with-transaction, if not given an option map, expands into a form that ends in (or nil {})

valerauko07:11:54

and linters yell at me about that

valerauko07:11:46

would ~(or opts {}) work instead of (or ~opts {}) or are there some concerns i may be missing?

niclasnilsson13:11:27

Hi! Using next-jdbc, I can’t get the results from a MS SQL Server database to have qualified keywords (:table/column) as it does in the examples and also when I try with SQLite. Does anyone have a hint on what to look for to get qualified keywords with MSSQL?

niclasnilsson13:11:39

(jdbc driver is com.microsoft.sqlserver/mssql-jdbc {:mvn/version “7.4.1.jre8”})

seancorfield14:11:09

It's possible ms driver doesn't support getTableName without some additional options being passed.

seancorfield14:11:08

I thought I mentioned that in the docs (Oracle doesn't support it at all, even with an option to all for more data).

seancorfield14:11:45

When I get to my desk in a few hours I'll look that up for you, if you haven't already found it

niclasnilsson14:11:38

That might be it. I see now that you wrote about that in the Oracle chapter, but I didn’t look there.

niclasnilsson14:11:10

If that’s the case, does that mean I’m out of luck, or could there be a workaround somehow?

niclasnilsson14:11:12

Hmm. It does look like for example (-> rs .getMetaData (.getTableName 2)) returns an empty string.

niclasnilsson15:11:07

@seancorfield, I made a workaround that works but requires some discipline when writing queries and use as for every column. Could perhaps be a workaround worth to add to the docs for databases that returns blank table names. Thanks for pointing me in the right direction.

(ns user
  (:require
    [clojure.string :as str]
    [next.jdbc :as jdbc]
    [next.jdbc.result-set :as rs]
    [camel-snake-kebab.core :as csk]))

(defn ->qualified-keyword 
  "Turns a namespaced, PascalCased label into a string representing a qualified keyword.
  Namespaces are indicated with underscore.
  Example: ResourceType_CreatedDate becomes resource-type/created-date"
  [namespaced-label]
  (->> (str/split namespaced-label #"_") (map csk/->kebab-case) (str/join "/")))

(defn as-qualified-keyword-maps [rs opts]
  (rs/as-modified-maps rs (assoc opts :qualifier-fn ->qualified-keyword :label-fn ->qualified-keyword))) 

(def db "jdbc:sqlserver://...")
(def ds (jdbc/get-datasource db))

;; select * doesn't get qualified keywords
(jdbc/execute! ds ["select * from ResourceType"] {:builder-fn as-qualified-keyword-maps})
;; ... as TableName_ColumnName is turned into :table-name/column-name
(jdbc/execute! ds ["select CreatedDate as ResourceType_CreatedDate from ResourceType"] 
               {:builder-fn as-qualified-keyword-maps})

seancorfield15:11:43

Thanks. I'll update the docs to indicate sql server might also not give qualified column names.

jsa-aerial16:11:23

@seancorfield I'm starting to use next jdbc and have a couple questions that are probably detailed somewhere, but I haven't been able to find them. First, "SQL + parameters" seems to be a vector with a "parameterized" string with following items being the values of parameters. Is this defined anywhere? From examples it looks like a 'parameter' is a positional '?' but if you have more than one are they still just '?' or '?i', i in (range 1 n). Also presumably they are substituted in order with the remaining vector elements, correct? Second, it looks like 'result sets' can be customized or at least given this form: "but the row builder and result set builder machinery is open and alternatives are provided to produce unqualified keywords as column names, and to produce a vector the column names followed by vectors of column values for each row". Where is this described? I need that because I have queries where the same table (and column) is aliased as 'different' tables and so only the last column and value is included in the default result set maps.

jsa-aerial16:11:32

I think I have figured out 2 above.

seancorfield16:11:19

I would recommend you use the default qualified keyword approach -- or are you migrating from clojure.java.jdbc?

jsa-aerial17:11:14

The default cannot work with a table 'aliased' multiple times, because the result set maps just return the 'last' value of the table column value, i.e., since the multiple values come from the same table and column, in the return map you just get the last k/v pair.

jsa-aerial17:11:36

Here's an example (from what I'm working on): select be.name,sfq1.value,sfq2.value from bioentry as be, seqfeature as sf, seqfeature_qualifier_value as sfq1, seqfeature_qualifier_value as sfq2 ...

jsa-aerial17:11:47

with the default (jdbc/execute! ds [qstg]) you get vector of maps like this one:

{:bioentry/name "NC_000067",
  :seqfeature_qualifier_value/value
  "Derived by automated computational analysis using gene prediction method: Gnomon."}

jsa-aerial17:11:48

with

(jdbc/execute! ds [qstg] {:builder-fn rs/as-unqualified-arrays})
you get like this one:
["NC_000067"
  "Xkr4"
  "Derived by automated computational analysis using gene prediction method: Gnomon."]

jsa-aerial17:11:59

The latter is correct, the former is not.

jsa-aerial17:11:26

I found the result-set chapter document and maybe the former could be 'fixed' by using :as-modified-maps with a :label-fn and :qualifier-fn also given.

seancorfield17:11:37

Ah, I misunderstood what you were trying to do. Yeah, that's an interesting case. I would recommend aliasing the column in the SQL: select be.name, sf1.value as sf1_value, sf2.value as sf2_value ...

jsa-aerial17:11:32

Good point - that would also work. But I actually like the simple vector of vector approach

seancorfield17:11:42

clojure.java.jdbc would have produced value and value_1 if I recall correctly (which would be just as confusing in this case since value_1 would have come from sf2!).

👍 4
seancorfield17:11:07

Sure. And the vector of vectors format works well with insert-multi! if you need that.

👍 4
jsa-aerial17:11:58

This thing is nice - thanks for the work!!

seancorfield17:11:04
replied to a thread:@seancorfield, I made a workaround that works but requires some discipline when writing queries and use `as` for every column. Could perhaps be a workaround worth to add to the docs for databases that returns blank table names. Thanks for pointing me in the right direction. (ns user (:require [clojure.string :as str] [next.jdbc :as jdbc] [next.jdbc.result-set :as rs] [camel-snake-kebab.core :as csk])) (defn -&gt;qualified-keyword "Turns a namespaced, PascalCased label into a string representing a qualified keyword. Namespaces are indicated with underscore. Example: ResourceType_CreatedDate becomes resource-type/created-date" [namespaced-label] (-&gt;&gt; (str/split namespaced-label #"_") (map csk/-&gt;kebab-case) (str/join "/"))) (defn as-qualified-keyword-maps [rs opts] (rs/as-modified-maps rs (assoc opts :qualifier-fn -&gt;qualified-keyword :label-fn -&gt;qualified-keyword))) (def db "jdbc:sqlserver://...") (def ds (jdbc/get-datasource db)) ;; select * doesn't get qualified keywords (jdbc/execute! ds ["select * from ResourceType"] {:builder-fn as-qualified-keyword-maps}) ;; ... as TableName_ColumnName is turned into :table-name/column-name (jdbc/execute! ds ["select CreatedDate as ResourceType_CreatedDate from ResourceType"] {:builder-fn as-qualified-keyword-maps})

@niclasnilsson https://social.msdn.microsoft.com/Forums/sqlserver/en-US/55e8cbb2-b11c-446e-93ab-dc30658caf99/resultsetmetadatagettablename-returns-instead-of-table-name -- sounds like you could pass in {:result-type :scroll-insensitive} and it should return the table names?

seancorfield17:11:09

If you can confirm that (either :scroll-sensitive or :scroll-insensitive should work), I will note that in the docs. I'm adding a MS SQL Server section to the Tips &amp; Tricks part of Friendly SQL Functions...

niclasnilsson17:11:26

Hmm, that did something different. I’ll try that on something more real and see if it works.

niclasnilsson17:11:18

Syntax error (IllegalArgumentException) compiling at (REPL:1:17).
:concurrency, :cursors, and :result-type may not be specified independently.
, so I need to set at least one more thing

niclasnilsson17:11:01

But

{:result-type :scroll-sensitive :concurrency :updatable})))
seems to do the trick.

niclasnilsson17:11:18

I probably need to read up on :concurrency, :cursors and :result-type to make some good choices here, but it seems to work! Thanks!

seancorfield17:11:36

Thanks. I'd forgotten you needed to specify additional options with that...

niclasnilsson17:11:10

If you happen to have a good resource to read for which combinations to choose in which situations, that would be much appreciated (but only if you have one you know about, I’m not asking you to google for me)

seancorfield17:11:19

@niclasnilsson I suspect I'd have to go digging through the MS SQL Server JDBC documentation for that...

niclasnilsson17:11:54

Don’t, I’ll do that myself.

niclasnilsson17:11:59

Thanks for you help, this was really nice. The code I posted above turned out to be buggy anyway since it created a keyword that looked like a qualified keyword, but wasn’t. Probably due to next-jdbc merging “” with “namespace/label” which turns into a non qualified kw that looks quite alike a qualified one upon visual inspection. So this solutions was much, much better

seancorfield18:11:02

Ouch! Glad you have it working properly now. I updated the docs (on master) to clarify that :result-type requires :concurrency as well.

👍 4
seancorfield18:11:59

@jsa-aerial I've updated the docs (on master) to clarify column conflicts can arise even with qualified column names. Thank you!

👍 4
jsa-aerial18:11:20

@seancorfield I have another question vis-a-vis SQL + parameter(s). I'm trying to figure out how to pass a set of things to be substituted. Specific example: instead of hard coding where be.name in (\"NC_000067\", \"NC_000077\") in the sql string or (?,?) and pass two strings, is there a way to have ... in (?) and pass a list / vec so that it works? I've tried a couple permutations, but the server complains about bad syntax

jsa-aerial18:11:45

Oh, wait maybe I see what I did wrong.

jsa-aerial18:11:59

No, I'm wrong about knowing what I did wrong 🙂. So, is there a canonical way to do this?

seancorfield18:11:55

Normally you need ? for each parameter so it would be ["select ... where be.name in (?,?)" "NC_000067" "NC_000077"] (for two values)

seancorfield18:11:18

BTW, this is why the docs also recommend looking at things like honeysql since that knows how to deal with the multiple-`?`/multiple-parameters aspect of in when it generates SQL @jsa-aerial

jsa-aerial19:11:58

Thanks - I'm using MySql so that PostgresSQL trick doesn't apply. Oh well.

seancorfield19:11:44

@jsa-aerial HoneySQL might become your best friend then 🙂

4