plunder

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

datom.sire (23844B)


      1 ; Copyright 2023 The Plunder Authors
      2 ; Use of this source code is governed by a BSD-style license that can be
      3 ; found in the LICENSE file.
      4 
      5 #### datom <- sandbox
      6 
      7 ;;;; Datom
      8 ;;;; =====
      9 ;;;;
     10 ;;;; Embeddable Entity/Attribute/Value datastore.
     11 
     12 
     13 ;;; Imports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
     14 
     15 :| sire
     16 :| hitch
     17 :| mutrec
     18 :| stew
     19 
     20 
     21 ;;; Utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
     22 
     23 = (fromSingletonSet opt bad gud)
     24 | if (neq 1 | setLen opt) bad
     25 | **gud (setMin opt)
     26 
     27 
     28 ;;; Types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
     29 
     30 ;; for v0, all trees are Tabs and Sets, not hitch stuff. doing all the hitch
     31 ;; stuff will require adding a `Multimap a b c -> Map a (Map b (MapSet c))`
     32 ;; typw.
     33 
     34 # data Error
     35 - UNIQUE_VIOLATION attrName/Any attrVal/Any oldEid/Nat newEid/Nat
     36 - LOOKUP_NO_ATTRIBUTE attrName/Any
     37 - LOOKUP_ATTRIBUTE_NOT_UNIQUE attrName/Any
     38 - LOOKUP_NO_MATCH attrName/Any val/Any
     39 ;
     40 - INVALID_ROW row/(Row Any)
     41 - INVALID_ROW_OP op/Any
     42 ;
     43 - BAD_MAP_VAL val/Any
     44 ;
     45 - PULL_NOT_ROW val/Any
     46 - BAD_PULL_TERM val/Any
     47 
     48 ;; The toplevel DB type.
     49 ;;
     50 ;; for v0, all tables are (Tab Any (Tab Any (Set Any))).
     51 # record Db
     52 | DB
     53 * eav    : Any
     54 * aev    : Any
     55 * ave    : Any
     56 * vae    : Any
     57 * maxEid : Nat
     58 
     59 ; raw operation to add a resolved tuple to the database
     60 > Nat > Nat > Any > Bit > Bit > Bit > Db > Db
     61 = (rawAdd e a v writeAve many isRef db)
     62 @ DB(..) db
     63 ; only usable for tuples which end in [v] (eav, aev)
     64 @ (alterEndVal c in)
     65     | SOME
     66     : set < maybeCase in (setSing c)
     67     | if many | setIns c set
     68     | setSing c
     69 ; only usable for tuples whose middle is not v (eav, aev)
     70 @ (alterNotVal b c in)
     71     | SOME
     72     : tab
     73         < maybeCase in
     74         | tabSing b (setSing c)
     75     | tabAlter (alterEndVal c) b tab
     76 ; only usable for tuples whose middle IS v (ave)
     77 @ (alterEndNonV c in)
     78     | SOME
     79     : set < maybeCase in (setSing c)
     80     | setIns c set
     81 ; only usable for tuples whose middle IS v (ave)
     82 @ (alterMiddleEndNonV b c in)
     83     | SOME
     84     : tab < maybeCase in
     85           | tabSing b (setSing c)
     86     | tabAlter (alterEndNonV c) b tab
     87 @ eav | tabAlter (alterNotVal a v) e eav
     88 @ aev | tabAlter (alterNotVal e v) a aev
     89 @ ave
     90      | ifNot writeAve ave
     91      | tabAlter (alterMiddleEndNonV v e) a ave
     92 @ vae
     93     | ifNot isRef vae
     94     | tabAlter (alterMiddleEndNonV a e) v vae
     95 | DB eav aev ave vae maxEid
     96 
     97 ; raw remove
     98 > Nat > Nat > Any > Db > Db
     99 = (rawRm e a v db)
    100 @ DB(..) db
    101 @ (alter2 c in)
    102     : set < maybeCase in NONE
    103     @ rmed | setDel c set
    104     | if (setIsEmpty rmed) NONE
    105     | SOME rmed
    106 @ (alter b c in)
    107     : tab < maybeCase in NONE
    108     @ rmed | tabAlter (alter2 c) b tab
    109     | if (tabIsEmpty rmed) NONE
    110     | SOME rmed
    111 @ eav | tabAlter (alter a v) e eav
    112 @ aev | tabAlter (alter e v) a aev
    113 ; always try to purge values from the ave table; the user might have changed
    114 ; the value of :db/unique between add and retract.
    115 @ ave | tabAlter (alter v e) a ave
    116 | DB eav aev ave vae maxEid
    117 
    118 = dbIdentId 0
    119 = dbIdentStr {:db/ident}
    120 = dbCardinalityId 1
    121 = dbCardinalityStr {:db/cardinality}
    122 = dbUniqueId 2
    123 = dbUniqueStr {:db/unique}
    124 = dbIndexedId 3
    125 = dbIndexedStr {:db/indexed}
    126 = dbValueTypeId 4
    127 = dbValueTypeStr {:db/valueType}
    128 = dbDocId 5
    129 = dbDocStr {:db/doc}
    130 
    131 = dbIdId 100  ; TODO: should be 0
    132 = dbIdStr {:db/id}
    133 
    134 = emptyDB
    135 @ db
    136     @ eav    #[]  ;  tab
    137     @ aev    #[]  ;  tab
    138     @ ave    #[]  ;  tab
    139     @ vae    #[]  ;  tab
    140     @ maxEid 6
    141     | DB eav aev ave vae maxEid
    142 @ (baseAdd e a v db)
    143     | rawAdd e a v TRUE TRUE FALSE db
    144 ; an "empty" db still needs to have some "built-in" tuples to allow for
    145 ; attribute resolution. each attribute still has an eid.
    146 ;
    147 ; :db/ident is the name identity of an attribute. in the eav tuple
    148 ; `[5 {:my/attr} {str}], we will first do an attribute/value lookup on
    149 ; [{:db/ident} {:my/attr}] and will use the entity number in storage.
    150 ;
    151 ; TODO: Must validate that incoming data type is a cord when I add value types.
    152 | baseAdd dbIdentId dbIdentId dbIdentStr
    153 | baseAdd dbIdentId dbUniqueId TRUE
    154 ;
    155 ; :db/unique is whether values for this attribute must be unique. It defaults
    156 ; to false. When true, and a transaction attempts to commit an already existing
    157 ; value, errors to the user.
    158 | baseAdd dbUniqueId dbIdentId dbUniqueStr
    159 ;
    160 ; :db/indexed is whether values for this attribute are indexed, ie, whether you
    161 ; can look up an eid by their attribute and value. :db/unique implies
    162 ; :db/indexed.
    163 | baseAdd dbIndexedId dbIdentId dbIndexedStr
    164 ;
    165 ; :db/cardinality is "one" or "many". When adding a tuple whose attribute has
    166 ; cardinality "one", the value is overwritten. When "many", multiple values
    167 ; accumulate.
    168 ;
    169 ; TODO: validate the values "one" or "many" once I implement :db/attr.preds.
    170 | baseAdd dbCardinalityId dbIdentId dbCardinalityStr
    171 ;
    172 ; :db/valueType is a string describing the type. For now, the values are nat,
    173 ; any and ref.
    174 | baseAdd dbValueTypeId dbIdentId dbValueTypeStr
    175 ;
    176 ; :db/doc is a user visible documentation string.
    177 | baseAdd dbDocId dbIdentId dbDocStr
    178 db
    179 
    180 # data EntIdent
    181 ; Specific numeric entity
    182 - ENT entityId/Any
    183 ; New entity referred to multiple times by this value
    184 - TMP val/Any
    185 ; Entity id based on an attribute lookup.
    186 - LOOKUP attr/Any val/Any
    187 
    188 # data TxnAction
    189 - ACTADD eid/Nat aid/Nat attrName/Any val/Any writeAve/Bit unique/Bit
    190          many/Bit isRef/Bit
    191 - ACTRM  eid/Any aid/Any val/Any
    192 
    193 ; Return a new entity id and record the new max in the database.
    194 > Db > [Nat Db]
    195 = (allocEntity db)
    196 @ DB(..) db
    197 ++ maxEid
    198 ++ | DB eav aev ave vae (inc maxEid)
    199 
    200 = (singletonSetDefault opt missing next)
    201 | **next
    202 | if (neq 1 | setLen opt) missing
    203 | setMin opt
    204 
    205 ; Internal functions on
    206 > Nat > Nat > Db > Set Any
    207 = (lookupByEidAid eid aid db)
    208 @ DB(..) db
    209 : av < maybeCase (tabLookup eid eav) %[] ; result is set
    210 : v  < maybeCase (tabLookup aid av) %[]  ; result is set
    211 | v
    212 
    213 > Nat > Nat > Db > Set Any
    214 = (lookupByAidEid aid eid db)
    215 @ DB(..) db
    216 : ev < maybeCase (tabLookup aid aev) %[]  ; result is set
    217 : v  < maybeCase (tabLookup eid ev) %[]   ; result is set
    218 | v
    219 
    220 > Nat > Nat > Db > Set Nat
    221 = (lookupByAidV aid val db)
    222 @ DB(..) db
    223 : ve < maybeCase (tabLookup aid ave) %[] ; result is set
    224 : e  < maybeCase (tabLookup val ve) %[]  ; result is set
    225 | e
    226 
    227 > Any > Nat > Db > Set Nat
    228 = (lookupByVAid val aid db)
    229 @ DB(..) db
    230 : ae < maybeCase (tabLookup val vae) %[] ; result is set
    231 : e  < maybeCase (tabLookup aid ae) %[]  ; result is set
    232 e
    233 
    234 ; Given a user level attribute name, translate that to a numeric attribute
    235 ; number and an 'indexed' boolean which is whether we can use the ave table to
    236 ; lookup data related to this attribute.
    237 ;
    238 > Str > Db > Either Error [Nat Bit Bit Bit Bit]
    239 = (resolveAttrName attrName db)
    240 ;| trk [%resolve attrName]
    241 @ attrsSet | lookupByAidV dbIdentId attrName db
    242 : attrEid
    243     < **fromSingletonSet attrsSet
    244     | LEFT | **LOOKUP_NO_ATTRIBUTE attrName
    245 @ isIndex
    246     @ attrIndexed | lookupByAidEid dbIndexedId attrEid db
    247     : indexed < **singletonSetDefault attrIndexed FALSE
    248     indexed
    249 @ isUnique
    250     @ attrUniq | lookupByAidEid dbUniqueId attrEid db
    251     : uniq     < **singletonSetDefault attrUniq FALSE
    252     uniq
    253 @ isMany
    254     @ attrMany | lookupByAidEid dbCardinalityId attrEid db
    255     : many     < **singletonSetDefault attrMany {many}
    256     # switch many
    257     * one  FALSE
    258     * many TRUE
    259     * _    | {unknown many value: } many
    260 @ isRef
    261     @ attrValType | lookupByAidEid dbValueTypeId attrEid db
    262     : t           < **singletonSetDefault attrValType {any}
    263     # switch t
    264     * ref TRUE
    265     * _   FALSE
    266 | RIGHT [attrEid isIndex isUnique isMany isRef]
    267 
    268 ; User level manual lookup
    269 > Nat > Any > Db > Set Any
    270 = (lookupByEA eid attr db)
    271 : aid
    272     < **fromSingletonSet (lookupByAidV dbIdentId attr db)
    273     | {unknown attr} attr
    274 | lookupByEidAid eid aid db
    275 
    276 > Any > Nat > Db > Set Any
    277 = (lookupByAE attr eid db)
    278 : aid
    279     < **fromSingletonSet (lookupByAidV dbIdentId attr db)
    280     | {unknown attr} attr
    281 | lookupByAidEid aid eid db
    282 
    283 > Any > Any > Db > Set Nat
    284 = (lookupByAV attr val db)
    285 # case (resolveAttrName attr db)
    286 - LEFT errs
    287     | {could not lookup. errors: } errs
    288 - RIGHT [aid indexed unique _]
    289     | ifNot (or indexed unique)
    290         | {cannot lookup unindexed attr/val} attr
    291     | lookupByAidV aid val db
    292 
    293 > Any > Any > Db > Set Nat
    294 = (lookupByVA val attr db)
    295 # case (resolveAttrName attr db)
    296 - LEFT errs
    297     | {could not lookup. errors: } errs
    298 - RIGHT [aid _ _ _ ref]
    299     | ifNot ref
    300         | {cannot lookup non-ref values} attr
    301     | lookupByVAid val aid db
    302 
    303 > Any > Any > Nat > Db > Set Nat
    304 = (lookupByIdentA identAttr identVal attr db)
    305 # case | resolveAttrName identAttr db
    306 - LEFT err
    307     | {could not lookup. error: } err
    308 - RIGHT [aid _ unique _]
    309     | ifNot unique | {cannot lookup unindexed attribute } identAttr
    310     @ eidSet | lookupByAidV aid identVal db
    311     : eid < **fromSingletonSet eidSet | {invalid eid for aid?! } aid
    312     | lookupByEA eid attr db
    313 
    314 
    315 ;;; User Transact Parsing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    316 
    317 ; Resolve a lookup ref into an eid or error.
    318 ;
    319 > Str > Any > Db > Either Error Nat
    320 = (performLookup attrName val db)
    321 # case | resolveAttrName attrName db
    322 - LEFT err
    323     | LEFT err
    324 - RIGHT [aid _ unique _ _]
    325     | ifNot unique
    326         | LEFT | **LOOKUP_ATTRIBUTE_NOT_UNIQUE attrName
    327     @ eids | lookupByAidV aid val db
    328     @ l | setLen eids
    329     | ifz l
    330         | LEFT | **LOOKUP_NO_MATCH attrName val
    331     | RIGHT | setMin eids
    332 
    333 ; Immediately resolves an entity to a numeric entity id.
    334 ;
    335 ; 5               -> RIGHT [5 ...]
    336 ; [{:b/first} 5]  -> RIGHT [9 ...] (assuming underlying LOOKUP succeeds)
    337 ; [{:b/second} 6] -> LEFT err (assuming lookup fails)
    338 ; %[{tmpid}]      -> RIGHT [2 ...] (assigns a tmpid and adds it to the map)
    339 ;
    340 > Tab Str Nat > Db > Any > Either Error [Nat (Tab Str Nat) Db]
    341 = (parseEntityId txnTmpIds db i)
    342 | if (isNat i)
    343     | RIGHT [i txnTmpIds db]
    344 | if (and (isRow i) (eql 2 | len i))
    345     # case | performLookup (idx 0 i) (idx 1 i) db
    346     - LEFT l    | LEFT l
    347     - RIGHT eid | RIGHT [eid txnTmpIds db]
    348 ; temporarily, we're going to wrap temporary identifiers in sets for ease of
    349 ; typing
    350 | if | rowAnd ++ isSet i
    351               ++ eql 1 | setLen i
    352               ++ isNat | setMin i
    353     @ val | setMin i
    354     : eid
    355         < maybeCase (tabLookup val txnTmpIds)
    356         @ [eid db] | allocEntity db
    357         | RIGHT [eid (tabIns val eid txnTmpIds) db]
    358     | RIGHT [eid txnTmpIds db]
    359 | LEFT [{Couldn't parse} i]
    360 
    361 ; Given a map, parse it into a series of actions and return the numeric eid
    362 ; parsed or assigned.
    363 ;
    364 ; We have to perform all numeric entity resolution immediately since we want to
    365 ; pass the eid upwards to participate in other parts of the transaction.
    366 ;
    367 ; TODO: Rewrite and reflow this function to the new 4 space standard. As is, the
    368 ; deep nesting makes editing most of this hard.
    369 ;
    370 > Tab Any Any > Db > (Maybe Nat, Row Error, Row TxnAction, Tab Any Any, Db)
    371 = (parseMapForm txnTmpIds db tab)
    372 ; if there's a :db/id, than use that. But if there's not, we have to assign
    373 ; a new temporary id.
    374 @ eitherEident
    375   : entVal
    376       < maybeCase (tabLookup dbIdStr tab)
    377       @ [eid db] | allocEntity db
    378       | RIGHT [eid txnTmpIds db]
    379   | parseEntityId txnTmpIds db entVal
    380 ;
    381 # case eitherEident
    382 - LEFT l
    383   [NONE (LEFT | CONS l NIL) NIL txnTmpIds db]
    384 - RIGHT [eid txnTmpIds db]
    385   | rowCons (SOME eid)
    386   ^ foldl _ [NIL NIL txnTmpIds db] | tabToPairs | tabDel dbIdStr tab
    387   & ([errs actions txnTmpIds db] [aVal vVal])
    388   ;
    389   # case (resolveAttrName aVal db)
    390   - LEFT err
    391     [(CONS err errs) actions txnTmpIds db]
    392   - RIGHT [aid indexed unique isMany isRef]
    393     @ writeAve | or indexed unique
    394     | if isRef
    395       ; TODO: This is super complex code. It's ugly, it's long, and I don't
    396       ; know how to effectively factor this in sire.
    397       | if (rowAnd [isMany (not | isSet vVal) (isRow vVal)])
    398         ; we have a list here, each value needs to be treated as a map form.
    399         @ [suberrs subactions subeids txnTmpIds db]
    400           ^ foldl _ [NIL NIL NIL txnTmpIds db] vVal
    401           & ([errs actions eids txnTmpIds db] i)
    402           | ifNot | isTab i
    403             ++ (CONS (BAD_MAP_VAL i) errs)
    404             ++ actions
    405             ++ eids
    406             ++ txnTmpIds
    407             ++ db
    408           ; we need to reparse map errors
    409           @ [mybEid suberrs subactions txnTmpIds db]
    410             | parseMapForm txnTmpIds db i
    411           ++ | listWeld suberrs errs
    412           ++ | listWeld subactions actions
    413           ++ : eid < maybeCase mybEid eids
    414              | CONS eid eids
    415           ++ txnTmpIds
    416           ++ db
    417         @ newActions
    418           : subeid < listForEach subeids
    419           (ACTADD eid aid aVal subeid writeAve unique isMany isRef)
    420         ++ (listWeld suberrs errs)
    421         ++ (listWeld newActions | listWeld subactions actions)
    422         ++ txnTmpIds
    423         ++ db
    424       | if ((isTab vVal) && (not | isSet vVal))
    425         @ [mybEid suberrs subactions txnTmpIds db]
    426           | parseMapForm txnTmpIds db vVal
    427         : subeid < maybeCase mybEid
    428                    ++ | listWeld suberrs errs
    429                    ++ | listWeld subactions actions
    430                    ++ txnTmpIds
    431                    ++ db
    432         @ topAdd | ACTADD eid aid aVal subeid writeAve unique isMany isRef
    433         ++ | listWeld suberrs errs
    434         ++ | CONS topAdd | listWeld subactions actions
    435         ++ txnTmpIds
    436         ++ db
    437       ; we have one entity id as the rhs.
    438       # case | parseEntityId txnTmpIds db vVal
    439       - LEFT err
    440         ++ (CONS err errs)
    441         ++ actions
    442         ++ txnTmpIds
    443         ++ db
    444       - RIGHT [rightEid txnTmpIds db]
    445         ++ errs
    446         ++ | CONS (ACTADD eid aid aVal rightEid writeAve unique isMany isRef)
    447                   actions
    448         ++ txnTmpIds
    449         ++ db
    450     | if isMany
    451       ; many, but not references. So if this is a row, parse each item as a
    452       ; value, otherwise
    453       @ adds
    454         | if | isRow vVal
    455           : v < listForEach (listFromRow vVal)
    456           | ACTADD eid aid aVal v writeAve unique isMany isRef
    457         | CONS | ACTADD eid aid aVal vVal writeAve unique isMany isRef
    458           NIL
    459       ++ errs
    460       ++ listWeld adds actions
    461       ++ txnTmpIds
    462       ++ db
    463     ; sometimes a val is just a val
    464     ++ errs
    465     ++ | CONS (ACTADD eid aid aVal vVal writeAve unique isMany isRef)
    466               actions
    467     ++ txnTmpIds
    468     ++ db
    469 
    470 > Db > Tab Any Any > (Row Error, Row TxnAction, Tab Any Any, Db)
    471 = (parseListForm txnTmpIds db row)
    472 ; rows are a tuple of [op espec attr val]
    473 | ifNot | eql 4 | len row
    474     ++ (CONS (INVALID_ROW row) NIL)
    475     ++ NIL
    476     ++ txnTmpIds
    477     ++ db
    478 @ op | idx 0 row
    479 | if | and | neq %add op
    480            | neq %rm op
    481    ++ (CONS (INVALID_ROW_OP op) NIL)
    482    ++ NIL
    483    ++ txnTmpIds
    484    ++ db
    485 # case | parseEntityId txnTmpIds db (idx 1 row)
    486 - LEFT err
    487     ++ (CONS err NIL)
    488     ++ NIL
    489     ++ txnTmpIds
    490     ++ db
    491 - RIGHT [eid txnTmpIds db]
    492     ; todo: in list form, attr is not just an attrName, but can also be a
    493     ; raw eid or a lookupref. we completely punt on this: it should be easy
    494     ; to add back later when there's a more complete type system and we
    495     ; aren't going to use this for now.
    496     @ attrName | idx 2 row
    497     # case (resolveAttrName attrName db)
    498     - LEFT err
    499         [(CONS err NIL) NIL txnTmpIds db]
    500     - RIGHT [aid indexed unique isMany isRef]
    501         @ writeAve | or indexed unique
    502         ; In list form, we don't do arity resolution. We treat the value
    503         ; literally.
    504         @ val | idx 3 row
    505         | if (eql op %rm)
    506             ++ NIL
    507             ++ | CONS | ACTRM eid aid val
    508             ++ txnTmpIds
    509             ++ db
    510         ++ NIL
    511         ++ @ a | ACTADD eid aid attrName val writeAve unique isMany isRef
    512            | CONS a NIL
    513         ++ txnTmpIds
    514         ++ db
    515 
    516 ; Given a user-level structure of rows and tabs, parse it into a series of adds
    517 ; and retracts.
    518 ;
    519 ; Parsing depends on a database value because the query has attributes in it
    520 ; and we have to lookup the properties of those attributes to know how to parse
    521 ; them.
    522 ;
    523 > Tab Any Any > Db > Any > (Row Error, Row TxnAction, Tab Any Any, Db)
    524 = (parseItem txnTmpIds db item)
    525 | if (isTab item)
    526     | drop 1 | parseMapForm txnTmpIds db item
    527 | if (isRow item)
    528     | parseListForm txnTmpIds db item
    529 ;
    530 | LEFT {1) What}
    531 
    532 > Db > Row Any > Either (Row Error) (Row TxnAction, Db)
    533 = (parseAllItems db items)
    534 ^ foldl _ [NIL NIL #[] db] items
    535 & ([errs actions txnTmpIds db] i)
    536 @ [newErrs newActions txnTmpIds db] | parseItem txnTmpIds db i
    537 ++ | listWeld newErrs errs
    538 ++ | listWeld newActions actions
    539 ++ txnTmpIds ; Tab Any Any
    540 ++ db
    541 
    542 ; User transaction function. Given transaction items, resolve any pending
    543 ; entity numbers in them and then apply them to the database.
    544 ;
    545 > Row Any > Db > Either (Row Error) Db
    546 = (transact items db)
    547 @ [errs actions _ db] | parseAllItems db items
    548 | ifNot (listIsEmpty errs) | LEFT | listToRow errs
    549 @ [errs newDB]
    550     ^ listFoldl _ [errs db] actions
    551     & ([errs db] item)
    552     # case item
    553     - ACTADD eid aid attrName val writeAve unique many isRef
    554         | ifNot unique
    555             ; simple case, cannot error
    556             [errs (rawAdd eid aid val writeAve many isRef db)]
    557         ;
    558         ; check for uniqueness violations.
    559         @ oldEidSet | lookupByAidV aid val db
    560         | if (setIsEmpty oldEidSet)
    561             ; no previous value
    562             [errs (rawAdd eid aid val writeAve many isRef db)]
    563         ++ | CONS (UNIQUE_VIOLATION attrName val (setMin oldEidSet) eid) errs
    564         ++ db
    565     - ACTRM eid attr val
    566         [errs (rawRm eid attr val db)]
    567 | ifNot (listIsEmpty errs) | LEFT | listToRow errs
    568 | RIGHT newDB
    569 
    570 > Row TxnAction > Db > Db
    571 = (assertTransact items db)
    572 # case (transact items db)
    573 - LEFT l  | {datom transaction failed: } [errors=l =db]
    574 - RIGHT r | r
    575 
    576 ;;; Pull ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    577 
    578 ; AttrExpr, an "attribute expression", the parsed version of trying to represent
    579 ; the way of specifying an attribute in a pull, in either direction, subject to
    580 ; a set of options which controls how the results from the attribute match will
    581 ; be returned.
    582 # data AttrExpr
    583 - ATTR_EXPR name/String options/(Tab String Any)
    584 - ATTR_BACKWARDS name/String options/(Tab String Any)
    585 - ATTR_EID options/(Tab String Any)
    586 
    587 # data ParsedPull
    588 - PULL_WILDCARD
    589 - PULL_ATTR expr/AttrExpr
    590 - PULL_MAP spec/(Row [AttrExpr (Row ParsedPull)])
    591 
    592 ; cps
    593 > Row (Either (Row e) a) > (Row a > Either (Row e) b) > Either (Row e) b
    594 = (collectErrors vals cont)
    595 @ [errs parsedVals] | partition vals
    596 | if (len errs)
    597     | LEFT errs
    598 | cont parsedVals
    599 
    600 ; On error, return the error and don't progress further.
    601 > Either e a > (a > Either e b) > Either e b
    602 = (doRight val cont)
    603 # case val
    604 - LEFT l  | LEFT l
    605 - RIGHT r | cont r
    606 
    607 ; If nat is a backwards lookup, return the real attribute name in a SOME.
    608 ;
    609 ; Datomic and its descendants represent backward lookups by just changing the
    610 ; last component of the attribute name to start with a {_}. We should find a
    611 ; better way to represent this, but for now, literally clone.
    612 ;
    613 > Nat > Maybe Nat
    614 = (isBackwardsAttribute n)
    615 @ bar | natBar n
    616 : lastIdx < maybeCase (barElemIndexEnd {/} bar) NONE
    617 @ [first last] | barSplitAt (inc lastIdx) bar
    618 | if (neq {_} | barGet last 0)
    619     | NONE
    620 | SOME
    621 | barNat
    622 | barWeld first
    623 | barDrop 1 last
    624 
    625 > Db > Any > Either (Row Error) ParsedPull
    626 = (parse db val)
    627 | if | isRow val
    628     ; TODO: all non simple list forms.
    629     | LEFT {todo: any map term}
    630 | if | isTab val
    631     : specs
    632         < collectErrors
    633         : [k v]  < foreach (tabToPairs val)
    634         : parsed < doRight | parse db k
    635         # case parsed
    636         - PULL_ATTR keyExpr
    637             : parsedVals < collectErrors | map (parse db) v
    638             | RIGHT [keyExpr parsedVals]
    639         - _ | LEFT | **BAD_MAP_VAL parsed
    640     | RIGHT | PULL_MAP specs
    641 | if (val == {*})
    642     | RIGHT PULL_WILDCARD
    643 | if (val == dbIdStr)
    644     | RIGHT | **PULL_ATTR | ATTR_EID #[] ; ATTR_EID takes a tab
    645 | if (isNat val)
    646     | RIGHT
    647     | **PULL_ATTR
    648     : realName
    649         < maybeCase | isBackwardsAttribute val
    650         | ATTR_EXPR val #[]        ;  ATTR_EXPR takes tab
    651     | ATTR_BACKWARDS realName #[]  ;  ATTR_BACKWARDS takes a tab
    652 | LEFT | **BAD_PULL_TERM val
    653 
    654 > Db
    655 > Nat
    656 > Either Error [Str (Set Any) [Bit Bit Bit Bit]]
    657 = (lookupAttr db eid expr)
    658 ;
    659 @ (handleOptions name options attrType rawSet)
    660   @ outputKey | fromSome name | tabLookup {:as} options
    661   ; TODO: handle limits in rawSet.
    662   | RIGHT [outputKey rawSet attrType]
    663 # case expr
    664 - ATTR_EXPR name options
    665     : [aid indexed unique isMany isRef] < doRight | resolveAttrName name db
    666     @ rawSet | lookupByEidAid eid aid db
    667     | handleOptions name options [indexed unique isMany isRef] rawSet
    668 - ATTR_BACKWARDS name options
    669     : [aid indexed unique isMany isRef] < doRight | resolveAttrName name db
    670     @ rawSet | lookupByVAid eid aid db
    671     | handleOptions name options [indexed unique isMany isRef] rawSet
    672 - ATTR_EID options
    673     | handleOptions dbIdStr options [TRUE TRUE FALSE TRUE] (setSing eid)
    674 
    675 > Nat
    676 > Db
    677 > ParsedPull
    678 > Either Error [Any Any]
    679 = (lookupSpec eid db item)
    680 # case item
    681 - PULL_WILDCARD
    682     ; We have to return a map of all attributes to their values, including
    683     ; recursively, which is hard. We need to already have individual
    684     | {pull wildcard unimplemented} 0
    685 - PULL_ATTR expr
    686     : [outputKey set [indexed unique isMany isRef]]
    687         < doRight | lookupAttr db eid expr
    688     | if | setIsEmpty set
    689         | trk [%empty]
    690         | RIGHT []
    691     | if isMany
    692         | RIGHT [outputKey (setToRow set)]
    693     ;
    694     | RIGHT [outputKey (setMin set)]
    695 - PULL_MAP spec
    696     ; Need to process the map here. This is a key from a eid ref to all the
    697     ; values which match it.
    698     : result
    699         < collectErrors
    700         : [k v] < foreach spec
    701         : [outputKey set [indexed unique isMany isRef]]
    702             < doRight | lookupAttr db eid k
    703         : result
    704             < collectErrors
    705             : ?(lookupSubEid subeid) < foreach (setToRow set)
    706             : result
    707                 < collectErrors
    708                 | map (lookupSpec subeid db) v
    709             | RIGHT | tabFromPairs result
    710         | RIGHT [outputKey result]
    711     | RIGHT | cat result
    712 
    713 ; General lookup function which returns information starting at an entity.
    714 ;
    715 ; Given an entity number, produces a tab with attributes as keys as described
    716 ; by `pullSpec`. This tab can recurse downwards into linked attributes, but
    717 ; does cannot do arbitrary higher complexity queries.
    718 ;
    719 > Any > Any > Db > Either (Row Error) (Tab Any Any)
    720 = (pull pullSpec eidSpec db)
    721 # case
    722     | parseEntityId #[] db eidSpec ; {parseEntityId} takes a tab
    723 - LEFT l
    724     | LEFT [l]
    725 - RIGHT [eid _ _ _]
    726     | ifNot | isRow pullSpec
    727         | LEFT [(**PULL_NOT_ROW pullSpec)]
    728     : specs  < collectErrors | map (parse db) pullSpec
    729     : result < collectErrors | map (lookupSpec eid db) specs
    730     | RIGHT | tabFromPairs result
    731 
    732 ; General lookup function which returns information for multiple entities.
    733 ;
    734 > Any > Row Any > Db > Either (Row Error) (Row | Tab Any Any)
    735 = (pullMany pullSpec rowEidSpec db)
    736 : eids
    737     < collectErrors
    738     : eidSpec   < foreach rowEidSpec
    739     : [eid _ _] < doRight | parseEntityId #[] db eidSpec
    740     ;;;;;;;;;;;;;;;;;;;;;;;; parseEntityId takes a tab
    741     | RIGHT eid
    742 : specs
    743     < collectErrors | map (parse db) pullSpec
    744 : results
    745     < collectErrors
    746     : eid    < foreach eids
    747     : result < collectErrors | map (lookupSpec eid db) specs
    748     | RIGHT | tabFromPairs result
    749 | RIGHT results
    750 
    751 
    752 ;;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    753 
    754 ^-^
    755 ^-^ fromSingletonSet
    756 ^-^
    757 ^-^ UNIQUE_VIOLATION LOOKUP_NO_ATTRIBUTE LOOKUP_ATTRIBUTE_NOT_UNIQUE
    758 ^-^ LOOKUP_NO_MATCH INVALID_ROW INVALID_ROW_OP BAD_MAP_VAL BAD_PULL_TERM
    759 ^-^
    760 ^-^ Db DB emptyDB
    761 ^-^
    762 ^-^ lookupByEA lookupByAE lookupByAV lookupByVA transact assertTransact
    763 ^-^ pull pullMany
    764 ^-^
    765 ^-^ ; These shouldn't be exported, but are for testing. Remove these
    766 ^-^ ; when there's a proper complete pull api implementation.
    767 ^-^ lookupByAidV lookupByVAid lookupByIdentA dbIdentId
    768 ^-^