2017-12-29 33 views
3

私はwikiプログラムでSQLiteをデータベースとして使用しています。 wikiページとそれらのページを記述するタグとの間に多対多の関係を作りたいと思います。私はclojure.java.jdbcを使ってデータベース操作を処理しています。私はページ間の相互参照テーブルに外部キーの制約を適用したいと思います。私はSQLiteサイト(https://www.sqlite.org/foreignkeys.html)の外部キーに関する情報を見て、これが私が望むものだと信じています。clojure.java.jdbcでのClojureでの外部キー制約の使用

(def the-db-name "the.db") 
(def the-db {:classname "org.sqlite.JDBC" 
      :subprotocol "sqlite" 
      :subname  the-db-name}) 

(defn create-some-tables 
    "Create some tables and a cross-reference table with foreign key constraints." 
    [] 
    (try (jdbc/db-do-commands 
     the-db false 
     ["PRAGMA foreign_keys = ON;" 
      (jdbc/create-table-ddl :pages 
           [[:page_id :integer :primary :key] 
            ;... 
            [:page_content :text]]) 
      (jdbc/create-table-ddl :tags 
           [[:tag_id :integer :primary :key] 
            [:tag_name :text "NOT NULL"]]) 
      (jdbc/create-table-ddl :tags_x_pages 
           [[:x_ref_id :integer :primary :key] 
            [:tag_id :integer] 
            [:page_id :integer] 
            ["FOREIGN KEY(tag_id) REFERENCES tags(tag_id)"] 
            ["FOREIGN KEY(page_id) REFERENCES pages(page_id)"]])]) 

     (catch Exception e (println e)))) 

ただし、プラグマをオンにしても効果はありません。

(println "Check before:" (jdbc/query the-db ["PRAGMA foreign_keys;"])) 
; Transactions on or off makes no difference. 
(println "Result of execute!:" (jdbc/execute! the-db 
               ["PRAGMA foreign_keys = ON;"])) 
(println "Check after:" (jdbc/query the-db ["PRAGMA foreign_keys;"])) 

;=> Check before: ({:foreign_keys 0}) 
;=> Result of execute!: [0] 
;=> Check after: ({:foreign_keys 0}) 

結果は、ライブラリ(org.xerial/sqliteの-JDBC「3.21.0.1」)があるため、外部キーをサポートするためにコンパイルされたことを示している:だけでプラグマをオンにし、効果を確認するためにしようと

エラーはありませんでしたが、プラグマを設定しようとしても効果はありません。

2012年にJREAのJDBC JREAでthisが見つかりました。それ以降、説明されている変更は実装されていますが、コードはまだ効果がありません。

最後に、2011年にthis postに指摘されたStackoverflowの質問に対するこの回答が見つかりました。これにより、プラグマを設定していたようなものを一緒にまとめることができました。以下のコードは、特別に設定されたConnectionの作成に依存します。以上を踏まえ

(ns example 
    (:require [clojure.java.jdbc :as jdbc]) 
    (:import (java.sql Connection DriverManager) 
      (org.sqlite SQLiteConfig))) 

(def the-db-name "the.db") 
(def the-db {:classname "org.sqlite.JDBC" 
      :subprotocol "sqlite" 
      :subname  the-db-name}) 

(defn ^Connection get-connection 
    "Return a connection to a SQLite database that 
    enforces foreign key constraints." 
    [db] 
    (Class/forName (:classname db)) 
    (let [config (SQLiteConfig.)] 
    (.enforceForeignKeys config true) 
    (let [connection (DriverManager/getConnection 
         (str "jdbc:sqlite:" (:subname db)) 
         (.toProperties config))] 
     connection))) 

(defn exec-foreign-keys-pragma-statement 
    [db] 
    (let [con ^Connection (get-connection db) 
     statement (.createStatement con)] 
    (println "exec-foreign-keys-pragma-statement:" 
      (.execute statement "PRAGMA foreign_keys;")))) 

、私は上記の表の作成コードを書き直しました:期待通りにテーブルが作成され

(defn create-some-tables 
    "Create some tables and a cross-reference table with foreign key constraints." 
    [] 
    (when-let [conn (get-connection the-db)] 
    (try 
     (jdbc/with-db-connection 
     [conn the-db] 
     ; Creating the tables with the foreign key constraints works. 
     (try (jdbc/db-do-commands 
       the-db false 
       [(jdbc/create-table-ddl :pages 
             [[:page_id :integer :primary :key] 
             [:page_content :text]]) 
       (jdbc/create-table-ddl :tags 
             [[:tag_id :integer :primary :key] 
             [:tag_name :text "NOT NULL"]]) 
       (jdbc/create-table-ddl :tags_x_pages 
             [[:x_ref_id :integer :primary :key] 
             [:tag_id :integer] 
             [:page_id :integer] 
             ["FOREIGN KEY(tag_id) REFERENCES tags(tag_id)"] 
             ["FOREIGN KEY(page_id) REFERENCES pages(page_id)"]])]) 

      ; This still doesn't work. 
      (println "After table creation:" 
         (jdbc/query the-db "PRAGMA foreign_keys;")) 

      (catch Exception e (println e)))) 

     ; This returns the expected results. 
     (when-let [statement (.createStatement conn)] 
     (try 
      (println "After creating some tables: PRAGMA foreign_keys =>" 
        (.execute statement "PRAGMA foreign_keys;")) 
      (catch Exception e (println e)) 
      (finally (when statement 
        (.close statement))))) 
     (catch Exception e (println e)) 
     (finally (when conn 
       (.close conn)))))) 

clojure.java.jdbcの機能の中には、依然として望み通りに機能しないものがあります。 (リストの途中でjdbc/queryの呼び出しを見てください。)いつも期待どおりに動作するようにすることは、Javaの相互運用性を取り戻すことが非常に「手動」なようです。そして、データベースとのあらゆるやりとりのように、特別に設定されたConnectionget-connection関数が返して使用する必要があるようです。

ClojureでSQLiteに外部キー制約を適用するより良い方法はありますか?

+0

ところで、最後の5〜6年の間、 '(Class/forName(:classname db))'は不要でした。 – DodgyCodeException

答えて

1

私はSqlLiteでプレイしていませんでしたが、あなたは

  • H2のいずれかでテストをお勧めします:pure Javaのテスト(http://www.h2database.com
  • Postgresのためのメモリで実行できます。ニーズがインストールされているが、また、SQL準拠のためのゴールドスタンダード(https://www.postgresql.org

、デバッグするとき、純粋なSQL文字列(http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.htmlを参照)を使用する方が簡単かもしれ:

(j/execute! db-spec 
      ["update fruit set cost = (2 * grade) where grade > ?" 50.0]) 

(特にデバッグ時に)純粋なSQL文字列を使用すると、JDBCで多くの誤解や落とし穴を避けることができます。また、Clojure JDBCライブラリやDB自体のバグを発見するかもしれません。

+0

H2へのポインタありがとう。私は非常に長い時間それを使用していない。それは多くの改善しているようだ。私はそれを見てみましょう。応答のために – clartaq

1

SQLiteが上記の機能をサポートしているかどうかわかりません。厳密な制約でデータを構成し続けることを本当に望む場合は、PostgeSQLデータベースを使用してください。私は、SQLiteを使って作業することは、プロジェクトを始めたばかりの方がはるかに簡単ですが、Postgresを本当に使えると信じています。ここで

create table post(
    id serial primary key, 
    title text not null, 
    body text not null 
); 

create table tags(
    id serial primary key, 
    text text not null unique 
); 

create table post_tags(
    id serial primary key, 
    post_id integer not null references posts(id), 
    tag_id integer not null references tags(id), 
    unique(post_id, tag_id) 
); 

tagsテーブルが2個の同じタグを含めることはできません。ここで

はポストとタグの宣言は、アカウントに細部の多くを取るのPostgresを使用した例です。一意のタグ文字列だけを保持して、テーブルが拡大しないようにすることが重要です。

投稿とタグをリンクするブリッジテーブルには、特定のタグが複数回投稿にリンクされている場合を防ぐための特別な制約があります。たとえば、投稿に「python」タグと「clojure」タグが付いている場合は、もう一度「python」を追加することはできません。

最後に、テーブルを宣言するときに各reference句は、ターゲットテーブルに存在しないIDを参照できないようにする特別な制約を作成します。

Postgresをインストールして設定するのはちょっと難しいかもしれませんが、最近はPostgres Appのような使い慣れていないアプリケーションもあります。

+0

ありがとう。私はターゲットオーディエンスのために、より大きく、より完全なデータベースを明らかにしました。 Javaをインストールしても、少し問題があります。基本的には、バッチファイルを使用してプログラムを開始することができます。私は「オールインワン」のインストールについては考えていませんでした。それは良いアイデアです。 – clartaq