Вставка массивов PostgreSQL с помощью Clojure

Я не могу найти способ вставить тип массива Postgres с Clojure.

(sql/insert! db :things {:animals ["cow" "pig"]})

не сработало, чего я ожидал. Сообщение об ошибке:

PSQLException Can't infer the SQL type to use for an instance of clojure.lang.PersistentVector. Use setObject() with an explicit Types value to specify the type to use.  org.postgresql.jdbc2.AbstractJdbc2Statement.setObject (AbstractJdbc2Statement.java:1936)

даже самый прямой доступ к SQL, который я мог найти, не работал:

(sql/execute! db "INSERT INTO things (animals) VALUES ('{"cow", "pig"}')")

на самом деле не знаю, что здесь происходит:

ClassCastException java.lang.Character cannot be cast to java.lang.String  clojure.java.jdbc/prepare-statement (jdbc.clj:419)

конечно, это должно быть возможно как-то? Если не вспомогательные функции, тогда как выполнение чистом SQL.

3 ответов


использовать вставить! чтобы вставить вектор строк, вы должны создать объект (из вектора строк), который реализует java.язык SQL.Матрица. Вы можете использовать java.язык SQL.Соединение.createArrayOf создать такой объект

(def con (sql/get-connection db))

(def val-to-insert 
    (.createArrayOf con "varchar" (into-array String ["cow", "pig"]))

(sql/insert! db :things {:animals val-to-insert})

и

clojure.Ява.документы jdbc на выполнить! сказал

(execute! db-spec [sql & params] :multi? false :transaction? true)
(execute! db-spec [sql & param-groups] :multi? true :transaction? true)

вы должны поместить строку sql в вектор, чтобы она работала.

(sql/execute! db ["INSERT INTO things (animals) VALUES ('{\"cow\", \"pig\"}')"])

Вы можете сделать clojure.Ява.jdbc автоматически преобразует между векторами Clojure и массивами SQL, расширяя два протокола. Это можно сделать из вашего собственного кода:

(extend-protocol clojure.java.jdbc/ISQLParameter
  clojure.lang.IPersistentVector
  (set-parameter [v ^java.sql.PreparedStatement stmt ^long i]
    (let [conn (.getConnection stmt)
          meta (.getParameterMetaData stmt)
          type-name (.getParameterTypeName meta i)]
      (if-let [elem-type (when (= (first type-name) \_) (apply str (rest type-name)))]
        (.setObject stmt i (.createArrayOf conn elem-type (to-array v)))
        (.setObject stmt i v)))))

(extend-protocol clojure.java.jdbc/IResultSetReadColumn
  java.sql.Array
  (result-set-read-column [val _ _]
    (into [] (.getArray val))))

REPL пример:

user> (def db (clj-postgresql.core/pool :dbname "test"))
#'user/db
user> (clojure.java.jdbc/query db ["SELECT ?::text[], ?::int[]" ["foo" "bar"] [1 2 3]])
({:int4 [1 2 3], :text ["foo" "bar"]})

в настоящее время я работаю над библиотекой, которая будет поддерживать типы PostgreSQL и PostGIS автоматически. Это все еще очень много работы в процессе, хотя https://github.com/remodoy/clj-postgresql


аналогичная стратегия, которую я использовал:

(defn vec->arr [array-vector]
  (.createArrayOf (j/get-connection db) "varchar" (into-array String array-vector)))

(extend-protocol j/ISQLValue
    clojure.lang.IPersistentVector
    (sql-value [v]
    (vec->arr v)))