How have you all handled the process of "transferring" one Datomic database into another? My situation is thus: - I have an application database - There are some flat files out there in the world which I need to do an ETL process on in order to import the data contained therein into our database. - To do this, I create a temporary Datomic database with the same schema as the app database, perform the ETL on the flat file, and transact it into the temp db. The temp db is necessary because there's a lot of garbage in these flat files, and the user needs to pick and choose what they actually want to keep from the import. All of the above works pretty well. The issue I'm having is how to take those selections and efficiently process them and bring them over to the app DB. Naievely, a query + a pull is a great way to go. Pull gives you something that's a really close analog for a Datomic transaction (reverse references excluded):
(->>
(d/query {:query '{:find [?e]
:in [$ ?uuid]
:where [[?e :unique/id ?uuid]]}
:args [tmpdb uuids]})
(d/pull-many tmpdb [:db/id
:person/name
{:person/_friend [*]}]))
;; This comes out of the DB, AND could theoretically just be transacted as is, excepting the reverse reference problem
[{:db/id "aaa"
:person/name "Joey"
:person/_friend {:person/name "Andy"
:db/id "bbb"
:person/friend "aaa"}}
{:db/id "bbb"
:person/name "Andy"
:person/_friend {:person/name "Joey"
:db/id "aaa"
:person/friend "bbb"}}]
But a problem instantly arises in that the "transaction-version" of what you've pulled would duplicate any entity refs that are in that pull.
So the other option that arises is to query for a flat list of entities, which ensures that no duplication occurs, and stringify the DB refs in order to establish those ref relationships in the app DB:
(->> (d/query {:query '{:find [?e]
:in [$ ?uuid]
:where [[?e :unique/id ?uuid]]}
:args [tmpdb uuids]})
(d/pull-many tmpdb [*]))
;; This comes out of the DB query
[{:db/id "aaa"
:person/name "Joey"
:person/friend {:db/id "bbb"}}
{:db/id "bbb"
:person/name "Andy"
:person/friend {:db/id "aaa"}}]
;; After a little processing, this is the transaction:
[{:db/id "aaa"
:person/name "Joey"
:person/friend "bbb"}
{:db/id "bbb"
:person/name "Andy"
:person/friend "aaa"}]
But as the ref relationships get deeper, more complex, more intertwined, I'm a little concerned about writing a bunch of bespoke queries for individual entity types in order to achieve this flat list. The "pull everything and rely on Datomic's ability to take a map of nested maps and run THAT as the transaction" is super appealing in contrast, but comes with the duplication problem.Hmm, thinking of it a bit more I think I did aggregate the set of datoms building my own small walker that walked the pull expression on its own and aggregated the datoms (or just EAVs) starting from a d/entity. My walker could only handle the naive subset of the pull expression, but that was enough at the time.
One way to pick a subset of a datomic database is to make all the pull expressions (or whatever you do), and make sure that you can transform that query result to {E A V}-typed data (as a set of [E A V] or. more optimized, as a map {E {A #{V ...}}}). Then you could do a datomic.api/filter on that database that looked up the datoms (make sure to get the added? correct) and do d/datoms :eavt on that filtered database. When adding all this to the real database, you might need to do it in batches. if so you need to keep track of the EIDs in the temporary database and how they where realized in at transaction time, then preprocess the upcoming batches (E and, when A is ref, also the V). Maybe you can be clever about how you split the transaction data so it makes more sense (ie not take 1000 datoms at the time, but all the entities that can fit into 1000 datoms, ie not split entities over two transactions). It is not very complicated to walk a "pulled expression", but you might need to be aware of the initial pull expression, the attribute cardinality etc.