Fork me on GitHub
#sql
<
2022-10-05
>
Teemu Kaukoranta12:10:43

Hi! I'm trying to migrate to migratus 1.4.4 from from 1.3.6. The newer versions of migratus drop clojure.java.jdbc, and instead use next.jdbc. I'm trying to figure out how to give the db-spec to migratus, but can't figure it out. Previously we just had a map like {:db (config/db-uri)} that we gave to migratus. db-uri being e.g. "" . I feel like I've tried everything that the next.jdbc or migratus docs are suggesting, but I can't get the connection to work :thinking_face:

Teemu Kaukoranta12:10:08

I'm able to get this to work

(.getConnection (next.jdbc/get-datasource {:jdbcUrl "jdbc:"
                                            :user "docker"
                                            :password "docker"}))
=> #object[org.postgresql.jdbc.PgConnection 0xbe0eb3d "org.postgresql.jdbc.PgConnection@be0eb3d"]
but I can't get the "raw" string to work, where the user and password are contained in the string

Teemu Kaukoranta13:10:02

This also works

(next.jdbc/get-connection "jdbc:")
=> #object[org.postgresql.jdbc.PgConnection 0x4624fa68 "org.postgresql.jdbc.PgConnection@4624fa68"]

Teemu Kaukoranta13:10:32

so it looks like next.jdbc doesn't support the user:password@host format?

Teemu Kaukoranta13:10:42

Also, mildly interesting that the "jdbc:" is required at the beginning of the string now, previously it wasn't required. I'm not sure if that was a feature of the old version of migratus, or clojure.java.jdbc

seancorfield14:10:39

It was an old "bug" in java.jdbc. I would have expected the plain JDBC string to work, at least with recent versions of next.jdbc. Let me check.

seancorfield14:10:54

Yeah, the jdbc: string should work just fine in next.jdbc so maybe that's a bug in Migratus?

Søren Sjørup15:10:41

It’s not a user vs username thing? I seem to remember having an issue with that when I migrated a long time ago.

Teemu Kaukoranta15:10:57

The "jdbc:" format works, "jdbc: doesn't edit: works when given to next.jdbc/get-connection

Teemu Kaukoranta15:10:20

Results in an UnknownHostException

Teemu Kaukoranta15:10:31

not sure if this is the best document to look at, but the format is listed here https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING

seancorfield16:10:30

@U021UJJ3CQ6 HikariCP has the user/username thing, not JDBC itself, just FYI.

👍 1
seancorfield16:10:30

@UCQGNA673 Does that second format work when given to next.jdbc directly? (with user:password@host) If so, then it sounds like something Migratus is doing wrong. Otherwise, if that second format does not work in next.jdbc on its own, please open a GH issue.

Teemu Kaukoranta17:10:06

(next.jdbc/get-connection "jdbc:")
=> #object[org.postgresql.jdbc.PgConnection 0x543f6258 "org.postgresql.jdbc.PgConnection@543f6258"]

(next.jdbc/get-connection "jdbc:")
Execution error (UnknownHostException) at sun.nio.ch.NioSocketImpl/connect (NioSocketImpl.java:567).
docker:[email protected]
It doesn't work when I call get-connection. I think this is what you meant with giving it to next.jdbc directly?

seancorfield17:10:37

OK, I'll consider that a bug in next.jdbc. I'm on vacation today so if you can pop that into a GH issue with that code/output, I'll get it fixed this weekend.

Teemu Kaukoranta17:10:23

Thank you, I'll open an issue!

1
seancorfield19:10:33

FWIW, the next.jdbc code around this simply delegates to DriverManager/getConnection so it appears that it is JDBC itself that is rejecting the URL with the embedded user:password format (for whichever DB driver you are using).

seancorfield19:10:49

I just looked around at JDBC documentation for several databases and I can't actually find any examples of user:password@host -- except for Oracle Thin JDBC which supports user/password@host -- all of the other JDBC docs I've found show ?user=..&password=.. in the URL, not embedded, and talk about setting them as properties or providing them to getConnection() rather than embedded before the hostname in the URL.

seancorfield19:10:45

https://jdbc.postgresql.org/documentation/use/ doesn't seem to indicate that user:password@host is accepted.

Teemu Kaukoranta06:10:22

Interesting. It's accepted by clojure.java.jdbc

Teemu Kaukoranta06:10:18

I'll open the ticket nevertheless and include this info on the ticket. I'm not sure if it's better to support the format or not. 🤷

seancorfield06:10:47

I'll have to check but I think java.jdbc parses the string and converts it to a legal JDBC string -- which I think was a mistake.

seancorfield07:10:36

