How to dynamically reference nodes in xdmp:node-replace in MarkLogic? - xquery

In my function update-replace, I am trying to dynamically replace an XML node in one of my XML data source files in MarkLogic by calling xdmp:node-replace like below:
declare function update-lib:update-rec($doc as xs:string, $path as xs:string, $country as xs:string, $name as xs:string, $population as xs:integer, $latitude as xs:decimal, $longitude as xs:decimal) as document-node() {
(: read lock acquired :)
fn:doc($doc),
xdmp:node-replace(fn:doc($doc)/$path,
<city>
<country>{$country}</country>
<name>{$name}</name>
<population>{$population}</population>
<latitude>{$latitude}</latitude>
<longitude>{$longitude}</longitude>
</city>
),
(: after the following statement, txn ends and locks released :)
xdmp:commit()
};
The function takes 7 arguments with the 1st arg being path to the XML source file, the 2nd being path inside the XML file to the node to be updated and the rest corresponds to the child element values.
When I call xdmp:node-replace to update data, I encounter the following error:
500 Internal Server Error
XDMP-ARGTYPE: (err:XPTY0004) xdmp:node-replace("/cities/city[3961]",
JPMiyoshi56958)
-- arg1 is not of type node() ...
So I decided to have arg1 evaluated to ensure that node() gets passed as the 1st arg of node-replace:
xdmp:node-replace(xdmp:eval(fn:doc($doc)/$path),
<city>
<country>{$country}</country>
<name>{$name}</name>
<population>{$population}</population>
<latitude>{$latitude}</latitude>
<longitude>{$longitude}</longitude>
</city>
),
Now I receive the below error instead:
XDMP-UPEXTNODES:
xdmp:node-replace(fn:doc("/content/Users/Tako/Sites/MarkLogic/xml/worldcities/import/cities1000_02.xml")/cities/city[3961],
JPMiyoshi56958)
-- Cannot update external nodes ...
After a little googling, I've found this. It sounds like an issue with xdmp:eval and its context:
http://developer.marklogic.com/pipermail/general/2008-September/001753.html
I tried the workaround suggested here using fn:concat to have everything constructed as a string including xdmp:node-replace and evaluating the whole statement.
xdmp:eval(fn:concat('xdmp:node-replace(fn:doc("', $doc, '")', $path,
', ', '<city><country>',$country,'</country><name>',$name,'</name><population>',$population,'</population><latitude>',$latitude,'</latitude><longitude>',$longitude,'</longitude></city>', ')')),
The application sits and waits for a very long time before timing out when I tried this.
500 Internal Server Error
SVC-EXTIME:
xdmp:node-replace(fn:doc("/content/Users/Tako/Sites/MarkLogic/xml/worldcities/import/cities1000_17.xml")/cities/city[3961],
JPMiyoshi56958)
-- Time limit exceeded ...
All I want to do is to dynamically reference the XML file and the nodes to be updated and update the node with the info passed in. I must be overlooking something very fundamental or doing this completely wrong.
Could someone please shed light on this?

In your first solution you are applying a string value to each document node returned by fn:doc($doc). That way you only end up with the string value of $xpath itself. The second solution effectively takes the the value of $xpath too, and tries to evaluate that. That is likely to generate a lot of nodes that are potentially all being updated.
I am not entirely sure why you are getting XDMP-UPEXTNODES, and timeouts, but the following should do..
Replace:
fn:doc($doc)/$path
with:
xdmp:value(fn:concat("fn:doc($doc)", $path))
HTH!

Related

how to write query where the input file is passed from the command line (Saxon)

I'm very new to this.
I have a query and an xml file.
I can write a query over that specific file
for $x in doc("file:///C:/Users/Foo/IdeaProjects/XQuery/src/books.xml")/bookstore/book
where $x/price>30
order by $x/title
return $x/title
I have a basic xml file, with books in it, works nicely in intellij.
but if I wanted to run this query against some file defined on the command line, then how do I do it?
the command line for running the above is (as much for other peoples reference)
java -cp C:\Users\Foo\.IdeaIC2019.2\config\plugins\xquery-intellij-plugin\lib\Saxon-HE-9.9.1-7.jar net.sf.saxon.Query -t -q:"C:\Users\Foo\IdeaProjects\XQuery\src\w3schools.com.xqy"
and that also works nicely.
the saxon documentation
https://www.saxonica.com/html/documentation/using-xquery/commandline.html
implies that I can specify an input file, using "-d"
and "The document node of the document is made available to the query as the context item"
but this doesnt really make any sense to my 1 day old XQuery skills.
how do I specify the document is sent from the command line in the query? what is the context item? and how do I reference it?
(I can do a bit of XSLT 1.0, so I understand the notion of a context).
I think the option is named -s (for source) so you can use -s:books.xml and inside your XQuery main expression any path is evaluated with that document as the context item so you can just use e.g.
for $x in /bookstore/book
where $x/price>30
order by $x/title
return $x/title
and the answer is to drop the doc() function
for $x in bookstore/book
i.e. the same notion as xslt.

