sql

All things SQL and JDBC...
dharrigan 2020-03-09T06:27:19.083100Z

Thanks 🙂

dharrigan 2020-03-09T07:22:08.083500Z

That's great documentation and good examples, thanks @seancorfield! 🙂

➕ 1
kwladyka 2020-03-09T09:19:14.086900Z

(postgres/query
          ["SELECT * FROM shops WHERE uuid = ?::uuid" uuid]
          {:qualifier "shop"})
Do you work with :qualifier? I am trying to decide if I want or not. It looks improving readability of data, but on the other hand things like graphql and everything don’t support namespaces keywords, so it makes things more complex at the same time. What is your experience? Is it worth to use this? To have namespaced data structure in the system vs not namespaced?
{:user {:id 1
        :email "<mailto:foo@example.com|foo@example.com>"}
 :shops [{:id 1
          :engine "foo"}]}

vs

{:user/id 1
 :user/email "<mailto:foo@example.com|foo@example.com>"
 :shops [{:user/id 1
          :shop/id 1
          :shop/engine "foo"}]}

or something like that, but for example only shop returned
The example is bad, but I more and more I am thinking about this… When make sense to use :qualifier ?

kwladyka 2020-03-09T09:46:54.087400Z

oh there is https://github.com/seancorfield/next-jdbc new library

kwladyka 2020-03-09T09:47:08.087700Z

good work

dharrigan 2020-03-09T09:47:58.088Z

yes, next jdbc == bees knees

kwladyka 2020-03-09T10:09:47.089100Z

oh this include JSONB, uuid etc. data types . Am I right?

dharrigan 2020-03-09T10:12:11.089700Z

No, for example, if you want to support JSONB there is a nice section here on how to get next jdbc to work with it: https://cljdoc.org/d/seancorfield/next.jdbc/1.0.395/doc/getting-started/tips-tricks

dharrigan 2020-03-09T10:12:23.090Z

<https://cljdoc.org/d/seancorfield/next.jdbc/1.0.395/doc/getting-started/tips-tricks#working-with-json-and-jsonb>

kwladyka 2020-03-09T10:18:41.090600Z

oh, ok I said that too fast

kwladyka 2020-03-09T10:36:46.091900Z

(extend-protocol prepare/SettableParameter
  clojure.lang.IPersistentMap
  (set-parameter [m s i]
    (.setObject s i (-&gt;pgobject m))))

  clojure.lang.IPersistentVector
  (set-parameter [v s i]
    (.setObject s i (-&gt;pgobject v))))

vs

(extend-protocol prepare/SettableParameter
  clojure.lang.IPersistentMap
  (set-parameter [m s i]
    (.setObject s i (-&gt;pgobject m)))

  clojure.lang.IPersistentVector
  (set-parameter [v s i]
    (.setObject s i (-&gt;pgobject v))))
There is a bug in the doc, additional )

kwladyka 2020-03-09T10:46:34.092900Z

still thinking about this namespaced keywords like :user/id in context of graphql etc. What do you feel about this?

dharrigan 2020-03-09T11:07:26.093800Z

I don't have much experience with graphql, so cannot really comment.

kwladyka 2020-03-09T11:07:52.094Z

