This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-08
Channels
- # aleph (2)
- # announcements (2)
- # asami (50)
- # babashka (39)
- # beginners (17)
- # calva (61)
- # cider (9)
- # clj-kondo (5)
- # clojure (37)
- # clojure-europe (52)
- # clojure-nl (1)
- # clojure-norway (14)
- # clojure-uk (5)
- # clojurescript (28)
- # cursive (3)
- # datahike (11)
- # datomic (28)
- # deps-new (11)
- # events (3)
- # fulcro (18)
- # google-cloud (1)
- # graphql (8)
- # introduce-yourself (4)
- # jobs (2)
- # leiningen (7)
- # lsp (15)
- # pathom (9)
- # re-frame (6)
- # reagent (35)
- # reitit (17)
- # releases (1)
- # shadow-cljs (20)
- # specter (1)
- # test-check (106)
- # tools-deps (8)
- # uncomplicate (1)
- # vim (29)
Really happy with Asami, but still a bit of a n00b with the queries. How do I dynamically construct a query that ANDs a bunch of triples together? i.e. I have a variable set of mentions that I want to use to build a query such that the query looks/works like
(d/q [:find '[?path ...]
:where
(and ['?e :document/mention m_1]
['?e :document/mention m_2]
['?e :document/mention ...]
['?e :document/mention m_n-1]
['?e :document/mention m_n])
'[?e :file/extension "xml"]
'[?e :file/path ?path]]
conn)
I am unsure how to build a query that accomplishes this. Currently trying to build the and
clause on in a let
, inserting it into the rest of the query where every symbol is now individually quoted, but this is quite messy. Surely there is a better way?
Help please! :Soh wait, I guess the AND
is actually implicit, so one could do this right?
(d/q (into '[:find [?path ...]
:where
[?e :file/extension "xml"]
[?e :file/path ?path]]
(for [m mention]
['?e :document/mention m]))
conn)
Am I on the right track? Or is there a syntax shortcut to match against a set of values?Yes, the AND is implicit at the top level.
Your previous query could work with ('and ...)
also, I presume that the ['?e :document/mention ...]
doesn't actually have an ellipses in it, right? Because that wouldn't work
You don't need a for
there. It's more idiomatic to map
it instead:
(map #(vector '?e :document/mention %) mention)
Ok, but the principle is the same 🙂 I’m mostly wondering if there was a syntax shortcut to match against a set of values.
Also, if you're constructing a query, you can use a map object instead:
(d/q {:find '[?path ...]
:where
(into [[?e :file/extension "xml"]
[?e :file/path ?path]]
(map #(vector '?e :document/mention %)))}
conn)
Hmmm... I can only think of how you can match when ?e
is to be matched against ANY of the mentions, not all of them 😕
(d/q '[:find [?path ...]
:in $ [?m ...]
:where
[?e :file/extension "xml"]
[?e :file/path ?path]]
[?e :document/mention ?m]))
conn)
The various :in
forms are described in the https://docs.datomic.com/on-prem/query/query.html#bindings
Which I should ALSO describe in the Asami docs, because Asami does do things differently in many cases. But the :in
forms are the same
Yeah, I am also doing a bunch of RDF queries in Aristotle (i.e. Apache Jena) in my other project, so I get to learn all the subtle differences between RDF, Asami, and Datomic 😛
Ah... I've been motivated to wrap Asami in RDF/SPARQL. That will be happening soon
Thanks for this comment! I'm not always motivated to spend time on Asami, but this helps a lot ❤️
Personally, I wouldn’t be querying it using SPARQL, but I can definitely see the value in that
SPARQL is important, because it provides a clean way for people to access it outside of Clojure
Just to say I’d be very interested in seeing this 👌
Well, I'm hoping it's not TOO big a deal. Most of the operations have a 1:1 mapping, and Asami includes things like BIND, OPTIONAL and so on. Also, the not
operator is actually just the SPARQL MINUS operator, rather than the precise semantics of Datomic's not
.
My approach will be to convert the grammar to Instaparse, then map the resulting structures to the current query language, since that language is:
a) already a Clojure structure, which is what lexing/parsing is trying to do
b) already in a form that can be executed by the query engine
@U051N6TTC: We already have a very complete instaparse grammar that converts SPARQL into an EDN based AST representation, if you’re interested
I can’t promise it’s 100% complete; but IIRC it’s pretty close
e.g.
(-> "
SELECT DISTINCT ?_ WHERE {
?s1 <<http://p>>/<> ?o1 .
?s2 a ?o2 .
?s3 uk-wfd:RiverBasinDistrict ?o3 .
?s4 uk-wfd: ?o4
}
ORDER BY ASC (?s)
LIMIT 10
"
parse
clojure.pprint/pprint)
;; =>
{:type :select,
:modifier "DISTINCT",
:projection
[{:type :qvar, :x ?s} {:type :qvar, :x ?p} {:type :qvar, :x ?o}],
:expr
[{:type :triple,
:s {:type :qvar, :x ?s},
:p #object[grafter_2.rdf4j.sparql.path.Group 0x14367ef3 "[email protected]"],
:o {:type :qvar, :x ?o}}
{:type :triple,
:s {:type :qvar, :x ?a},
:p {:type :qvar, :x ?b},
:o {:type :qvar, :x ?c}}],
:modifiers
[{:type :order-by, :modifier "ASC", :expr {:type :qvar, :x ?s}}
{:type :limit, :n 10}]}
Not that I want to take the fun out of writing such thing from you 😆
There's an ego thing of building it from scratch. And for better or worse, people don't like external libs 😕 Asami is remarkably self-contained (many of the libs are also written by me)
Sure… The code isn’t public yet; but you could always inline some bits if you found it useful… e.g. even just the file of instaparse compatible ebnf
as iirc you can’t quite just copy/paste the grammar from the sparql spec
then there’s about 300 lines of clojure to convert it into the edn ast representation — which you could choose to ignore or not
I didn’t write it, a colleague did — so I’d want to run it by him first, but the plan was to open the code at some point anyway.
it could pretty easily be cut down to have minimal dependencies
(defn- entity->where-triples
"Deconstruct partial `entity` description into triples for a search query."
[entity]
(->> (for [[k v] entity]
(if (coll? v)
(map #(vector '?e k %) v)
[['?e k v]]))
(apply concat)
(set)))
(defn- entity->search-query
"Build an entity search query from a partial `entity` description."
[entity]
(into '[:find [?id ...]
:where [?e :db/ident ?id]]
(entity->where-triples entity)))
(defn match-entity
"Look up entity IDs in `conn` matching partial `entity` description."
[conn entity]
(d/q (entity->search-query entity) conn))
(defn search
"Return matching entities in `conn` based on partial `entity` description."
[conn entity]
(map (partial d/entity conn) (match-entity conn entity)))
Here’s what I ended up doing. Works for my purposes as long as I am not having to do OR queries.