Mercurial > lasercutter
view src/clojure/contrib/datalog/database.clj @ 10:ef7dbbd6452c
added clojure source goodness
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 21 Aug 2010 06:25:44 -0400 |
parents | |
children |
line wrap: on
line source
1 ;; Copyright (c) Jeffrey Straszheim. All rights reserved. The use and2 ;; distribution terms for this software are covered by the Eclipse Public3 ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can4 ;; be found in the file epl-v10.html at the root of this distribution. By5 ;; using this software in any fashion, you are agreeing to be bound by the6 ;; terms of this license. You must not remove this notice, or any other,7 ;; from this software.8 ;;9 ;; database.clj10 ;;11 ;; A Clojure implementation of Datalog -- Support for in-memory database12 ;;13 ;; straszheimjeffrey (gmail)14 ;; Created 21 Feburary 200917 (ns clojure.contrib.datalog.database18 (:use clojure.contrib.datalog.util)19 (:use clojure.contrib.def)20 (:use [clojure.set :only (union intersection difference)])21 (:use [clojure.contrib.except :only (throwf)])22 (:import java.io.Writer))25 (defstruct relation26 :schema ; A set of key names27 :data ; A set of tuples28 :indexes) ; A map key names to indexes (in turn a map of value to tuples)31 ;;; DDL33 (defmethod print-method ::datalog-database34 [db ^Writer writer]35 (binding [*out* writer]36 (do37 (println "(datalog-database")38 (println "{")39 (doseq [key (keys db)]40 (println)41 (println key)42 (print-method (db key) writer))43 (println "})"))))45 (defn datalog-database46 [rels]47 (with-meta rels {:type ::datalog-database}))49 (def empty-database (datalog-database {}))51 (defmethod print-method ::datalog-relation52 [rel ^Writer writer]53 (binding [*out* writer]54 (do55 (println "(datalog-relation")56 (println " ;; Schema")57 (println " " (:schema rel))58 (println)59 (println " ;; Data")60 (println " #{")61 (doseq [tuple (:data rel)]62 (println " " tuple))63 (println " }")64 (println)65 (println " ;; Indexes")66 (println " {")67 (doseq [key (-> rel :indexes keys)]68 (println " " key)69 (println " {")70 (doseq [val (keys ((:indexes rel) key))]71 (println " " val)72 (println " " (get-in rel [:indexes key val])))73 (println " }"))74 (println " })"))))76 (defn datalog-relation77 "Creates a relation"78 [schema data indexes]79 (with-meta (struct relation schema data indexes) {:type ::datalog-relation}))81 (defn add-relation82 "Adds a relation to the database"83 [db name keys]84 (assoc db name (datalog-relation (set keys) #{} {})))86 (defn add-index87 "Adds an index to an empty relation named name"88 [db name key]89 (assert (empty? (:data (db name))))90 (let [rel (db name)91 inx (assoc (:indexes rel) key {})]92 (assoc db name (datalog-relation (:schema rel)93 (:data rel)94 inx))))96 (defn ensure-relation97 "If the database lacks the named relation, add it"98 [db name keys indexes]99 (if-let [rel (db name)]100 (do101 (assert (= (:schema rel) (set keys)))102 db)103 (let [db1 (add-relation db name keys)]104 (reduce (fn [db key] (add-index db name key))105 db1106 indexes))))109 (defmacro make-database110 "Makes a database, like this111 (make-database112 (relation :fred [:mary :sue])113 (index :fred :mary)114 (relation :sally [:jen :becky])115 (index :sally :jen)116 (index :sally :becky))"117 [& commands]118 (let [wrapper (fn [cur new]119 (let [cmd (first new)120 body (next new)]121 (assert (= 2 (count body)))122 (cond123 (= cmd 'relation)124 `(add-relation ~cur ~(first body) ~(fnext body))125 (= cmd 'index)126 `(add-index ~cur ~(first body) ~(fnext body))127 :otherwise (throwf "%s not recognized" new))))]128 (reduce wrapper `empty-database commands)))130 (defn get-relation131 "Get a relation object by name"132 [db rel-name]133 (db rel-name))135 (defn replace-relation136 "Add or replace a fully constructed relation object to the database."137 [db rel-name rel]138 (assoc db rel-name rel))141 ;;; DML144 (defn database-counts145 "Returns a map with the count of elements in each relation."146 [db]147 (map-values #(-> % :data count) db))149 (defn- modify-indexes150 "Perform f on the indexed tuple-set. f should take a set and tuple,151 and return the new set."152 [idxs tuple f]153 (into {} (for [ik (keys idxs)]154 (let [im (idxs ik)155 iv (tuple ik)156 os (get im iv #{})157 ns (f os tuple)]158 [ik (if (empty? ns)159 (dissoc im iv)160 (assoc im iv (f os tuple)))]))))162 (defn- add-to-indexes163 "Adds the tuple to the appropriate keys in the index map"164 [idxs tuple]165 (modify-indexes idxs tuple conj))167 (defn- remove-from-indexes168 "Removes the tuple from the appropriate keys in the index map"169 [idxs tuple]170 (modify-indexes idxs tuple disj))172 (defn add-tuple173 "Two forms:175 [db relation-name tuple] adds tuple to the named relation. Returns176 the new database.178 [rel tuple] adds to the relation object. Returns the new relation."179 ([db rel-name tuple]180 (assert (= (-> tuple keys set) (-> rel-name db :schema)))181 (assoc db rel-name (add-tuple (db rel-name) tuple)))182 ([rel tuple]183 (let [data (:data rel)184 new-data (conj data tuple)]185 (if (identical? data new-data) ; optimization hack!186 rel187 (let [idxs (add-to-indexes (:indexes rel) tuple)]188 (assoc rel :data new-data :indexes idxs))))))190 (defn remove-tuple191 "Two forms:193 [db relation-name tuple] removes the tuple from the named relation,194 returns a new database.196 [rel tuple] removes the tuple from the relation. Returns the new197 relation."198 ([db rel-name tuple] (assoc db rel-name (remove-tuple (db rel-name) tuple)))199 ([rel tuple]200 (let [data (:data rel)201 new-data (disj data tuple)]202 (if (identical? data new-data)203 rel204 (let [idxs (remove-from-indexes (:indexes rel) tuple)]205 (assoc rel :data new-data :indexes idxs))))))207 (defn add-tuples208 "Adds a collection of tuples to the db, as209 (add-tuples db210 [:rel-name :key-1 1 :key-2 2]211 [:rel-name :key-1 2 :key-2 3])"212 [db & tupls]213 (reduce #(add-tuple %1 (first %2) (apply hash-map (next %2))) db tupls))215 (defn- find-indexes216 "Given a map of indexes and a partial tuple, return the sets of full tuples"217 [idxs pt]218 (if (empty? idxs)219 nil220 (filter identity (for [key (keys pt)]221 (if-let [idx-map (idxs key)]222 (get idx-map (pt key) #{})223 nil)))))225 (defn- match?226 "Is m2 contained in m1?"227 [m1 m2]228 (let [compare (fn [key]229 (and (contains? m1 key)230 (= (m1 key) (m2 key))))]231 (every? compare (keys m2))))233 (defn- scan-space234 "Computes a stream of tuples from relation rn matching partial tuple (pt)235 and applies fun to each"236 [fun db rn pt]237 (let [rel (db rn)238 idxs (find-indexes (:indexes rel) pt)239 space (if (empty? idxs)240 (:data rel) ; table scan :(241 (reduce intersection idxs))]242 (trace-datalog (when (empty? idxs)243 (println (format "Table scan of %s: %s rows!!!!!"244 rn245 (count space)))))246 (fun #(match? % pt) space)))248 (defn select249 "finds all matching tuples to the partial tuple (pt) in the relation named (rn)"250 [db rn pt]251 (scan-space filter db rn pt))253 (defn any-match?254 "Finds if there are any matching records for the partial tuple"255 [db rn pt]256 (if (= (-> pt keys set) (:schema (db rn)))257 (contains? (:data (db rn)) pt)258 (scan-space some db rn pt)))261 ;;; Merge263 (defn merge-indexes264 [idx1 idx2]265 (merge-with (fn [h1 h2] (merge-with union h1 h2)) idx1 idx2))267 (defn merge-relations268 "Merges two relations"269 [r1 r2]270 (assert (= (:schema r1) (:schema r2)))271 (let [merged-indexes (merge-indexes (:indexes r1)272 (:indexes r2))273 merged-data (union (:data r1)274 (:data r2))]275 (assoc r1 :data merged-data :indexes merged-indexes)))277 (defn database-merge278 "Merges databases together"279 [dbs]280 (apply merge-with merge-relations dbs))282 (defn database-merge-parallel283 "Merges databases together in parallel"284 [dbs]285 (preduce merge-relations dbs))288 ;; End of file