(get-by-id :shops #uuid "00000000-0000-0000-0000-000000000000" :uuid {})
Is a way to make it work without #uuid ?

kwladyka 2020-03-09T11:08:52.095100Z

Well yes, But what I mean namespaces keywords from postgresql don’t work with other ideas and systems, which don’t predict namespaced keywords. Mainly in API I guess. How do you deal with this?

kwladyka 2020-03-09T11:09:10.095500Z

(-&gt; (shop-db/get-shop-by-uuid uuid)
      (clojure.walk/stringify-keys)
      (clojure.walk/keywordize-keys))
each time?

dharrigan 2020-03-09T11:09:20.095900Z

You can remove the namespace, thus:

dharrigan 2020-03-09T11:09:45.096300Z

by passing this {:builder-fn rs/as-unqualified-lower-maps} into the ops of the jdbc invocation

dharrigan 2020-03-09T11:10:05.096500Z

<https://cljdoc.org/d/seancorfield/next.jdbc/1.0.395/doc/getting-started/result-set-builders>

dharrigan 2020-03-09T11:10:12.096700Z

there's a plethora of choices 😉

kwladyka 2020-03-09T11:14:09.097700Z

yes, but maybe I miss how this should by solved in API like graphql. Maybe this is fine, but I have to see this from different perspective. Or I just shouldn’t use namspeaces keywords 🙂

kwladyka 2020-03-09T11:14:25.098200Z

I have to go, thank you for feedback. I will be back in a few hours.

dharrigan 2020-03-09T11:23:44.098400Z

np

dharrigan 2020-03-09T11:24:28.098800Z

You can strip the namespace programmically if you wish (into {} (map (fn [[k v]] [(keyword (name k)) v])) m)

seancorfield 2020-03-09T18:13:36.103600Z

Re: qualified keys in SQL results. I try to use the qualified keys as much as possible, unless I have very specific situations where I explicitly do not want them. For APIs, data will come in unqualified (usually) and that can be validated and inserted as-is -- the logic in both c.j.j and next.jdbc uses just the name portion of keywords for inserts etc. For returning output, I generally try to coerce the SQL results to plain hash maps anyway because it's rare (in my experience) that you'll be passing the entire result set back to whatever client is consuming that data (outside your code) so that explicit coercion can do the unqualify operation. I think JSON libraries in general will also strip the qualifier when converting from hash maps to JSON but I can't say I've paid much attention to that.

seancorfield 2020-03-09T18:14:55.105500Z

It's worth noting that if you use plan (in next.jdbc, or reducible-query in c.j.j) then you will likely be accessing columns via their plain label anyway and not actually producing full-on Clojure hash maps so you can avoid the whole issue. See the link to the updated docs I posted above.

kwladyka 2020-03-09T18:15:40.106400Z

not sure what is the the conclusion: Do you use :foo/bar when get data from postgresql or other database and before you add them to response change them to :foo_bar or something like that?

kwladyka 2020-03-09T18:15:59.106900Z

I don’t know plan yet. I will read doc soon.

seancorfield 2020-03-09T18:16:13.107200Z

:foo/bar for column bar from table foo -- letting next.jdbc handle that automatically.

kwladyka 2020-03-09T18:16:45.108200Z

yes but if you will have to return this data in graphql or other API what will you do?

kwladyka 2020-03-09T18:16:59.108700Z

to keep structure of data consistent and make sense

seancorfield 2020-03-09T18:17:03.108900Z

(`clojure.java.jdbc` handles this differently: it doesn't qualify by default and the :qualifier option is a fixed string rather than the auto-added table name as in next.jdbc)

kwladyka 2020-03-09T18:17:04.109Z

and simple

seancorfield 2020-03-09T18:17:22.109400Z

Returning data -- exactly as I described above.

seancorfield 2020-03-09T18:17:38.109600Z

> For returning output, I generally try to coerce the SQL results to plain hash maps anyway because it's rare (in my experience) that you'll be passing the entire result set back to whatever client is consuming that data (outside your code) so that explicit coercion can do the unqualify operation.

kwladyka 2020-03-09T18:18:22.111100Z

sorry, not sure how to interpretate this. Can you say that using Clojure? 😉

seancorfield 2020-03-09T18:18:23.111200Z

For example, I might have a setting column in the DB that is an int and I might want to return it as one of a set of string values.

isak 2020-03-09T18:18:55.111900Z

For GraphQL a pretty simple approach is to just use queries that return JSON (works fine in postgres and SQL Server)

kwladyka 2020-03-09T18:19:32.112400Z

not sure what you mean @isak in context of namespaces keywords

kwladyka 2020-03-09T18:20:21.113200Z

graphql doesn’t let return :foo/bar

kwladyka 2020-03-09T18:20:30.113500Z

no /

isak 2020-03-09T18:20:31.113700Z

@kwladyka GraphQL doesn't have namespaces, so you would just label stuff by their fieldName in GraphQL

kwladyka 2020-03-09T18:20:57.114200Z

I know, but this mean I have to add a lot of complexity DB <-> graphql

kwladyka 2020-03-09T18:21:04.114500Z

just to play with namespaced keywords

kwladyka 2020-03-09T18:21:08.114700Z

does it make sense?

isak 2020-03-09T18:21:46.115400Z

no, i'm not sure why you're considering namespaced keywords at all if you need to return data to GraphQL

seancorfield 2020-03-09T18:22:35.116500Z

I draw a pretty hard line between data in my domain (where I would use namespace-qualified keys) and data I return to clients outside (which I would use plain string keys in general).

seancorfield 2020-03-09T18:23:08.117200Z

I don't view it as "complexity" since I think there should always be a translation layer from your domain data to your external data.

seancorfield 2020-03-09T18:23:39.117800Z

I would not expect your regular result set data to be structured the same as your GraphQL result -- in general.

seancorfield 2020-03-09T18:24:02.118300Z

(result sets are flat vectors of hash maps -- GraphQL results are typically more structured)

isak 2020-03-09T18:27:36.119800Z

Also if all your data is one relational DB, it is better to just transform the query AST to a SQL query directly, instead of relying on resolvers

kwladyka 2020-03-09T18:27:51.120100Z

> translation layer from your domain data to your external data. sure but I feel in many cases I can return data as it is

kwladyka 2020-03-09T18:28:02.120400Z

in this case + remove namespace from keywords

kwladyka 2020-03-09T18:28:12.120600Z

maybe not a big deal

kwladyka 2020-03-09T18:28:23.120900Z

but then why to use them

kwladyka 2020-03-09T18:29:41.121400Z

> transform the query AST to a SQL query directly, instead of relying on resolvers Can you show me code? I don’t what it means.

kwladyka 2020-03-09T18:31:05.122100Z

Abstract Syntax Trees? I didn’t use this

kwladyka 2020-03-09T18:31:12.122500Z

at least I don’t recognize name

kwladyka 2020-03-09T18:33:27.123400Z

yeah so in short words I confused myself if I should use namespaced keywords or not. It was looking like a cool idea, but mmm I don’t know.

isak 2020-03-09T18:35:13.124600Z

Basically for a GraphQL query like this:

users(first: 10) {
	id
	posts { title }
}
Just run a SQL query like this (SQL Server syntax, it is slightly less elegant for postgres, but basic idea the same) :
select top 10 id,
    (select title
     from posts p 
     where p.user_id = a.i 
	 for json path) as posts
 from users a
 for json path

kwladyka 2020-03-09T18:39:14.125100Z

Does lacinia have such kind of integration with postgresql?

isak 2020-03-09T18:40:50.125700Z

@kwladyka Here is an example of what the AST looks like: https://github.com/walmartlabs/lacinia/blob/master/test/com/walmartlabs/lacinia/parser/query_test.clj#L52-L57

isak 2020-03-09T18:42:58.126800Z

No, I don't havent seen anything

seancorfield 2020-03-09T18:44:59.129500Z

> yeah so in short words I confused myself if I should use namespaced keywords or not. It was looking like a cool idea, but mmm I don’t know. Like I said: within the domain, I try to use namespaced keywords because it plays nicely with Spec and makes it easier to see what entity :name or :id belongs to (because it would be :member/name, :product/name, :invoice/id, etc). When I'm returning data from an API, that (flat) data will be transformed into whatever is more appropriate for the client -- most likely a nested structure -- and that transformation will include converting (qualified) keywords to strings.

seancorfield 2020-03-09T18:45:17.130200Z

If you have clear system boundaries (and you should) then this is a no-brainer.

kwladyka 2020-03-09T18:45:28.130500Z

Oh maybe I will ask in that way: What advantage did you achieve by using namespaced keywords in practice in your systems? What pros it gives to be worth to add complexity to convert nammespaced keywords into fileds for graphql or other API?

seancorfield 2020-03-09T18:45:43.130800Z

I just answered that 🙂

kwladyka 2020-03-09T18:45:44.131Z

oh you already answered

kwladyka 2020-03-09T18:45:57.131400Z

I started to write this at the same time

seancorfield 2020-03-09T18:46:14.131800Z

Spec, clarity of domain entities. Those are why Clojure has qualified keywords.

kwladyka 2020-03-09T18:47:49.133200Z

ok this is the answer what I wanted. So you are saying this is still worth to do and you are happy with this choice.

seancorfield 2020-03-09T18:49:19.133900Z

I think it is the right choice in general, yes. Apart from anything else, it reminds you that you have a system boundary that you are passing data across.

kwladyka 2020-03-09T18:50:37.134600Z

(-&gt; (shop-db/get-shop-by-uuid uuid)
      (clojure.walk/stringify-keys)
      (clojure.walk/keywordize-keys))
Do you convert data similar to above or you foud more sophisticated way?

kwladyka 2020-03-09T18:51:04.135100Z

the general concept

seancorfield 2020-03-09T18:52:22.135500Z

Why not use a JSON library? I assume you trying to produce JSON to return?

kwladyka 2020-03-09T18:53:04.136100Z

hmm can I return JSON to lacinia ? I thought I can’t.

kwladyka 2020-03-09T18:53:57.136600Z

lacinia return JSON, but as an input I think it needs edn

kwladyka 2020-03-09T18:54:21.137Z

but I am not lacinia expert

kwladyka 2020-03-09T18:56:27.137400Z

doing this without lacinia would be nightmare

kwladyka 2020-03-09T19:01:23.138300Z

but I see, you probably don’t use graphql so this is the point where you don’t feel my concern

seancorfield 2020-03-09T19:02:41.138900Z

We use Lacinia at work -- well, my teammate uses it, I haven't had to deal with that code much yet.

kwladyka 2020-03-09T19:05:44.139400Z

thank you both @seancorfield @isak for advices

seancorfield 2020-03-09T19:08:22.141Z

My teammate's code uses namespace-qualified keywords very heavily. I'm checking with him whether we do anything specific to work with Lacinia in that regard...

👍 1
seancorfield 2020-03-09T19:13:36.141800Z

@kwladyka He says "it looks like I manually map all the namespace keywords to keywords that literally match what is in the schema, so for example :storage/message-count -> :message_count" -- which is exactly the pattern I was recommending above: an explicit transformation layer between the domain and the API return values.

❤️ 1
kwladyka 2020-03-09T19:14:19.142Z

thank you

dharrigan 2020-03-09T19:17:45.142800Z

That's what I (mostly) do as well. I remap the keys that I puck from my sql queries into json keys (we work in json a lot!)

dharrigan 2020-03-09T19:18:01.143200Z

so internally to my application, it's all namespace qualified

dharrigan 2020-03-09T19:18:08.143500Z

but interface to the outside world, it's remapped.

seancorfield 2020-03-09T19:18:37.144Z

I see that jsonista allows you to explicitly map qualified names to unqualified names.

seancorfield 2020-03-09T19:18:53.144400Z

(I was looking for that in Cheshire but didn't see it)

seancorfield 2020-03-09T19:19:23.144900Z

(! 774)-&gt; clj -Sdeps '{:deps {metosin/jsonista {:mvn/version "RELEASE"}}}'
Clojure 1.10.1
user=&gt; (require '[jsonista.core :as j])
nil
user=&gt; (def mapper (j/object-mapper {:encode-key-fn name :decode-key-fn keyword}))
#'user/mapper
user=&gt; (j/write-value-as-string {:hello/world 1} mapper)
"{\"world\":1}"
user=&gt; (j/read-value *1 mapper)
{:world 1}

dharrigan 2020-03-09T19:19:58.145500Z

Yeah, I use cheshire - simply because interia - when I was (still am!!!) learning clojure, Cheshire was my first hit 🙂

seancorfield 2020-03-09T19:21:14.145800Z

Ah, yeah, Cheshire can do it too...

seancorfield 2020-03-09T19:22:46.146100Z

(! 775)-&gt; clj -Sdeps '{:deps {cheshire {:mvn/version "RELEASE"}}}'
Clojure 1.10.1
user=&gt; (require '[cheshire.core :as j])
nil
user=&gt; (j/generate-string {:foo/bar 42} {:key-fn name})
"{\"bar\":42}"
user=&gt; (j/parse-string *1 true)
{:bar 42}

dharrigan 2020-03-09T19:23:21.146300Z

happy days!

kwladyka 2020-03-09T22:31:09.147900Z

sorry, probably I miss something big here:

(defn fixture-db-transaction [tests]
  (jdbc/with-transaction [tx db-psql/db {:rollback-only true}]
                         (with-redefs [db-psql/db tx]
                           (tests))))
it doesn’t work even if I do (.rollback tx)
(use-fixtures :each utils/fixture-db-transaction)
why data stay in DB?

kwladyka 2020-03-09T22:32:31.148600Z

Does it work like it replace db-psql with tx and rollback on the end of tests? why not?

kwladyka 2020-03-09T22:34:23.149Z

this is db-psql/db

(def db "jdbc:<postgresql://localhost:5432/postgres?user=postgres&amp;password=secret>")

kwladyka 2020-03-09T22:36:52.149300Z

this doesn’t help too

(throw (Exception. "foo"))

kwladyka 2020-03-09T22:39:49.149600Z

oh maybe because this is macro

kwladyka 2020-03-09T22:40:28.149800Z

yeah, exactly

kwladyka 2020-03-09T22:40:37.150Z

(next.jdbc/transact db-psql/db (fn* [tx] (with-redefs [db-psql/db tx] (tests))) {:rollback-only true})

kwladyka 2020-03-09T22:41:47.150200Z

or not

kwladyka 2020-03-09T22:41:53.150400Z

How can I achieve what I want?

seancorfield 2020-03-09T22:56:40.151100Z

Your use of a def is what's problematic there since it is not dynamic -- so it will be compiled into the code that uses it.

seancorfield 2020-03-09T22:57:43.151800Z

If you made it a ^:dynamic var and used binding instead of with-redefs it would work I think.

seancorfield 2020-03-09T22:58:43.152100Z

expectations.clojure.test gives examples https://cljdoc.org/d/expectations/clojure-test/1.2.1/doc/fixtures-focused-execution

seancorfield 2020-03-09T22:59:05.152600Z

(the same applies to clojure.test)

seancorfield 2020-03-09T22:59:56.152800Z

^ @kwladyka