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

xmldb:rename() function fails if the collection name contains spaces #5299

Open daliboris opened 4 months ago

daliboris commented 4 months ago

Collection with spaces in name cannot be renamed

If the collection name contains spaces, calling function xmldb:rename($source-collection-uri as xs:string, $new-collection-name as xs:string) fails with the following error:

exerr:ERROR Could not locate collection: /db/test/data%20with%20spaces [at line 1, column 1, source: String/2077783963999039558]

Expected behavior

Collection with spaces should be renamed.

The xmldb:rename() function with 4 parametrs works if the renamed resource contains spaces, for instance

xmldb:rename('/db/test/', replace('xml with spaces.xml', ' ', '%20'), 'clean-xml.xml')

To Reproduce

xquery version "3.1";

module namespace t="http://exist-db.org/xquery/test";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare
    %test:setUp
function t:setup() {
    let $testCol := xmldb:create-collection("/db", "test")
    let $spacesCol := xmldb:create-collection("/db/test", "data%20with%20spaces")
    return ()
};

declare
    %test:tearDown
function t:tearDown() {
    xmldb:remove("/db/test")
};

declare
    %test:assertEquals('data%20with%20spaces')
function t:collection-with-spaces-exists() {
    let $result := xmldb:get-child-collections('/db/test/')
    return $result
};

declare
    %test:assertTrue
function t:collection-exists() {
    let $result := xmldb:get-child-collections('/db/test/')
    return
       count($result) eq 1
};

declare
%test:assertError
function t:rename-collection-throws-error() {
    xmldb:rename('/db/test/data%20with%20spaces', '/db/test/data-with-dashes')
};

Context

daliboris commented 4 months ago

Workaround

let $main-colection := '/db/test'
let $old-collection-name := 'data with spaces'
let $new-collection-name := 'data-with-dashes'

let $old-collection := $main-colection || replace($old-collection-name, ' ', '%20')
let $new-collection := $main-colection || $new-collection-name 

let $resources := xmldb:get-child-resources($old-collection)[matches(., ".*\.*")]
let $created-collection := xmldb:create-collection($main-colection, $new-collection-name)
let $result :=
  for $resource in $resources return
    xmldb:copy-resource($old-collection, $resource, $new-collection, $resource, true())
return (xmldb:remove($old-collection),  $created-collection, $result)
joewiz commented 4 months ago

See discussion at https://github.com/eXist-db/exist/issues/1824. From my summary:

In conclusion, these cases suggest that eXist sometimes silently performs URI-encoding, sometimes doesn't, and may produce identical or different results when you pre-encode a collection name depending on the characters used, and may raise different errors or produce unexpected outcomes if you don't pre-encode. These differences make it difficult to create applications that handle characters consistently, and thus cause users confusion when they encounter inconsistency or errors caused by this design.

daliboris commented 4 months ago

From my point of view, there is an inconsistency in the treatment of collection names.

The following function call works

but this one fails

It uses the same (encoded) name of the collection.

line-o commented 4 months ago

@daliboris I tried to reproduce the issue and found it to work with collections with %-encoded spaces:

xquery version "3.1";

let $_ := xmldb:create-collection("/db", "test")
let $_ := xmldb:create-collection("/db/test", "data%20with%20spaces")

return xmldb:rename("/db/test/data%20with%20spaces", "a")

Maybe the issue was just that xmldb:rename expects just the new name of the collection in the second parameter and not the entire path.

daliboris commented 4 months ago

You're right @line-o, your code works.

Maybe the documentation is misleading:

xmldb:rename($source-collection-uri as xs:string, $new-collection-name as xs:string) as item()

Renames the collection $source-collection-uri with new name $new-collection-name.

But the $new-collection-name forms only the last part of the $source-collection-uri.

Or does it mean that the collection name represented by the URI is only the outermost part of the URI, not the entire URI?