eXist-db / exist

eXist Native XML Database and Application Platform
https://exist-db.org
GNU Lesser General Public License v2.1
421 stars 179 forks source link

fn:serialize should default to adaptive serialisation method #2339

Open line-o opened 5 years ago

line-o commented 5 years ago

What is the problem

Serializing function types with fn:serialize should default to the adaptive method. At the moment fn:serialize([1]), fn:serialize(map{1:0}) both return an empty string.

What did you expect

The serialisation to default to the adaptive method.

Describe how to reproduce or add a test

let $data := (
  [1, 2, [1, 2]],
  map{"this": "is", "just": ("a", "test")}
)
return fn:serialize($data) eq fn:serialize($data, map {"method": "adaptive"})

Context information

line-o commented 5 years ago

I am also wondering what the expected behaviour for maps is. BaseX just throws an error while existdb will return an empty string again. When I have time to look at the spec I might expand this issue.

line-o commented 5 years ago

Same behaviour applies to util:log("debug", [1,2])

dizzzz commented 5 years ago

maybe in fn:serialize([1]) the [1] is seen as a predicate.....

saxon returns "1" anyway

joewiz commented 5 years ago

[1] instance of array(*), [1,2] instance of array(*) returns true(), true(), so eXist knows it's an array.

The spec for fn:serialize says:

If the second argument is omitted, or is supplied in the form of an output:serialization-parameters element, then the values of any serialization parameters that are not explicitly specified is ·implementation-defined·, and may depend on the context.

This leads to the question, what is eXist's default set of serialization parameters?

If, however, we supply a serialization method, then eXist should respect it. Let's try "text":

xquery version "3.1";

serialize([1], map {"method": "text"})

This also returns "". Thus I think we have a serialization bug. The relevant portion of the spec is https://www.w3.org/TR/xslt-xquery-serialization-31/#serdm, which states:

Each item in the sequence that is an array is flattened by calling the function array:flatten() before being copied.

If we do this manually (via serialize(array:flatten([1])), we get the expected result, "1".

In other words, eXist is failing to flatten arrays during sequence normalization.

line-o commented 5 years ago

Thanks @joewiz for your thorough investigation of the issue. I already learned a lot.

line-o commented 5 years ago

One thing keeps bugging me: array:flatten([1]) should yield [1] and thus both should be identical. To me array:flatten would only be necessary for nested arrays (serialize([1, [2, 3]])). The outcome 1 2 3 would also not meet my expectations. If the spec says so for text then I would argue that exist should use JSON as the default.

line-o commented 5 years ago

When serializing maps

xquery version "3.1";
let $m := map{"key":"1"}
return (
  serialize($m),
  serialize($m, map{'method':'text'}),
  serialize($m, map{'method':'json'}),
  serialize($m, map{'method':'xml'})
)

(: yields ("", "", "{\"key\": \"1\"}", "") :)

Only JSON serialisation does return the expected output.

line-o commented 5 years ago

Digging a little deeper I found that there is an 'adaptive' method which does things as one would expect

xquery version "3.1";
let $m := (map {"key":"1"}, [1, [2, 3]])
return (
  serialize($m),
  serialize($m, map {'method':'adaptive'}),
  serialize($m, map {'method':'text'}),
  serialize($m, map {'method':'json'}),
  serialize($m, map {'method':'xml'})
)
line-o commented 5 years ago

Just read the spec for array:flatten to learn that is indeed a function that returns a sequence of all members of nested arrays.

joewiz commented 4 years ago

This problem still affects eXist 5.2.0.