Yeah, it specifically treats the JDBC connection string as a URI (which it really isn't) and pulls apart the "user info" portion of the URI and turns it into separate properties that get passed to DriverManager/getConnection:

(if-let [user-info (.getUserInfo uri)]
       {:user (first (str/split user-info #":"))
        :password (second (str/split user-info #":"))})
In addition, it specifically strips jdbc: off the front, if present, so that it can treat the JDBC connection string as a URI (which it isn't).

seancorfield07:10:54

java.net.URI is a very forgiving format but it really has nothing to do with what JDBC drivers accept as a connection string.

Teemu Kaukoranta07:10:38

Sorry for taking so long with it, it was pretty late yesterday when we had the discussion

Teemu Kaukoranta07:10:40

I understand if you don't want to support it by the way. It will make our migration more difficult, but I don't think you want to support all the weird decisions that were made with clojure.java.jdbc

Teemu Kaukoranta07:10:06

I mean, it is a valid design choice to try to make the migration as smooth as possible, but there's a balance

seancorfield17:10:21

I think it's an interesting possible enhancement. My thinking is that I would wrap the DriverManager/getConnection call in a try / catch and if the URL passed in looks like it might include credentials, I could unpack it and retry with user / password in the properties instead of the JDBC URL. And I would print a warning to stdout about it so folks know there's a migration needed.

seancorfield17:10:54

I suspect that wouldn't help if you were using a connection pool tho' (e.g., HikariCP) -- which I would expect everyone to be using in production?

seancorfield17:10:02

Looks like you opened the issue on the wrong repo -- it should be next.jdbc, not HoneySQL -- @UCQGNA673

Teemu Kaukoranta17:10:52

You have too many great libraries ;)

seancorfield18:10:38

Are you using connection pooling? (if not, why not?)

Teemu Kaukoranta18:10:04

We are, although not for migrations. The get-connection is there just for testing

seancorfield19:10:19

So... for production use, where you use HikariCP (or whatever), are you using a JDBC URL with embedded user:password@host?

Teemu Kaukoranta06:10:17

We have a lot of services, some of them use a map for the db-spec, and a minority use JDBC URL. I know there's at least a few of these that parse the URL and turn it into a map. I'm not that familiar with all of them, I can take a closer look if it's relevant?

Teemu Kaukoranta06:10:36

The services used to be spread out over multiple repos, we've now moved to a polylith monorepo and are trying to harmonise the way we do things, but that of course takes time 🙂

seancorfield06:10:57

It kind of is. I'm trying to get a sense of how broad your migration problem is and how much effort it's worth expending on adding this support vs fixing your code 🙂

Teemu Kaukoranta06:10:09

ahh right, makes sense

seancorfield06:10:04

After all, you could probably do a global regex find'n'replace to turn URL strings from jdbc::port/etc to jdbc::port/etc?user=user&password=password 🙂

seancorfield06:10:36

Or change how you build JDBC URLs from component parts. Depending on how/where you store user name and password.

Teemu Kaukoranta06:10:56

IF next.jdbc does not support the "embedded" format for the JDBC URLs, we'll need to migrate a handful of services. We'd probably make them use a map for the db-spec, which is better anyway IMO compared to a string. This will probably be a day or three worth of work.

seancorfield06:10:32

Like I say, this is about JDBC drivers themselves not supporting it -- next.jdbc currently just passes the URL to the driver.

Teemu Kaukoranta06:10:51

Right, important distinction 👍

seancorfield06:10:46

To "support" this, next.jdbc would have to add logic around that JDBC call to catch errors, figure out if the cause might be embedded credentials, if so pick apart the URL to extract user/password, and then rebuild the URL as a valid JDBC URL and retry.

Teemu Kaukoranta06:10:10

I tried changing the URLs in our config btw, but we have a few places where we construct a java.net.URI out of the string, and call .getUserInfo on it. That doesn't work when you have the jdbc: prefix, or have the username and password as query parameters.

Teemu Kaukoranta06:10:22

but that is more our problem

seancorfield06:10:32

Which I'd then be committed to documenting and maintaining "forever". Which is why I'd prefer not to do this.

seancorfield06:10:00

clojure.java.jdbc should never have supported the URI format, TBH.

seancorfield06:10:37

(predated me taking it over I believe)

Teemu Kaukoranta09:10:54

commented on the ticket too, but I went through a few of the services and it looks like all of them parse the string to a map that is then given to HikariCP

Teemu Kaukoranta09:10:56

(defn- parse-db-uri
  "Returns the database configuration as a map."
  [db-uri]
  (let [uri (URI. db-uri)
        user-info (str/split (.getUserInfo uri) #":")]
    {:adapter (.getScheme uri)
     :server-name (.getHost uri)
     :port-number (.getPort uri)
     :database-name (subs (.getPath uri) 1)
     :username (first user-info)
     :password (second user-info)}))

seancorfield17:10:29

OK. Er... ouch! 🙂

Teemu Kaukoranta17:10:41

I'm sure there was a reason years ago why they decided to do it like that 😅