Conj in a rule fails on the transactor


#1

Hi everyone,

I have a hard time understanding why the following rule sometimes fail with a ClassCastException on the conj. Apparently ?parent-path is a Java array and not a IPersistentCollection.

This only happens in the transactor and not with d/with.

We would like to refer to nodes in a tree using their path from the root. I was hoping that a recursive rule like this would work:

(def node-path
  '[;; node without a parent
    [(node-path ?n ?p)
     [?n :node/name ?name]
     [(missing? $ ?n :node/parent)]
     [(vector ?name) ?p]]
    ;; node with a parent
    [(node-path ?n ?p)
     [?n :node/name ?name]
     [?n :node/parent ?parent]
     (node-path ?parent ?parent-path)
     [(conj ?parent-path ?name) ?p]]])

I can send a minimal schema and query to reproduce my use-case if needed.

My expectation was that a vector would always be built at the bottom of the recursion. ?p would be bound to the result of (vector ?name). And then we conj this vector until we reach the leaf. Was my expectation correct? If not, where is it wrong?

Thanks a lot for your help.


#2

To repro on the lastest Datomic free transactor.

(ns sandbox
  (:require [datomic.api :as d]))

(def db-uri "datomic:free://localhost:4334/sandbox")

(def schema
  [#:db{:ident :node/name
        :valueType :db.type/string
        :cardinality :db.cardinality/one}
   #:db{:ident :node/parent
        :valueType :db.type/ref
        :cardinality :db.cardinality/one}
   #:db{:ident :node/path->eid
        :fn #db/fn
        {:lang "clojure"
         :params [db path]
         :requires [[datomic.api :as d]]
         :code (ffirst
                (d/q '[:find ?e
                       :in $ % ?path
                       :where (node-path ?e ?path)]
                     db
                     '[[(node-path ?n ?p)
                        [?n :node/name ?name]
                        [(missing? $ ?n :node/parent)]
                        [(vector ?name) ?p]]
                       [(node-path ?n ?p)
                        [?n :node/name ?name]
                        [?n :node/parent ?parent]
                        (node-path ?parent ?parent-path)
                        [(conj ?parent-path ?name) ?p]]]
                     path))}}
   #:db{:ident :node/ensure-path
        :fn #db/fn
        {:lang "clojure"
         :params [db path parent?]
         :requires [[datomic.api :as d]]
         :code (if-let [eid (d/invoke db :node/path->eid db path)]
                 (when parent?
                   eid)
                 (let [name (last path)
                       parent-path (butlast path)]
                   [(cond-> {:db/id (d/tempid :db.part/user)
                             :node/name name}
                      (seq parent-path)
                      (assoc :node/parent
                             (d/invoke db :node/ensure-path
                                       db parent-path true)))]))}}])

(defn connect
  []
  (d/create-database db-uri)
  (let [conn (d/connect db-uri)]
    @(d/transact conn schema)
    conn))

(def test-data
  [["1"
    ["1A"]
    ["1B"
     ["1B1"]
     ["1B2"]]]
   ["2"]])

(defn plex-tx
  ([data]
   (plex-tx data nil))
  ([data parent]
   (mapcat (fn [[name & children]]
             (let [tempid (d/tempid :db.part/user)
                   node (cond-> {:db/id tempid
                                 :node/name name}
                          parent (assoc :node/parent parent))]
               (into [node] (plex-tx children tempid))))
           data)))

(comment

  (def conn (connect))
  @(d/transact conn (plex-tx test-data))

  (def db (d/db conn))
  (d/invoke db :node/path->eid db ["1" "1B" "1B1"])

  @(d/transact conn [[:node/ensure-path ["1" "1B" "1B3"] false]])
  @(d/transact conn [[:node/ensure-path ["1" "1B"] false]])

  ;; This tx throws
  @(d/transact conn [[:node/ensure-path ["1"] false]])

  )