Welcome! Please see the About page for a little more info on how this works.

0 votes
in Protocols by


Here is a code from honeysql:

(extend-protocol ToSql
  #?(:clj clojure.lang.Keyword
     :cljs cljs.core/Keyword)
  (to-sql [x]
    (let [s (name x)]
      (case (.charAt s 0)
        \% (let [call-args (string/split (subs s 1) #"\." 2)]
             (to-sql (apply call (map keyword call-args))))
        \? (to-sql (param (keyword (subs s 1))))
        (quote-identifier x))))

  .... ;SKIPPED

  #?(:clj Object :cljs default)
  (to-sql [x]
    #?(:clj (add-anon-param x)
       :cljs (if (sequential? x)
               (seq->sql x)
               (add-anon-param x)))))

Assume i'm calling

(to-sql :column-name)

Method selected to be called depends on lookup order because Keyword is a subclass of Object and :column-name IS-A Keyword and IS-A Object.

I didn't find any info about defined order of methods lookup in clojure protocol documentation.

Also i investigated implementation of extend-protocol and as i understood there is a usual HashMap for :impls where all methods are stored so again there is no any guarantee about order of lookup.

Another words, what are the rules of extending protocol for types which have subclass relations or maybe this is not correct at all and i should extend protocol only for types which don't have any type relations.


1 Answer

+2 votes
selected by
Best answer

The most specific match will be chosen - given Keyword and Object impls, the Keyword one will be selected.

If the most specific match is ambiguous (same concrete type extends two interfaces that are extended), the selected impl is arbitrary and undefined (so you should probably avoid doing that).

From https://clojure.org/reference/protocols:

"if one interface is derived from the other, the more derived is used, else which one is used is unspecified"

edited by
Just to clarify.

Having one extension of  ToSql protocol i want to extend the same protocol *overriding* type which already was extended in previous extension.

For example, i have ToSql extension for Keyword type in honey sql library and i made ToSql extension for Keyword type in my code.

How these extensions are combined? Does my extension overrides extension defined in library?

Or this is a case "same concrete type extends two interfaces that are extended" where Keyword is an extended type, ToSql is an interfaces which is extended two times by the same type.

As i can see extend-protocol transforms into extend-type Keyword for ToSql and latest extend-type wins.

(-reset-methods (alter-var-root (:var proto) assoc-in [:impls atype] mmap))

Please, confirm.

Yes, there is only one "slot" for a particular type in a protocol, so you can replace it by extending. That said, you should be careful when you do this (as you are replacing functionality someone else installed). There are some guidelines at https://clojure.org/reference/protocols#_guidelines_for_extension.
As the maintainer of HoneySQL, I'd be concerned that you are trying to override one of the built-in, low-level type conversions here -- what do you need HoneySQL to do for keywords that would be different to how the library handles them by default?
Hi, Sean!
HoneySQL is a great, compact and minimal. It's a great *physical* pleasure for me to read and tinker it :)
Also I didn't play with your next.jdbc but it's definitely in my plans.

My task is following:
I need to batch several insert honey sql commands which have same metadata shape (table and columns) into single jdbc/execute! call.
1. {:insert-into [:my-table]
    :values [{:col1 "str1"
              :col2 "str2"}]}
2. {:insert-into [:my-table]
    :values [{:col1 "str1"
              :col2 nil}]} <--------- here we have nil value

honey sql gives me:
1. "insert into "my-table" ("col1","col2") VALUES (?, ?)"  ["str1" "str2"]
2. "insert into "my-table" ("col1","col2") VALUES (?, NULL)"  ["str1"]   <----- NULL is inlined

jdbc/execute! awaits sql and vector of sql-params, so i want to submit one sql-statement and vector of sql-params:
"insert into "my-table" ("col1","col2") VALUES (?, ?)" and [["str1" "str2"] ["str1"]] but to do this i should have same params "layout".

As a result i need to override default honeysql behavior that inlines 'NULL' (and booleans) to hoist them into sql parameters.


1. "insert into "my-table" ("col1","col2") VALUES (?, ?)"  ["str1" "str2"]
2. "insert into "my-table" ("col1","col2") VALUES (?, ?)"  ["str1" nil]

Maybe you can advice more accurate way to do.