如何使用Clojure JDBC插入Postgres枚举值?

时间:2013-02-05 23:57:24

标签: sql jdbc clojure

例如,这是PostgreSQL中的产品表,其状态为枚举:

create type product_status as enum ('InStock', 'OutOfStock');

create table product (
    pid            int primary key default nextval('product_pid_seq'),
    sku            text not null unique,
    name           text not null,
    description    text not null,
    quantity       int not null,
    cost           numeric(10,2) not null,
    price          numeric(10,2) not null,
    weight         numeric(10,2),
    status         product_status not null
);

插入产品的典型Clojure代码是:

(def prod-12345 {:sku "12345"
                 :name "My Product"
                 :description "yada yada yada"
                 :quantity 100
                 :cost 42.00
                 :price 59.00
                 :weight 0.3
                 :status "InStock"})

(sql/with-connection db-spec
   (sql/insert-record :product prod-12345))

但是,status是枚举,因此您无法将其作为普通字符串插入而不将其强制转换为枚举:

'InStock'::product_status

我知道你可以用准备好的声明来做,例如:

INSERT INTO product (name, status) VALUES (?, ?::product_status)

但是有没有办法在不使用准备好的声明的情况下做到这一点?

5 个答案:

答案 0 :(得分:2)

今天我使用stringtype=unspecified hack 解决方法让这个工作。

您可以将此参数添加到db-spec,如下所示:

(def db-spec {:classname "org.postgresql.Driver"
              :subprotocol "postgresql"
              :subname "//myserver:5432/mydatabase"
              :user "myuser"
              :password "mypassword"
              :stringtype "unspecified"}) ; HACK to support enums

然后像往常一样使用insert!

如果有一个不会过分削弱类型安全性的解决方案,那将是一件好事。

答案 1 :(得分:1)

Kris Jurka replied讨论Mike Sherrill上面提到的解决方法:

  

使用url参数stringtype = unspecified [在JDBC连接URL中]使setString始终绑定到unknown而不是varchar,然后不需要任何代码更改。

我在Java中试过这个,看起来效果很好。

答案 2 :(得分:0)

除非您将纯SQL传递给后端,否则您将不得不使用强制转换。 (SQL语句INSERT INTO product (name, status) VALUES ('SomeName', 'InStock');应该可以正常工作。)

在你提出问题一周后,pgsql-hackers上的

Tom Lane addressed this issue

  

AFAIK这与JDBC一样正常:setString()暗示着   参数是字符串类型。实际上,如果这种类型,它将会失败   必需的只是一个字符串。 (我不是Java专家,但我似乎   回想一下,使用setObject代替标准的解决方法。)

     

Enums在这里没有任何特殊的困难,我会反对   弱化类型系统给他们一个特殊的传递。

我们自己的@CraigRinger participated in that discussion,现在可能已找到相关内容。

答案 3 :(得分:0)

This blog post很好地解决了这个问题。 jdbc提供了ISQLValue protocol,它只有一种方法sql-value,该方法将clojure值转换为由PGObject表示的sql值。该博客文章建议使用:type/value形式的关键字来表示枚举,因此ISQLValue可以实现如下:

(defn kw->pgenum [kw]
  (let [type (-> (namespace kw)
                 (s/replace "-" "_"))
        value (name kw)]
    (doto (PGobject.)
      (.setType type)
      (.setValue value))))

(extend-type clojure.lang.Keyword
  jdbc/ISQLValue
  (sql-value [kw]
    (kw->pgenum kw)))

在您的示例中,您将产品插入:

(def prod-12345 {:sku "12345"
                 :name "My Product"
                 :description "yada yada yada"
                 :quantity 100
                 :cost 42.00
                 :price 59.00
                 :weight 0.3
                 ;; magic happens here
                 :status :product_status/InStock})

(sql/with-connection db-spec
   (sql/insert-record :product prod-12345))

问题是查询数据库时,枚举是一个简单的字符串而不是关键字。可以通过实现IResultSetReadColumn protocol来以类似的方式解决此问题:

(def +schema-enums+
  "A set of all PostgreSQL enums in schema.sql. Used to convert
  enum-values back into Clojure keywords."
  ;; add your other enums here
  #{"product_status"})

(extend-type java.lang.String
  jdbc/IResultSetReadColumn
  (result-set-read-column [val rsmeta idx]
    (let [type (.getColumnTypeName rsmeta idx)]
      (if (contains? +schema-enums+ type)
        (keyword (s/replace type "_" "-") val)
        val))))

答案 4 :(得分:0)

如果有人在使用后继者clojure.java.jdbc,jdbc.next时引用此问题,则用于插入枚举的代码如下:

(ns whatever
 (:require
  [next.jdbc.sql :as jdbc.sql]
  [next.jdbc.types :as jdbc.types]
 ))

;; ...define your database connection and data source...    

(def prod-12345 {:sku "12345"
                 :name "My Product"
                 :description "yada yada yada"
                 :quantity 100
                 :cost 42.00
                 :price 59.00
                 :weight 0.3
                 :status (jdbc.types/as-other "InStock")})

(jdbc.sql/insert! ds :product prod-12345)

https://github.com/seancorfield/next-jdbc/blob/develop/doc/tips-and-tricks.md标题下的“使用枚举类型”所述。