XQuery / eXist-db Bootcamp for Humanists
CIHAM and HiSOMA
University of Lyon @ Online
2021-03-22
Director and CTO of Evolved Binary
XQuery / XSLT / Schema / RelaxNG
Scala / Java / C++ / Rust*
Concurrency and Scalability
Creator of FusionDB multi-model database (5 yrs.)
Contributor to Facebook's RocksDB (5 yrs.)
Core contributor to eXist-db XML Database (15 yrs.)
Founder of EXQuery, and creator of RESTXQ
Was a W3C XQuery WG Invited expert
What are these, and how are they related?
FusionDB
Uses eXist-db components - 100% eXist-db API Compatible
Improved Storage, ACID Transactions, Instant Backup, etc.
Target - Larger projects and organisations
Source Available - Free For Dev. / Requires Licenses for Production Use
Elemental
Hard-fork of eXist-db - 100% eXist-db API Compatible (Release Q2 2021)
New Features and Fixes. Community Roadmap driven.
Target - Smaller projects and users
Source Available - Completely Free to Use
eXist-db
No one owns it
Open Source - Completely Free to Use
What is REST?
eXist-db's REST Server
Using eXist-db's REST API
How to design your own REST API
Plain REST vs. controller.xq vs. RESTXQ
REST - Representational State Transfer
~ Architectural Styles and the Design of Network-based Software Architectures. Fielding, Roy. 2000.
A set of Principles, Properties, and Constraints
Helps guide us to build well-designed Web Applications
Focused on the use of HTTP
RESTful Applications are applications that adhere to REST principles
Not unusual for applications to be partially RESTful
You likely use RESTful applications every day via your Web Browser
Client / Server Architecture
Separate the UI concerns from the Storage concerns
The Web does this! i.e. Web Browser and Web Server
Statelessness
Every request is isolated and can stand alone
Cacheability
Layered System
Code on Demand
Uniform Interface
URIs, HTTP Headers, and HyperMedia
Refers to Extrinsic State (Application State)
Specifically, NOT keeping Application State at the Server
Client maintains its own application state instead
Sends what is needed in each request
Reduces load on the Server
No Session needed per-user
Reduces conditional computation
Horizontal Scaling
Any server can process any request
This breaks Statelessness for REST!
Establish Session
Optional
URI
One per resource (or collection of resources)
All apples (or the only apple): /apple
A specific apple: /apple/pink-lady
Manipulation
Verbs = HTTP Methods: GET
, HEAD
, PUT
, POST
, PATCH
*, DELETE
Self-Descriptive Messages
Media Type
HTTP Headers: Content-Type
, Accept
Hypermedia (HATEOS)
Response should include links to further resources
e.g. HTML
GET
Retrieve a (representation of a) Resource(s)
HEAD
Get but with no representation (body), just metadata (headers)!
PUT
Store/Replace a Resource at URI
POST
Store a Resource as subordinate of URI
Update a Resource (consider PATCH
instead)
DELETE
Delete a Resource(s)
Content Type
Indicates the Internet Media Type (Mime Type) of the Request/Response Body
e.g. Response from GET https://www.google.com
:
Client POST/PUT/PATCH - tells the server the Media Type of the Resource it is sending
Server - tells the client the Media Type of the Resource it is returning
Accept
Indicates one or more Internet Media Types that the client will accept for the Response Body
Content-type: text/html; charset=ISO-8859-1
Hyperlinks - Not just HTML!
HTML
Unbiquitous - understood by your Web-browser
<h2>Apples</h2>
<ul>
<li>
<a href="/apple/pink-lady">Pink Lady</a>
</li>
<li>
<a href="/apple/golden-delicious">Golden Delicious</a>
</li>
<li>
<a href="/apple/hollowcore">Hollowcore</a>
</li>
</ul>
JSON
[
{"name": "Pink Lady", "href": "/apple/pink-lady"},
{"name": "Golden Delicious", "href": "/apple/golden-delicious"},
{"name": "Hollowcore", "href": "/apple/hollowcore"}
]
XML
Syntax for linking is up to you! Various standards exist
<apples xmlns:xlink="http://www.w3.org/1999/xlink">
<apple xlink:href="/apple/pink-lady">
<name>Pink Lady</name>
</apple>
<apple xlink:href="/apple/golden-delicious">
<name>Golden Delicious</name>
</apple>
<apple xlink:href="/apple/hollowcore">
<name>Hollowcore</name>
</apple>
</apples>
API Base URI: /exist/rest/db
REST API for Document Database CRUD Operations
Get Collection
Create Document
Get Document
Replace Document
Update Document
Delete Document
Delete Collection
Additionally
Execute Dynamic Query
Execute Stored Query
HTTP GET http://localhost:8080/exist/rest/db/
Web Browser Example
❯ curl -v -X GET http://localhost:8080/exist/rest/db/
> GET /exist/rest/db/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 10:50:26 GMT
< Content-Type: application/xml; charset=UTF-8
< Last-Modified: Sun, 21 Mar 2021 10:49:57 GMT
< Created: Sun, 21 Mar 2021 10:49:57 GMT
< Vary: Accept-Encoding, User-Agent
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<exist:result xmlns:exist="http://exist.sourceforge.net/NS/exist">
<exist:collection name="/db" created="2021-03-21T11:49:56.377+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x">
<exist:collection name="system" created="2021-03-21T11:49:56.381+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x"/>
<exist:collection name="apps" created="2021-03-21T11:49:57.135+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x"/>
</exist:collection>
</exist:result>
cURL Example:
HTTP GET http://localhost:8080/exist/rest/db/
Python Example:
#!/usr/bin/python3
import http.client
conn = http.client.HTTPConnection("localhost", 8080)
conn.request("GET", "/exist/rest/db/")
response = conn.getresponse()
print(response.status, response.reason)
responseBody = response.read()
print(responseBody.decode())
conn.close()
200 OK
<exist:result xmlns:exist="http://exist.sourceforge.net/NS/exist">
<exist:collection name="/db" created="2021-03-21T11:49:56.377+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x">
<exist:collection name="system" created="2021-03-21T11:49:56.381+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x"/>
<exist:collection name="apps" created="2021-03-21T11:49:57.135+01:00" owner="SYSTEM" group="dba" permissions="rwxr-xr-x"/>
</exist:collection>
</exist:result>
Result:
HTTP PUT vs. POST
Subtly different
Think - Document vs. Collection of Documents
Can be confusing at first!
HTTP PUT - to the URI of the Document
e.g. http://localhost:8080/exist/rest/db/apple/pink-lady.xml
NOTE: If document already exists, it will be replaced!
HTTP POST - to the URI of the Collection in which to store the Document
e.g. http://localhost:8080/exist/rest/db/
Response will tell you the new URI of the Document
WARNING: eXist-db does NOT support this correctly!
eXist-db uses POST only for XQuery and XUpdate.
HTTP PUT http://localhost:8080/exist/rest/db/apple/pink-lady.xml
cURL Example
❯ curl -v -X PUT -H "Content-Type: application/xml" -d "<apple><name>Pink Lady</name></apple>" \
http://admin:@localhost:8080/exist/rest/db/apple/pink-lady.xml
> PUT /exist/rest/db/apple/pink-lady.xml HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/xml
> Content-Length: 23
>
< HTTP/1.1 201 Created
< Date: Sun, 21 Mar 2021 11:48:12 GMT
< Content-Length: 0
< Server: Jetty(9.4.38.v20210224)
<
TIP: Instead of specifying body inline with -d
, use a file:
❯ curl -v -X PUT -H "Content-Type: application/xml" -d "@pink-lady.xml" \
http://admin:@localhost:8080/exist/rest/db/apple/pink-lady.xml
Similar to Get Collection
HTTP GET http://localhost:8080/exist/rest/db/apple/pink-lady.xml
cURL Example
❯ curl -v -X GET http://localhost:8080/exist/rest/db/apple/pink-lady.xml
> GET /exist/rest/db/apple/pink-lady.xml HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 12:31:09 GMT
< Last-Modified: Sun, 21 Mar 2021 11:48:13 GMT
< Created: Sun, 21 Mar 2021 11:48:13 GMT
< Content-Type: application/xml; charset=UTF-8
< Vary: Accept-Encoding, User-Agent
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<apple><name>Pink Lady</name></apple>
Uses XUpdate to describe the changes to make
NOTE: eXist-db should probably use HTTP PATCH instead of POST!
HTTP POST http://localhost:8080/exist/rest/db/apple/pink-lady.xml
cURL Example
❯ curl -v -X POST -H "Content-Type: application/xml" \
-d "<xu:modifications version='1.0' xmlns:xu='http://www.xmldb.org/xupdate'>
<xu:update select='/apple/text()'>Just updated</xu:update>
</xu:modifications>" \
http://admin:@localhost:8080/exist/rest/db/apple/pink-lady.xml
> POST /exist/rest/db/apple/pink-lady.xml HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/xml
> Content-Length: 151
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 12:58:46 GMT
< Content-Type: application/xml; charset=UTF-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<?xml version="1.0" ?><exist:modifications xmlns:exist="http://exist.sourceforge.net/NS/exist" count="1">1 modifications processed.</exist:modifications>
Can also be applied to an entire Collection!
HTTP POST http://localhost:8080/exist/rest/db/
Alternatives:
HTTP POST an XQuery containing XQuery Update expression(s)
HTTP GET or POST to a Stored XQuery
Similar to Get Document
HTTP DELETE http://localhost:8080/exist/rest/db/apple/pink-lady.xml
cURL Example
❯ curl -v -X DELETE http://admin:@localhost:8080/exist/rest/db/apple/pink-lady.xml
> DELETE /exist/rest/db/apple/pink-lady.xml HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 13:12:15 GMT
< Content-Length: 0
< Server: Jetty(9.4.38.v20210224)
<
Similar to Get Collection
HTTP DELETE http://localhost:8080/exist/rest/db/apple
cURL Example
❯ curl -v -X DELETE http://admin:@localhost:8080/exist/rest/db/apple
> DELETE /exist/rest/db/apple HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 13:15:53 GMT
< Content-Length: 0
< Server: Jetty(9.4.38.v20210224)
<
You can either HTTP GET or HTTP POST
XQuery Context is the Collection or Document indicated by the URI
HTTP GET http://localhost:8080/exist/rest/db/?_query=/apple
Useful for simple/short queries
Query and Parameters are sent as URL Query String Parameters
Parameters must be URL Encoded
Variety of parameters available, e.g. _start
, _howmany
See eXist-db docs - Get Requests
HTTP POST http://localhost:8080/exist/rest/db/
Useful for complex/long queries
Query and Parameters are sent within HTTP Request Body
Variety of parameters available, See eXist-db docs - Post Requests
cURL Example
❯ curl -v http://localhost:8080/exist/rest/db/?_query=/apple&_start=1&_howmany=10
> GET /exist/rest/db/?_query=/example HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 18:08:00 GMT
< Content-Type: application/xml; charset=UTF-8
< Vary: Accept-Encoding, User-Agent
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<exist:result xmlns:exist="http://exist.sourceforge.net/NS/exist" exist:hits="1" exist:start="1" exist:count="1"
exist:compilation-time="0" exist:execution-time="0">
<apple><name>Pink Lady</name></apple>
</exist:result>
TIP: specify _wrap=no
if you want the raw results
cURL Example
❯ curl -v -X POST -H "Content-Type: application/xml" -d "
<query xmlns='http://exist.sourceforge.net/NS/exist' start='1' max='10'>
<text><![CDATA[
/apple
]]></text>
</query>" http://localhost:8080/exist/rest/db/
> POST /exist/rest/db/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/xml
> Content-Length: 126
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 18:30:06 GMT
< Content-Type: application/xml; charset=UTF-8
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<exist:result xmlns:exist="http://exist.sourceforge.net/NS/exist" exist:hits="1" exist:start="1" exist:count="1"
exist:compilation-time="1" exist:execution-time="1">
<apple><name>Pink Lady</name></apple>
</exist:result>
TIP: specify wrap="no"
if you want the raw results
You can store an XQuery into the database like any other document!
Use the Content-Type: application/xquery
HTTP PUT http://localhost:8080/exist/rest/db/time.xq
cURL Example:
❯ curl -v -X PUT -H "Content-Type: application/xquery" -d "<now>{current-dateTime()}</now>" \
http://admin:@localhost:8080/exist/rest/db/time.xq
> PUT /exist/rest/db/time.xq HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/xquery
> Content-Length: 31
>
< HTTP/1.1 201 Created
< Date: Sun, 21 Mar 2021 18:42:54 GMT
< Content-Length: 0
< Server: Jetty(9.4.38.v20210224)
<
Executable via HTTP GET or POST
HTTP GET http://localhost:8080/exist/rest/db/time.xq
cURL Example:
❯ curl -v -X GET http://admin:@localhost:8080/exist/rest/db/time.xq
> GET /exist/rest/db/time.xq HTTP/1.1
> Host: localhost:8080
> Authorization: Basic YWRtaW46
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 21 Mar 2021 18:46:44 GMT
< X-XQuery-Cached: false
< Content-Type: application/xml; charset=UTF-8
< Vary: Accept-Encoding, User-Agent
< Transfer-Encoding: chunked
< Server: Jetty(9.4.38.v20210224)
<
<now>2021-03-21T19:46:44.393+01:00</now>
Stored Query can read HTTP Request
e.g. request:get-parameter#2
Consider Resources first (the things you talk about)
Design URI that point to Resources and Collections of Resources
Consider Representations (of Resources) second
One or more representations, e.g. HTML, XML, JSON, etc.
Use HTTP Content-Type
and Accept
Headers to negotiate
Consider actions upon those resources
Apply HTTP Methods as Verbs to achieve the actions
Don't forget Hypermedia (i.e. navigability via Hyperlinks)
There are Models for evaluating RESTful'ness
Consider using RESTXQ first
Guides you to create a "RESTful" approach
Enforces Statelessness
RESTXQ Documentation and Video: http://www.adamretter.org.uk/presentations.xml#xmlprague12
REST Server with Stored Queries
Is just enough to create a REST API of your own
Not as pure as RESTXQ
REST Server Documentation: http://www.exist-db.org/exist/apps/doc/devguide_rest
controller.xq
Just adds URI Routing/Rewriting to REST Server
URL Rewrite Documentation: http://www.exist-db.org/exist/apps/doc/urlrewrite