Skip to content

Instantly share code, notes, and snippets.

@jsmorph
Created July 6, 2012 14:01

Revisions

  1. jsmorph renamed this gist Jul 6, 2012. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. jsmorph renamed this gist Jul 6, 2012. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. jsmorph revised this gist Jul 6, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion lucenalog.clj
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,7 @@ lv2 to v2. That query string is then used in the Lucene query."
    (reduce str
    (interpose " AND "
    (map (fn [[k v]]
    (str (subs (str k) 1) ":" v))
    (str (name k) ":" v))
    (remove (comp logic/lvar? last)
    walked))))]
    (note :lucene-search walked q a :query query))
  4. jsmorph revised this gist Jul 6, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion lucenalog.clj
    Original file line number Diff line number Diff line change
    @@ -58,7 +58,7 @@ dispatch."
    "Query Lucene based on the given object and Substitutions. The
    query Q should be a map that has some values bound by substitutions A.
    The generated Lucene query string looks like 'p1:v1 AND p2:v2', where
    Q contains :p1 lv1 and :p2 lv2 and the substituions take lv1 to v1 and
    Q contains :p1 lv1 and :p2 lv2 and the substitutions take lv1 to v1 and
    lv2 to v2. That query string is then used in the Lucene query."
    ([q a]
    (db/search (index)
  5. jsmorph created this gist Jul 6, 2012.
    117 changes: 117 additions & 0 deletions lucenalog.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    (ns lucenalog.core
    "Lucenalog = Datalog interface to Lucene in 10 lines.
    Simple but powerful.
    Use
    (db/add (index) {:a \"foo\" :b \"bar\"})
    to index a map with Lucene. Then you can use the relation
    'lucenalog-rel' from core.logic. That relation works on maps. Bound map
    values produce a Lucene query, which returns matching maps. See
    'lucenalog-test' for an example."


    (:require [clucy.core :as db]
    [clojure.core.logic :as logic]))

    ;; (defproject lucenalog "0.0.1-SNAPSHOT"
    ;; :description "Lucenalog = Datalog interface to Lucene in 10 lines"
    ;; :dependencies [[org.clojure/clojure "1.2.1"]
    ;; [clucy "0.3.0"]
    ;; [org.clojure/core.logic "0.6.6"]
    ;; ]
    ;; :main lucenalog.core)


    (set! *warn-on-reflection* true)

    ;; Accessory functions. Not important.

    (let [m (ref {:max-query-results 1024
    :verbose true})]
    (defn config
    "Lucenalog configuration. Call with no args to see the current
    configuration. Get a configuration property's value by passing the
    property to this function. Change the config using the two-argument
    dispatch."
    ([] @m)
    ([k] (@m k))
    ([k v] (dosync (alter m assoc k v)))))


    (defonce index ;; Get the default Lucene index.
    (let [i (db/memory-index)]
    (fn [] i)))

    (defn- note [& args]
    "Println the args if (config :verbose). Return the last arg."
    (when (config :verbose)
    (apply println :note args))
    (last args))


    ;; Generate the Lucene query string.

    (defn- lucene-query
    "Query Lucene based on the given object and Substitutions. The
    query Q should be a map that has some values bound by substitutions A.
    The generated Lucene query string looks like 'p1:v1 AND p2:v2', where
    Q contains :p1 lv1 and :p2 lv2 and the substituions take lv1 to v1 and
    lv2 to v2. That query string is then used in the Lucene query."
    ([q a]
    (db/search (index)
    (let [walked
    (logic/walk* a q)
    query
    (reduce str
    (interpose " AND "
    (map (fn [[k v]]
    (str (subs (str k) 1) ":" v))
    (remove (comp logic/lvar? last)
    walked))))]
    (note :lucene-search walked q a :query query))
    (config :max-query-results))))

    ;; The core.logic relation.

    (defn lucenalog-rel [q]
    "A clojure.core.logic relation backed by Lucene. Lucene query
    generated by lucene-query based on the given map."
    (fn [a]
    (logic/to-stream
    (map #(logic/unify a % q)
    (lucene-query q a)))))


    ;; Tests and examples.

    (defn lucenalog-test
    "Simple check that Lucenalog is working. Returns true if things
    look okay."
    ([]
    ;; Let's use our own in-memory Lucene index.
    (binding [index (let [i (db/memory-index)]
    (fn [] i))]
    (with-open [i ^org.apache.lucene.store.Directory (index)]
    ;; Add a little data.
    (doseq [m [{:a "a1" :b "z1"}
    {:a "z1" :b "b1"}
    {:a "a1" :b "z2"}
    {:a "z1" :b "b2"}]]
    (db/add i m))
    ;; Check to see if we can chase a1 to b1 and b2 via z1.
    (let [expect #{"b1" "b2"}
    got (set
    (logic/run* [q]
    (logic/fresh [a b c]
    (logic/== a "a1")
    (lucenalog-rel {:a a :b b})
    (lucenalog-rel {:a b :b c})
    (logic/== q c))))
    passed? (= expect got)]
    (println :test passed? :got got :expected expect)
    passed?)))))

    (defn -main ([& args] (lucenalog-test)))