Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inserting PostgreSQL arrays with Clojure

I can't find a way to insert Postgres' array type with Clojure.

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

Didn't work which I kind of expected. Error message:

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)

Even the most direct access to SQL that I could find didn't work:

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

Don't really know what's going on here:

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

Surely it must be possible somehow? If not by the helper functions, then by somehow executing raw SQL.

like image 741
Erik Strömberg Avatar asked Apr 09 '14 10:04

Erik Strömberg


People also ask

Can you insert array in PostgreSQL?

PostgreSQL allows columns of a table to be defined as variable-length multidimensional arrays. Arrays of any built-in or user-defined base type, enum type, composite type, range type, or domain can be created.

How do I declare an array in PostgreSQL?

You can easily create arrays in PostgreSQL by adding square brackets [] immediately after the data type for the column. create table employees ( first_name varchar, last_name varchar, phone_numbers integer[] ); In the above example, we have created column phone_numbers as an array of integers.

Should you use arrays in Postgres?

When you are considering portability (e.g. rewriting your system to work with other databses) then you must not use arrays. If you are sure you'll stick with Postgres, then you can safely use arrays where you find appropriate. They exist for a reason and are neither bad design nor non-compliant.


2 Answers

You can make clojure.java.jdbc automatically convert between Clojure vectors and SQL arrays by extending two protocols. This can be done from your own code:

(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 Example:

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"]})

I'm currently working on a library that will support PostgreSQL, and PostGIS types automatically. It's still very much work in process though https://github.com/remodoy/clj-postgresql

like image 176
jkj Avatar answered Oct 03 '22 02:10

jkj


to use insert! to insert a vector of strings you must create an object (from the vector of strings) that implements java.sql.Array. You can use java.sql.Connection.createArrayOf to create such object

(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})

and

clojure.java.jdbc's docs on execute! said

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

Your must put your sql string in a vector to make it work.

(sql/execute! db ["INSERT INTO things (animals) VALUES ('{\"cow\", \"pig\"}')"])
like image 30
mavbozo Avatar answered Oct 03 '22 01:10

mavbozo