Alex Everitt
Alex Everitt

Reputation: 23

Getting no updating expression error using XQuery XQUF in BaseX to make an unknown/variable number of updates in one function

This code is stopped in its tracks because there is technically no "updating expression" - probably because it is hiding in an eval string and the interpreter doesn't see it.

The examples that I have reviewed that use xquery update facility have a fixed number of update statements, but I'd like to handle a variable number of update statements in an updating function.

I may be way off (please tell me if I am) in how I've approached this solution but I tried to create a string with all the update statements comma delimited and then xquery:eval the string to do all the updates in one return statement.

I think this should work because the string looks like the statements that I would hard code if I knew how many.
e.g. return (replace value of node quantity with '1', replace value of node market with 'new_market')

Stopped at C:/Program Files (x86)/BaseX/webapp/product.xqm, 109/7:
[XUST0002] Function body is no an updating expression. 
(looks like a typo in the error message btw)

(: example new data values 
   $req - could be any number of nodes with new values
<request>
  <data>
    <ID>1</ID>
    <quantity>1</quantity>
    <market>new_market</market>
  </data>
</request>
:)

(:  Old data that matches ID and needs replacing
<csv>
  <record>
    <ID>1</ID>
    <quantity>3</quantity>
    <market>old_market</market>
  </record>
<csv>
:)

declare
  %rest:path("updy/{$db}/{$resource}")
  %rest:POST("{$req}")
  %updating function page:updy($db,$resource,$req as document-node()) 
{
let $input := concat("db:open('",$db,"','",$resource,"')") 
for $node_name in $req/request/data/(* except ID)/name()
  let $updating_record := query:eval($input)/csv/record[ID=$req/request/data/ID]
  for $updating_fields in $updating_record//*[name()=$node_name]
    let $to_value := $req/request/data/*[name()=$updating_fields/name()]/data()
    let $eval_string := string-join(concat("replace value of node ",$updating_fields/name()," with '",$to_value,"'")," , ")
return xquery:eval($eval_string))
};

The top is code that I'm trying to get to work from a rest call. The bottom is code that I'm trying to test to see if I can get this to work. This is set to return just the eval string then switch comments there to try and run it.

declare %private updating function local:updy($db,$resource,$req as element()) 
{
  let $input := concat("db:open('",$db,"','",$resource,"')")

  for $node_name in $req//data/(* except ID)/name()
    let $updating_record := xquery:eval($input)/csv/record[ID=$req//data/ID]
    for $updating_fields in $updating_record//*[name()=$node_name]
      let $to_value := $req//data/*[name()=$updating_fields/name()]/data()
      let $eval_string := string-join(concat("replace value of node ",$updating_fields/name()," with '",$to_value,"'")," , ")
  (: return $eval_string :)
  return xquery:eval($eval_string)
};

(: example new data values :)
(: $req - could be any number of nodes with new values :)
let $req := 
<request>
  <data>
    <ID>1</ID>
    <quantity>1</quantity>
    <market>new_market</market>
  </data>
</request>

(:  Old data that matches ID and needs replacing :)
let $input := 
<csv>
  <record>
    <ID>1</ID>
    <quantity>3</quantity>
    <market>old_market</market>
  </record>
</csv>
let $dbadd := db:create("ProductTest", $input, "exportDataTest.csv") 
return local:updy("ProductTest","exportDataTest.csv",$req)

I'm adding the finished code below because with the help I received here I was able to get it working properly.

declare
  %rest:path("updz/{$db}/{$resource}")
  %rest:POST("{$req}")
  %updating function page:updz($db,$resource, $req as document-node()) {
    let $input := concat("db:open('",$db,"','",$resource,"')")
    let $the_id := $req//data/ID
    let $update_string := 
      for $node_name in $req//data/(* except ID)/name()
        let $updating_record := xquery:eval($input)/csv/record[ID=$req//data/ID]
        for $updating_fields in $updating_record//*[name()=$node_name]
          let $to_value := $req//data/*[name()=$updating_fields/name()]/data()

    return string-join(concat("replace value of node ", $input,"/csv/record[ID='",$the_id,"']//",$updating_fields/name()," with '",$to_value,"'")," , ")

    let $final_update_string := concat("(",string-join($update_string,","),",update:output(<response><status>1</status></response>))")
    (: return $final_update_string :)
    return xquery:eval-update($final_update_string)
};      

Upvotes: 0

Views: 303

Answers (1)

Christian Gr&#252;n
Christian Gr&#252;n

Reputation: 6229

The reason for the error message (with the unexpected typo; thanks for the hint) is that your function that contains the xquery:eval call is annotated as %updating. The xquery:eval function, however, is a non-updating function.

The XQuery Update Recommendation postulates that each expression must be either updating or read-only. As the argument of xquery:eval can vary during query execution…

for $query in ('123', 'delete node <a/>')
return xquery:eval($query)

…there is a second function called xquery:eval-update, which can be used for evaluating queries that contain updating expressions. If you know that the incoming queries will be non-updating, simply get rid of the %updating annotation.

One more comment: If you create a database in a query, you cannot request its contents in the same query (a single XQuery is a single transaction). Please check out the short documentation on the Pending Update List or have a look into the full specification.

Upvotes: 2

Related Questions