Invoking an XQuery Updating function results in the error If any subexpression is updating, then all must be updating

35 Views Asked by At

My XML document contains a record of the movement of an object. The XML document consists of a series of observations. Each observation contains the lat/long location of the object, the lat/long of the observing sensor, and a date/time stamp.

<Track-History>
    <Track-ID>XYZ</Track-ID>
    <Observation>
        <Target-Latitude>10.3</Target-Latitude>
        <Target-Longitude>20.8</Target-Longitude>
        <Observer-Latitude>40.0</Observer-Latitude>
        <Observer-Longitude>50.0</Observer-Longitude>
        <DateTime>20230202T071700.00</DateTime>
    </Observation>
    <Observation>
        <Target-Latitude>15.1</Target-Latitude>
        <Target-Longitude>25.2</Target-Longitude>
        <Observer-Latitude>40.0</Observer-Latitude>
        <Observer-Longitude>50.0</Observer-Longitude>
        <DateTime>20230202T071800.00</DateTime>
    </Observation>
</Track-History>

I want to fuzz the locations of the object by rounding the decimal lat and long values. So I created this updating function:

declare updating function f:fuzzPoint($lat as element(), $long as element())
{
    replace node $lat with 
        element {name($lat)} {round(number(data($lat)))},
    replace node $long with 
        element {name($long)} {round(number(data($long)))}
};

When I invoke that function:

{f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}

I get this error message:

If any subexpression is updating, then all must be updating

Oxygen XML gives a red squiggly line under the function arguments, so apparently the arguments must be updating. Yes? If so, how to make the arguments updating?

Below is my complete XQuery Update program.

declare namespace f = "function";

declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)

declare function f:isInside($points, $polygon) as xs:boolean
{
    true()
};

declare updating function f:fuzzPoint($lat as element(), $long as element())
{
    replace node $lat with 
        element {name($lat)} {round(number(data($lat)))},
    replace node $long with 
        element {name($long)} {round(number(data($long)))}
};

if (f:isInside($track-history-points, $AOR)) then
    for $obs in $Track-History//Observation return
      replace node $obs with
      <Observation>
            {f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}
            {$obs/Observer-Latitude}
            {$obs/Observer-Longitude}
            {$obs/DateTime}
       </Observation>
else
   replace node $Track-History/* with
      <Track-History/>
2

There are 2 best solutions below

2
Christian Grün On BEST ANSWER

Inside an update expression – replace node $obs with ... – it’s not allowed to perform other updates. The code works if you modify f:fuzzPoint such that it returns the new element nodes:

declare function f:fuzzPoint($lat as element(), $long as element()) {
  element { name($lat) } { round(number(data($lat))) },
  element { name($long) } { round(number(data($long))) }
};

The updating keyword in the function declaration makes sense if you move the update operation inside the function body:

declare updating function f:replace($obs as element()) {
  let $lat := $obs/Target-Latitude
  let $long := $obs/Target-Longitude
  return replace node $obs with <Observation>{
    element { name($lat) } { round(number(data($lat))) },
    element { name($long) } { round(number(data($long))) },
    $obs/Observer-Latitude,
    $obs/Observer-Longitude,
    $obs/DateTime
  }</Observation>
};

if (f:isInside($track-history-points, $AOR)) then
  for $obs in $Track-History//Observation return f:replace($obs)
else
   replace node $Track-History/* with
      <Track-History/>
1
Martin Honnen On

I wonder whether

declare namespace f = "function";

declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)

declare function f:isInside($points, $polygon) as xs:boolean
{
    true()
};

declare function f:fuzzValue($v as xs:double) as xs:double
{
  round($v)
};


if (f:isInside($track-history-points, $AOR)) then
    for $lv in $Track-History//Observation/(Target-Latitude, Target-Longitude) 
    return
      replace value of node $lv  
      with f:fuzzValue($lv)

else
   replace node $Track-History/* with
      <Track-History/>

is all you want to do there.