Can I pass a vector as an argument to datomic query? - datomic

if i had a schema (in pseudocode)
{:collection/name
:type "string"},
{:photo/collection
:type ref}
And I had a vector of 3 collections from which I wanted to see all photos.
Can I do something like below? (show me all photos that are in collection A, B, or C
:find [?p]
:where [?p :photo/collection [17592186045568 17592186045597 17592186045654]]
I get the following error when I try something like this..
IllegalArgumentExceptionInfo :db.error/invalid-lookup-ref Invalid list form: [[17592186045568 17592186045597 17592186045654]] datomic.error/arg (error.clj:55)
If I can't do this like this, is there some other way I can go about this?
Update:
I see in the tutorial there is a way to pass a vector using ... (btw is there a name for this kind of query?) and I tried
{:query {:find [?p ?coll], :where [[?p :photo/collection ?coll]], :in [$ [?coll ...]]}
With [17592186045568 17592186045597 17592186045654] as an input, but I still get the same error. It seems my syntax is correct because I did try this with another attribute (not a ref) and it worked ok. I'm just stuck on refs. There has to be a best practice for doing this?

You would do this using a collection binding. There are some simple illustrations of this here.
There's a collection restructuring section in Learn Datalog Today! (and problem 1 uses this, if you want to try to verify that the syntax of your solution is correct).
A similar approach to your problem might read:
(d/q '[:find ?pid ?collection
:in $ [?collection ...]
:where [?p :photo/collections ?collection]
[?p :photo/id ?pid]]
(d/db conn)
["A" "B" "C"])
The results would be something like this (assuming simple integer photo ids - I'm not sure how you're actually identifying photos):
[1 "A"]
[2 "A"]
[3 "B"]
If you just want entity id's, you should be able to use the simpler query:
[:find ?p
:in $ [?collection ...]
:where
[?p :photo/collection ?collection]]

Related

Clojure assoc/assoc-in

(def db-sample
{
:person [{:person/id 9 :name "rich" :surname "hickey" :join-date "04.04.2016" :experience :experience/lead :loyality-level :loyality-level/more-than-seven-years :work-type :work-type/tenure :work-time :work-time/part-time}]
:employees/developer-team [{:frontend [[:person/id 1] [:person/id 2] [:person/id 3] [:person/id 4]]
}
hello everyone, I making practice on assoc functions so I wanted to create a sample database just using assoc functions to do the practice.
I checked it's quick docs but there is no explanation about how can I create a vector and put data into it. I left an example on top, my question is how can I create db-sample data by using assoc functions? (or maybe easier better options)
In practice if you wanted to simulate a db it would first have to be an atom so you can update in place. And your "adding a person" would be something like:
(def db-sample (atom {:person [] :employees/developer-team []})
(swap! db-sample update :person #(conj % new-person))
Things can get tricky when your database is too nested - there are libraries for this such as specter. But keeping databases relatively flat is also good practice IMHO.

Difficulty initialising an array of strings in structured text

Structured text seems to delight in making the simple really difficult (much more used to C). Could somebody please rearrange this so it will compile.
VAR_GLOBAL
ErrorStg : Array[0 .. 10] of STRING[16] := "Good","Bad","Fsked";
END_VAR
Did you read the compiler error at all...?
The working version at least on CODESYS 3 based platforms:
ErrorStg : ARRAY[0 .. 10] OF STRING[16] := ['Good','Bad','Fsked'];
The working version at least on CODESYS 2 based platforms:
ErrorStg : ARRAY[0 .. 10] OF STRING[16] := 'Good', 'Bad', 'Fsked';
You should use ' instead of " with regular strings.
When you initialize an array on the definition line, you have to give all of the values (all 11 in your case).
As an alternative, I would suggest a much easier solution - use an init routine to assign the values. If you are opposed to “burning boot time”, you can still solve by defining a FB called InitGlobals and then define a FB_INIT method and put your assignments there. FB_INIT executes as soon as the object exists and not when your program runs. Add an instance of InitGlobals to your code, of course.

In Perl 6, can I use an Array as a Hash key?

In the Hash documentation, the section on Object keys seems to imply that you can use any type as a Hash key as long as you indicate but I am having trouble when trying to use an array as the key:
> my %h{Array};
{}
> %h{[1,2]} = [3,4];
Type check failed in binding to parameter 'key'; expected Array but got Int (1)
in block <unit> at <unknown file> line 1
Is it possible to do this?
The [1,2] inside the %h{[1,2]} = [3,4] is interpreted as a slice. So it tries to assign %h{1} and %{2}. And since the key must be an Array, that does not typecheck well. Which is what the error message is telling you.
If you itemize the array, it "does" work:
my %h{Array};
%h{ $[1,2] } = [3,4];
say %h.perl; # (my Any %{Array} = ([1, 2]) => $[3, 4])
However, that probably does not get what you want, because:
say %h{ $[1,2] }; # (Any)
That's because object hashes use the value of the .WHICH method as the key in the underlying array.
say [1,2].WHICH; say [1,2].WHICH;
# Array|140324137953800
# Array|140324137962312
Note that the .WHICH values for those seemingly identical arrays are different.
That's because Arrays are mutable. As Lists can be, so that's not really going to work.
So what are you trying to achieve? If the order of the values in the array is not important, you can probably use Sets as keys:
say [1,2].Set.WHICH; say [1,2].Set.WHICH
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
Note that these two .WHICHes are the same. So you could maybe write this as:
my %h{Set};
dd %h{ (1,2).Set } = (3,4); # $(3, 4)
dd %h; # (my Any %{Set} = ((2,1).Set) => $(3, 4))
Hope this clarifies things. More info at: https://docs.raku.org/routine/WHICH
If you are really only interested in use of an Object Hash for some reason, refer to Liz's answer here and especially the answers to, and comments on, a similar earlier question.
The (final1) focus of this answer is a simple way to use an Array like [1,'abc',[3/4,Mu,["more",5e6],9.9],"It's {<sunny rainy>.pick} today"] as a regular string hash key.
The basic principle is use of .perl to approximate an immutable "value type" array until such time as there is a canonical immutable Positional type with a more robust value type .WHICH.
A simple way to use an array as a hash key
my %hash;
%hash{ [1,2,3].perl } = 'foo';
say %hash{ [1,2,3].perl }; # displays 'foo'
.perl converts its argument to a string of Perl 6 code that's a literal version of that argument.
say [1,2,3].perl; # displays '[1, 2, 3]'
Note how spaces have been added but that doesn't matter.
This isn't a perfect solution. You'll obviously get broken results if you mutate the array between key accesses. Less obviously you'll get broken results corresponding to any limitations or bugs in .perl:
say [my %foo{Array},42].perl; # displays '[(my Any %{Array}), 42]'
1 This is, hopefully, the end of my final final answer to your question. See my earlier 10th (!!) version of this answer for discussion of the alternative of using prefix ~ to achieve a more limited but similar effect and/or to try make some sense of my exchange with Liz in the comments below.

Simpler way of giving a new value to an attribute

There is an attribute :organisation/ord. This is how I'm getting the data structure to pass to d/transact:
(assoc (d/pull db [:db/id] (:db/id organisation)) :organisation/ord new-org-ord)
;; => {:db/id 17592186045432, :organisation/ord 4198}
Here organisation is of type datomic.query.EntityMap and new-org-ord is an integer. This works fine but seems unwieldy. Is there simpler code that does the same job?
Thinking all I need do was turn EntityMap into a real map I tried this:
(assoc (into {} organisation) :organisation/last-invoice-ordinal new-org-ord)
But got:
:db.error/not-an-entity Unable to resolve entity: #:db{:id 17592186045433} in datom [-9223301668109597772 :organisation/timespan #:db{:id 17592186045433}]
This is simpler:
{:db/id (:db/id organisation), :organisation/ord new-org-ord}
And here's another alternative that also works:
(assoc (select-keys organisation [:db/id]) :organisation/ord new-org-ord)
It doesn't really make sense to be transacting with a map that has anything in it apart from a map-entry to identify the entity id you want to assert some new facts against, together with map-entries that represent those facts.

Clojure database unit testing / mocking

I just recently started working on a database-heavy Clojure application and am attempting to get some unit tests in place. Ideally, I'd like to avoid actually hitting a real database by mocking things out.
Here's an example of a simple test:
test-core.clj
(deftest core-test
(is (> (count (fn-under-test "foo")) 0)))
core.clj
(defn fn-under-test [slug]
(db/query "select * from %1" slug))
db.clj
(defn query [q & args]
(sql/with-connection db
(sql/with-query-results res
[(clause q args)]
(doall res))))
My question: is there a way, from within test-core.clj, to bind a custom function to 'db/query' such that core.clj will use it, as opposed to the definition within db.clj?
Thanks!
You can use binding to try and override db/query, but you'll need to define the namespace and var first. The easiest way is to import the db.clj into the same namespace and then use bindings.
(ns test-core
(:use clojure.test)
(:require db))
(deftest core-test
(binding [db/query (fn [query & args] (comment return some value here))]
(is (> (count (fn-under-test "foo")) 0))))

Resources