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