【问题标题】:How do you insert a Postgres enum value using Clojure JDBC?如何使用 Clojure JDBC 插入 Postgres 枚举值?
【发布时间】:2013-01-21 01:39:24
【问题描述】:

例如,这是 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)

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

【问题讨论】:

    标签: sql jdbc clojure


    【解决方案1】:

    我今天使用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!

    最好有一个不会严重削弱类型安全的解决方案。

    【讨论】:

    • 我偶然发现了this 作为替代方案。有点冗长,但可能值得研究。
    【解决方案2】:

    Kris Jurka replied 参加上述 Mike Sherrill 引用的讨论并提供了解决方法:

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

    我在 Java 中尝试过,它似乎工作正常。

    【讨论】:

      【解决方案3】:

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

      Tom Lane addressed this issue 在您提出问题一周后在 pgsql-hackers 上发表。

      AFAIK 这与 JDBC 一样正常:setString() 意味着 参数是字符串类型。如果类型真的会倒下 required 不是字符串。 (我不是 Java 专家,但我似乎 回想一下,使用 setObject 是标准的解决方法。)

      枚举在这里没有遇到任何特殊的困难,我反对 弱化类型系统给他们一个特殊的通行证。

      我们自己的@CraigRinger participated in that discussion,现在可能已经找到相关的东西了。

      【讨论】:

      • 我认为@espeed 是在询问 Clojure 库 clojure.java.jdbc 的使用,而不是 Java 的 JDBC。
      【解决方案4】:

      This blog post 很好地解决了这个问题。 jdbc 提供了ISQLValue protocol,它只有一个方法sql-value,它将clojure 值转换为sql 值,由PGObject 表示。博客文章建议用: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))
      

      问题是查询db时,枚举是一个简单的字符串而不是关键字。这可以通过实现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))))
      

      【讨论】:

        【解决方案5】:

        如果有人在使用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 所述。

        【讨论】:

          猜你喜欢
          • 2011-05-07
          • 1970-01-01
          • 1970-01-01
          • 2023-03-19
          • 1970-01-01
          • 2020-08-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多