v0.10.0 Optional ref (left join)
Molecule now lets you make an optional cardinality-one reference to another namespace with ?
. Data is returned as an Option
with a value or tuple of ref attribute value(s):
A.i(1).save.transact
// Optional card-one ref (SQL left join) - no ref values as None
A.i.B.?(B.i).query.get ==> List(
(1, None),
)
A.i(2).B.i(3).s("b").save.transact
// Optional card-one ref (SQL left join) - values in optional tuple
A.i.B.?(B.i.s).query.get ==> List(
(1, None),
(2, Some((3, "b"))),
)
The query translates to the following SQL:
SELECT DISTINCT
A.i, B.i, B.s
FROM A
LEFT JOIN B ON A.b = B.id
WHERE
A.i IS NOT NULL;
Nested optional refs
We can "nest" optional references, also in inserts:
A.i.B.?(B.s.i.C.?(C.s.i)).insert(List(
(1, None),
(2, Some(("b", 2, None))),
(3, Some(("b", 3, Some(("c", 3))))),
)).transact
A.i.B.?(B.s.i.C.?(C.s.i)).query.get ==> List(
(1, None),
(2, Some(("b", 2, None))),
(3, Some(("b", 3, Some(("c", 3)))))
)
Which translates to 2 left joins, A -> B and B -> C
SELECT DISTINCT
A.i, B.s, B.i, C.s, C.i
FROM A
LEFT JOIN B ON A.b = B.id
LEFT JOIN C ON B.c = C.id
WHERE
A.i IS NOT NULL;
Adjacent optional refs
Use "adjacent" optional ref to make multiple optional refs/left joins from the initial namespace (A). Notice how a new definition starts outside the previous optional ref parenthesis.
A.i
.B.?(B.i.s)
.C.?(C.s.i).insert(List(
(1, None, None),
(2, Some((20, "b")), None),
(3, None, Some(("c", 300))),
(4, Some((40, "b")), Some(("c", 400))),
)).transact
A.i
.B.?(B.i.s)
.C.?(C.s.i).query.get ==> List(
(1, None, None),
(2, Some((20, "b")), None),
(3, None, Some(("c", 300))),
(4, Some((40, "b")), Some(("c", 400))),
)
Which translates to 2 left joins, A -> B and A -> C
SELECT DISTINCT
A.i, B.i, B.s, C.s, C.i
FROM A
LEFT JOIN B ON A.b = B.id
LEFT JOIN C ON A.c = C.id
WHERE
A.i IS NOT NULL;
In Datomic optional ref data is pulled (dummy default "__none__" value used for consistent output arities)
Nested:
[:find ?b
(
pull ?id1 [
{(:A/b :default "__none__") [
(:B/s :default "__none__")
(:B/i :default "__none__")
{(:B/c :default "__none__") [
(:C/s :default "__none__")
(:C/i :default "__none__")
]}
]
}
]
)
:where [?a :A/i ?b]
[(identity ?a) ?id1]]
Adjacent:
[:find ?b
(
pull ?id1 [
{(:A/b :default "__none__") [
(:B/i :default "__none__")
(:B/s :default "__none__")
]}
]
)
(
pull ?id2 [
{(:A/c :default "__none__") [
(:C/s :default "__none__")
(:C/i :default "__none__")
]}
]
)
:where [?a :A/i ?b]
[(identity ?a) ?id1]
[(identity ?a) ?id2]]