@konrad.kuehne this is my working thought process for the tuples: https://github.com/tonsky/datascript/issues/323 The only difference that I can see for Datahike is that the syntax of the schema would match the Datomic schema. I'm concerned that I'm not understanding the full behavior of how tuples work with upserts and queries, though. If you or anyone else in the community has feedback I'd love to hear it.
To me the major value seems to be more convenient syntax for storing refs in tuples (aka vectors)
They're actually very useful for upsert if you mark them db unique identity, and that's the expected behavior with Datomic.
would it be possible to give an example @metasoarous? 😅 For instance, if it's a unique identity, how would an upsert work?
(d/transact conn [{:ref/a+b+c [:a :b :c]})
it's unclear to me, if :ref/a+b+c
is a unique identity, how you could modify thatwould it work like this?
(d/transact conn [{:db/id [:ref/a+b+c [:a :b :c]] :ref/a+b+c [:a :b :not-c]}])
No; It would work like (d/transact conn [{:a "blah" :b 42 :c :dragonz :extra-data :foo}])
If you have a composite tuple of :a :b :c
, then its going to upsert that on whatever existing entity has those particular values
Definitely a major use case
I'm obviously super confused, because :a
I thought was a value, a keyword. You're saying that the keyword would resolve to an entity?
?
It doesn't matter what the types are
Works the same
there's some bit of info I'm missing, so I'm glad I'm asking this.
I think you could do it the way you're talking about, where you upsert using [:ref/a+b+c ...]
as a lookup ref, but I think the intention is that you don't have to think about the tuple so much
I'm not sure how
{:a "blah" :b 42 :c :dragonz :extra-data :foo}
relates to
{:ref/a+b+c [:a :b :c]}
😅
I'm sorry if it's obvious
would the schema be
{
:a {:db/unique :db.unique/identity}
:b {:db/unique :db.unique/identity}
:c {:db/unique :db.unique/identity}
:ref/a+b+c {:db/valueType :db.valueType/tuple
:db/tupleTypes [:db.type/keyword :db.type/keyword :db.type/keyword]
:db/unique :db.unique/identity}
}
?In the world I'm imagining, none of :a :b :c
are identity (since then it wouldn't make sense to upsert on a tuple of them; one would be enough!)
The composite tuple would be the identity
That would make it so that asserting facts about an entity with a particular set of :a :b :c
values would upsert based on the implied tuple.
gotcha. Okay so here's my question effectively
Let's say I do this
Here's an example from the docs:
{:db/ident :reg/semester+course+student
:db/valueType :db.type/tuple
:db/tupleAttrs [:reg/course :reg/semester :reg/student]
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
tupleTypes is not needed here
Because its implicit on tupleAttrs
(d/transact conn [{:a "blah" :b 42 :c :dragonz :extra-data :foo}])
then
(d/transact conn [{:a "heyyyy" :b 42 :c :dragonz :extra-data :foo}])
Maybe that's the part we're not connecting on
how do I know that I'm doing an upsert and not inserting a new tuple?
In those cases, you would be creating a new entity
It would upsert though if you did the following
so how would I upsert on that tuple?
ah sorry
(d/transact conn [{:a "blah" :b 42 :c :dragonz :extra-data :foo}])
then
(d/transact conn [{:a "blah" :42 :c :dragonz :extra-data :BAR!}])
ahhhh I see
It's basically the equivalent of a multi-column constraint or index in a table-based database
So you can't change the tuple identity
got it
that makes it easy
Well, you can
if it's a ref?
You would have to use the :db/id
of the entity in that case, and then could chance one of a b c
aha
so {:db/id <whatever> :a :jazz}
but you never change the tuple directly
the engine manages that
?
If it's a composite tuple (that is, with :db/tupleAttrs
), you at least don't need to change the tuple directly. I actually don't know for sure whether you can update the individual attrs by updating the tuple (something to test), but the bottom line is that they will always be in sync
For a regular tuple (one not created with :db/tupleAttrs
, you can certainly change the tuple however you like
Just by manually asserting facts about the tuple attr in question
okay given this example
(d/transact conn [{:a "blah" :42 :c :dragonz :extra-data :BAR!}])
what would the schema be?
{:ref/a+b+c
{:db/valueType :db.type/tuple
:db/tupleAttrs [:a :b :c]
:db/unique :db.unique/identity}
}
Yes; That looks right to me
cool, makes sense to me
Thank you @metasoarous!
Sure thing! Thank you for working on this 🙂
Would you agree with the following behavior?
(def conn (d/empty-db
{:ref/a+b+c
{:db/unique :db.unique/identity
:db/valueType :db.type/tuple
:db/tupleAttrs [:a :b :c]}}))
(d/transact conn [{:a "a" :b "b" :c "c" :d "d"}]) ;;=> insert tuple
(d/transact conn [{:a "not-a" :b "b" :c "c" :d "d"}]) ;;=> insert different tuple
(d/transact conn [{:a "a" :b "b" :c "c" :d "not-d"}]) ;;=> modifies :d
(d/transact conn [{:db/id [:ref/a+b+c [:a :b :c]] :a "not-a" :b "not-b" :c "not-c"}]) ;;=> modifies tuple values, unrelated to tuple ["not-a" "b" "c"]
Yup; That all checks out to me
I don't see anything in the docs about any changes to query or pull behavior.
i.e., this makes sense to me but I'm not sure if this is the behavior:
(d/q '[:find (pull ?e [*]) .
:where
[_ :reg/semester+course+student [_ _ ?e]]) ;;=>
{:db/id 123456
:student/first "John"
:student/last "Doe"
:student/email "<mailto:johndoe@university.edu|johndoe@university.edu>"}
(d/q '[:find (pull ?e [*]) .
:where
[?e :db/id [:reg/semester+course+student [234561 345621 123456]]
@conn) ;;=>
[{:db/id 234561
:semester/year 2018
:semester/season :fall}
{:db/id 345621
course/id "BIO-101"}
{:db/id 123456
:student/first "John"
:student/last "Doe"
:student/email "<mailto:johndoe@university.edu|johndoe@university.edu>"}]
I honestly don't know if you can pattern match into tuples like that. Worth trying. I suspect not, but I could totally be wrong.
You should be able to query by the literal tuple of ids though (as in the second case)
(though I don't think you'd get back what you're thinking; you'd get back the registration)
Important to note that ref tuples aren't super smart about resolving lookup refs and the like; You kind of have to use explicit db/id values, as you do in the second example.
thanks, that last point is especially important -- saves a whole bunch of unneeded code
Ah, found it: https://docs.datomic.com/cloud/query/query-data-reference.html#tuple
Note untuple
as well
tuple
creates a tuple.
https://docs.datomic.com/cloud/query/query-data-reference.html#tuple
There's untuple
for unpacking them, but I'm not clear on how it's distinct from using identity
in a function expression. https://docs.datomic.com/cloud/query/query-data-reference.html#untuple
i.e.
[(tuple ?t) [?a ?b ?c]]
seems equivalent to
[(identity ?t) [?a ?b ?c]]