Storing files with xquery in exist

Running the following xquery directly in eXide (Eval) it works fine, adding the XML files in MyFSdirectory into the MyCollectionPath:
xquery version "3.1";
let $selected_directory:= 'MyFSdirectory'
let $source-directory := $selected_directory
let $target-collection := 'MyCollectionPath'
return
xmldb:store-files-from-pattern($target-collection, $source-directory, '*.xml')
But when I add it to a function and call it from my app, the store-files-from-pattern is not doing the job (no errors are shown but files are not uploaded), the check point is printed in my screen, so the function is being called correctly. Any hints?
declare function app:upload_file($node as node(), $model as map(*)) {
let $selected_directory:= "MyFSdirectory"
let $source-directory := $selected_directory
let $target-collection := "MyCollectionPath"
return
<p>check point</p> |
xmldb:store-files-from-pattern($target-collection, $source-directory, '*.xml')
};
This sounds like a permissions issue. In other words, when you run the script in eXide, you're likely running as a user (e.g., "admin") with write permissions on the target collection, but in your application the script is likely running as a guest user without the required permission to write to the target collection.
To troubleshoot, add an expression calling xmldb:login() to your app:upload_file() function, supplying the credentials for the user you use in eXide.
If elevating privileges this way works, then the next step would be to consider setting appropriate permissions on the target collection or setting applying setuid or setgid on the module that writes to the database.

Updating embedded triples using xquery in MarkLogic

I tried to update embedded triples in marklogic using xquery but it seems to be not working for embedded triples however the same query is working for other triples
can you tell me if there is some other option which needs to specified while performing an update on embedded triples.
The code i used is
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/Marklogic/semantics.xqy";
let $triples := cts:triples(sem:iri("http://smartlogic.com/document#2012-10-26_DNB.OL_(Citi)_DNB_ASA_(DNB.OL)__Model_Update.61259187.xml"),()())
for $triple in $triples
let $node := sem:database-nodes($triple)
let $replace :=
<sem:triple>
<sem:subject>http://www.example.com/products/1001_Test
</sem:subject>
{$node/sem:predicate, $node/sem:object}
</sem:triple>
return $node ! xdmp:node-replace(., $replace)
My document contains the following triple
<sem:triples xmlns:sem="http://marklogic.com/semantics">
<sem:triple>
<sem:subject>http://smartlogic.com/document#2012-10-26_DNB.OL_(Citi)_DNB_ASA_(DNB.OL)__Model_Update.61259187.xml</sem:subject>
<sem:predicate>http://www.smartlogic.com/schemas/docinfo.rdf#cik</sem:predicate>
<sem:object>datatype="http://www.w3.org/2001/XMLSchema#string</sem:object>
</sem:triple>
</sem:triples>
and i want this particular subject to change into something like this
<sem:subject>http://www.example.com/products/1001_Test</sem:subject>
But when i use the xquery to update it , it does not alter anything, the embedded triple in the documents remains the same.
Because when i tried to see if any of the results have changed to the subject i specified it returned me no results.
I used the following query to test.
SELECT *
WHERE {
<http://www.example.com/products/1001_Test> ?predicate ?object
}
You need to add the option 'all' when you ask for the database nodes backing the triple: sem:database-nodes($triple, 'all').
To be perfectly honest, I am not 100% sure why, but I think this is because your sem:triples element is not the root element of the document it appears on.

How to rename a document in MarkLogic?

