 (declare experiment_sources features probes scores experiments exp_samples)
 
 (defentity cohorts)
-(defentity sources
-  (many-to-many experiment_sources experiments))
+(defentity sources)
 
 (defentity experiments
-  (many-to-many experiment_sources sources)
+  (many-to-many sources :experiment_sources)
   (has-many exp_samples)
   (has-many probes))
 

    (unparse fmtr timestamp)))

(defn- fmt-time [file-meta]
  (assoc file-meta :time (format-timestamp (:time file-meta))))

(defn clean-sources []
  (delete sources

      (insert table
              (values {id-key id :sources_id fid})))))

; work around h2 uppercase craziness
; XXX review passing naming option to korma
(defn- keys-lower [m]
  (into {} (map #(vector (keyword (subs (clojure.string/lower-case (% 0)) 1)) (% 1)) m)))

(defn- related-sources [table id]
  (map keys-lower
    (select table (join sources) (where {:id id}) (fields :sources.name :sources.hash :sources.time))))

; kdb/transaction will create a closure of our parameters,
; so we can't pass in the matrix seq w/o blowing the heap. We
; pass in a function to return the seq, instead.

(defn load-exp
  "Load matrix file and metadata. Skips the data load if file hashes are unchanged."
  ([files metadata matrix-fn features]
   (load-exp files metadata matrix-fn features false))

  ([files metadata matrix-fn features force]
   (kdb/transaction
     (let [matrix (matrix-fn)
           exp (merge-m-ent experiments-meta metadata)
           files (map fmt-time files)]
       (when (or force
                 (not (= (set files) (set (related-sources experiments exp)))))
         (println "loading data")
         (clear-by-exp exp)
         (load-related-sources
           experiment_sources :experiments_id exp files)
         (load-exp-samples exp (:samples (meta matrix)))
         (load-exp-matrix exp matrix))))))

; XXX factor out common parts with merge, above?
(defn del-exp [file]
