fluree / core

Fluree releases and public bug reports
0 stars 0 forks source link

Support additional "update" capabilities in transactions #11

Closed aaj3f closed 1 year ago

aaj3f commented 1 year ago

Description

Currently the only supported "update" syntax is either (1) a limited retract-and-replace pattern or (2) a "delete"+"where" pattern. Other required update patterns have been described that would allow...

Technical Details

If the current "delete"+"where" capability pattern is standards-based, then this work might want to comply with that standard. And even if not, it would be desirable for the above use cases to conform to a generally consistent pattern, such as the one already laid out by the "delete"+"where" syntax

A previous draft ticket (but an old one, possibly worth closing) suggested an extension to "delete"+"where" that singularly allowed multi-cardinality insertions. @dpetran @bplatz you may want to weigh in on this if you had previous discussion around that ticket.

bplatz commented 1 year ago

This should implement the syntax reference in the other ticket - details in the link above, but in summary was:

'{:delete [?s :schema/name "William"]
  :insert [?s :schema/name "Bill"]
  :where  [[?s :schema/name "William"]]}

The main item not addressed in the above example is that :insert and :delete as well should support multiple values as per the example below.

If this addition to this ticket makes it take any longer than only supporting the single-value example above, it should be split into two tickets and the multi-value syntax supported separately.

'{:delete [[?s :schema/name "William"]
           [?s :schema/age ?age]]
  :insert [[?s :schema/name "Bill"]
           [?s :schema/age 42]]
  :where  [[?s :schema/name "William"]
           {:optional [?s :schema/age ?age]}]}

Which would mean for anyone named "William", rename them to "Bill", delete their age(s) if they exist, and insert a single new age of 42.

bplatz commented 1 year ago

In the example from the ticket description to update an @id from x to y, the syntax would be:

'{:delete ["ex:person123" ?p ?o]
  :insert ["ex:personABC" ?p ?o]
  :where  [["ex:person123" ?p ?o]]}
bplatz commented 1 year ago

As a general reference to this feature, note https://www.w3.org/TR/sparql11-update/#deleteInsert - which we'll ultimately need to parse SPARQL in this format into our format specified above.

bplatz commented 1 year ago

Sample test for consideration:

(deftest ^:integration deleting-data
  (let [conn           (test-utils/create-conn)
        ledger         @(fluree/create conn "tx/delete" {:defaultContext ["" {:ex "http://example.org/ns/"}]})
        db             @(fluree/stage
                          (fluree/db ledger)
                          {:graph [{:id           :ex/alice,
                                    :type         :ex/User,
                                    :schema/name  "Alice"
                                    :schema/email "alice@flur.ee"
                                    :schema/age   42}
                                   {:id          :ex/bob,
                                    :type        :ex/User,
                                    :schema/name "Bob"
                                    :schema/age  22}
                                   {:id           :ex/jane,
                                    :type         :ex/User,
                                    :schema/name  "Jane"
                                    :schema/email "jane@flur.ee"
                                    :schema/age   30}]})

        ;; Change Bob's age - but only if his age is still 22
        db-update-bob  @(fluree/stage db
                                      '{:delete [:ex/bob :schema/age 22]
                                        :insert [:ex/bob :schema/age 23]
                                        :where  [[:ex/bob :schema/age 22]]})

        ;; Shouldn't change Bob's age as the current age is not a match
        db-update-bob2 @(fluree/stage db
                                      '{:delete [:ex/bob :schema/age 99]
                                        :insert [:ex/bob :schema/age 23]
                                        :where  [[:ex/bob :schema/age 99]]})

        ;; change Jane's age regardless of its current value
        db-update-jane @(fluree/stage db
                                      '{:delete [:ex/jane :schema/age ?current-age]
                                        :insert [:ex/jane :schema/age 31]
                                        :where  [[:ex/jane :schema/age ?current-age]]})]

    (testing "Updating property value only if it's current value is a match."
      (is (= {:id          :ex/bob,
              :type        :ex/User,
              :schema/name "Bob"
              :schema/age  23}
             @(fluree/query db-update-bob
                            '{:select {?s [:*]}
                              :where  [[?s :id :ex/bob]]}))
          "Bob's age should now be updated to 23 (from 22)."))

    (testing "No update should happen if there is no match."
      (is (= {:id          :ex/bob,
              :type        :ex/User,
              :schema/name "Bob"
              :schema/age  22}
             @(fluree/query db-update-bob2
                            '{:select {?s [:*]}
                              :where  [[?s :id :ex/bob]]}))
          "Bob's age should have not been changed and still be 22."))

    (testing "Replacing existing property value with new property value."
      (is (= {:id           :ex/jane,
              :type         :ex/User,
              :schema/name  "Jane"
              :schema/email "jane@flur.ee"
              :schema/age   31}
             @(fluree/query db-update-jane
                            '{:select {?s [:*]}
                              :where  [[?s :id :ex/jane]]}))
          "Jane's age should now be updated to 31 (from 30)."))))
zonotope commented 1 year ago

In the example from the ticket description to update an @id from x to y, the syntax would be:

'{:delete ["ex:person123" ?p ?o]
  :insert ["ex:personABC" ?p ?o]
  :where  [["ex:person123" ?p ?o]]}

I'm a little worried about this usage because of the interplay between subject ids and subject iris. The naive implementation would delete all the flakes related to the subject with iri ex:person123 and create a new subject with the a different iri and the same predicate-object mappings. The problem here is that we wouldn't update any references to that subject.

We could special case this specific operation to update the "@id" predicate on the subject, but I think the parsing code to support this would be a bit cumbersome.

Could we instead support the following syntax for updating a subject's id?

'{:delete [?s :id "ex:person123"]
  :insert [?s :id "ex:personABC"]
  :where  [[?s :id "ex:person123"]]}