I have simple task to do but unable to find the exact solutions for this.I have saved a file as abc.xml in MarkLogic.How can i rename the file as some example.xml using XQuery?
Code which I tried:
xquery version "1.0-ml";
xdmp:document-rename ("/aaa.xml","/final.xml");
This is showing an error.
There is no way, that I know of, to change the document URI of an existing document. The only way I can think of is to create a new document with the same content and the new URI, and delete the existing one, in the same transaction.
Where it gets tricky is to make sure to preserve the ownership, the permissions, all the properties, the property document, make sure that the old URI is not used anywhere to link to the existing document, etc.
But usually, the document URI is never really used. You should first considering whether you really need to rename the document, and why.
(Note that saying "this is showing an error" is rarely useful on SO or on mailing lists, if you do not show what the error is.)
Florent is correct, a true 'rename' is not possible, or perhaps not even meaningful. ( analogy - rename a file from one disk to another )
"Move" however is meaningful (copy then delete in a transaction).
Defining "Move" is use case dependent - i.e. what metatdata also needs to 'move' ? permissions? collections ? document properties ? inherited permissions ?
xmlsh (http://www.xmlsh.org) implements a 'rename' (http://www.xmlsh.org/MarkLogicRename) command for the marklogic extension which is really a 'move', with the implemenation borrowed from postings on markmail (http://markmail.org/)
The implementation is the following XQuery - it doesnt do everything you might want and it might do more then you want. YMMV
https://github.com/DALDEI/xmlsh/blob/master/extensions/marklogic/src/org/xmlsh/marklogic/resources/rename.xquery
( it was also written long ago - it is likely to benefit from improvement )
I have working example this works for me.
xquery version "1.0-ml";
declare function local:document-rename(
$old-uri as xs:string, $new-uri as xs:string)
as empty-sequence()
{
xdmp:document-delete($old-uri),
let $permissions := xdmp:document-get-permissions($old-uri)
let $collections := xdmp:document-get-collections($old-uri)
return xdmp:document-insert(
$new-uri, doc($old-uri),
if ($permissions) then $permissions
else xdmp:default-permissions(),
if ($collections) then $collections
else xdmp:default-collections(),
xdmp:document-get-quality($old-uri)
)
,
let $prop-ns := namespace-uri(<prop:properties/>)
let $properties :=
xdmp:document-properties($old-uri)/node()
[ namespace-uri(.) ne $prop-ns ]
return xdmp:document-set-properties($new-uri, $properties)
};
(: function call :)
local:document-rename ("/opt/backup/x.xml","y.xml");
MarkLogic has a tutorial up addressing file renaming (moving):
https://developer.marklogic.com/recipe/move-a-document/
Importantly, it uses the function xdmp:lock-for-update() to prevent modifications to the source file while it is being copied to the target location.
Also, if you are doing a batch renaming you'll want to make sure that each file URI you rename corresponds to a document in the database or you'll get runtime errors.

XQuery Update: insert or replace depending if node exists not possible?

I am trying to build simple XML database (inside BaseX or eXist-db), but I have trouble figuring how to modify values in document :
content is simple as this for test :
<p>
<pl>
<id>6></id>
</pl>
</p>
I am trying to build something like function which would insert element into <pl> if that element is not present or replace it if it is present. But XQuery is giving me troubles yet :
When I try it head-on with if-then-else logic :
if (exists(/p/pl[id=6]/name)=false)
then insert node <name>thenname</name> into /p/pl[id=6]
else replace value of node /p/pl[id=6]/name with 'elsename'
I get error Error: [XUDY0027] Replace target must not be empty. Clearly I am confused, why the else part is evaluated in both cases, thus the error.
When I empty out the else part :
if (exists(/p/pl[id=6]/name)=true)
then insert node <name>thenname</name> into /p/pl[id=6]
else <dummy/>
Then I get Error: [XUST0001] If expression: no updating expression allowed.
When I try through declaring updating function, even then it reports error :
declare namespace testa='test';
declare updating function testa:bid($a, $b)
{
if (exists(/p/pl[id=6]/name)=true)
then insert node <name>thenname</name> into /p/pl[id=6]
else <dummy/>
};
testa:bid(0,0)
Error: [XUST0001] If expression: no updating expression allowed.
I've got these errors from BaseX 6.5.1 package.
So how can I modify values in a simple fashion if possible ?
If I call insert straight, the there could be multiple elements of same value.
If I call replace it will fail when the node does not exist.
If I delete the node before insert/replace then I could destroy sub-nodes which I don't want.
In most SQL databases, these are quite simple task (like MYSQL 'replace' command).
#Qiqi: #Alejandro is right. Your if expression is incorrect XQuery syntax:
if (exists(/p/pl[id=6]/name))
then insert node <name>thenname</name> into /p/pl[id=6]
else replace value of node /p/pl[id=6]/name with 'elsename'
Note that eXist-db's XQuery Update functionality is currently an eXist-specific implementation, so in eXist (currently) 1.4.x and 1.5dev, you'll want:
if (exists(/p/pl[id=6]/name))
then update insert <name>thenname</name> into /p/pl[id=6]
else update value /p/pl[id=6]/name with 'elsename'
This eXist-specific XQuery Update syntax is documented on http://exist-db.org/update_ext.html. This syntax was developed before the W3C XQuery Update spec had reached its current state. The eXist team plans to make eXist fully compliant with the W3C spec soon, but in the meantime the docs above should help you achieve what you need to if you use eXist.
Note too that your example code contains a typo inside the pl and id elements. The valid XML version would be:
<p>
<pl>
<id>6</id>
</pl>
</p>

Resources