Skip to content

v0.10.0 Optional ref (left join)

Compare
Choose a tag to compare
@marcgrue marcgrue released this 11 Aug 14:49
· 29 commits to main since this release

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]]