Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merged in changes from the MarkLogic branch.

  • Loading branch information...
commit 3b880f9e57310ef8518efad66a50f3d65944ce3a 1 parent d575717
@ryangrimm authored
View
72 README.markdown
@@ -62,9 +62,13 @@ change the URL structure or add in more rules if need be there.
### Files relevant to the end user
- data/lib/rewriter.xqy - A URL rewriter for the REST calls
- config/endpoints.xqy - Configuration for the REST endpoints
- - data/lib/json.xqy - Has two public functions:
+ - data/lib/json.xqy - Library module for handling JSON, see comments inside file for details on each function
- jsonToXML - parses a JSON string into XML that can be stored in MarkLogic
- xmlToJSON - parses the generated XML into a JSON string
+ - document - constructs a JSON document
+ - object - constructs a JSON object
+ - array - constructs a JSON array
+ - null - constructs a JSON null
- data/lib/json-query.xqy - Tinkering with ways to query the stored JSON
## REST Capabilities
@@ -131,7 +135,71 @@ multiple values for the same key are or'd together.
- /data/kvquery?foo=bar&baz=yaz - Document that has a 'foo' key with a value of 'bar' and a 'baz' key with a value of 'yaz'
- /data/kvquery?foo=bar&foo=bar - Document that has a 'foo' key with a value of 'bar' or 'baz'
-### Server information
+## Index management
+### Fields
+A field groups together a number of keys so their values are treated as one
+block of text. Lets say you have two keys, "first_name" and "last_name" that
+you'd like to query as though they were one key "name". Creating a field
+called "name" that includes both "first_name" and "last_name" is an easy way to
+accomplish this.
+
+#### Get info about a field
+Returns what keys are included and excluded in a field.
+
+ - Request type: GET
+ - Example:
+ - /data/manage/field/my_field_name
+
+#### Create a field
+Creates a field in the database. Fields can not share the same name as range
+indexes or aliases, they must be unique. If the value of the incluced key is an
+object, you can exclude child keys in that object by adding an exclude
+parameter in the request.
+
+ - Request type: PUT|POST
+ - Examples:
+ - /data/manage/field/my_field_name?include=first_name&include=last_name
+ - /data/manage/field/my_field_name?include=first_name&include=last_name&exclude=middle_name
+
+#### Delete a field
+Deletes the field.
+
+ - Request type: PUT|POST
+ - Example:
+ - /data/manage/field/my_field_name
+
+### Range indexes
+A range index allows you to perform queries over a range of values. For
+example, if you want to fetch all documents where the "pub_date" is after
+2011-01-01, you would add a range index on the "pub_date" key.
+
+#### Get info about a range index
+Returns information about how a range index is configured.
+
+ - Request type: GET
+ - Example:
+ - /data/manage/range/publication-date
+
+#### Create a range index
+Creates a range index in the database. Range indexes can not share the same name as fields
+or aliases, they must be unique. You must specify the key to create the range
+index on, the datatype (number, string or date) and the operator (eq, ne, lt,
+le, gt or ge).
+
+ - Request type: PUT|POST
+ - Examples:
+ - /data/manage/range/date?key=pub_date&datatype=date&operator=eq
+ - /data/manage/range/date-before?key=pub_date&datatype=date&operator=lt
+ - /data/manage/range/date-after?key=pub_date&datatype=date&operator=gt
+
+#### Delete a range index
+Deletes the range index.
+
+ - Request type: DELETE
+ - Example:
+ - /data/manage/range/date-before
+
+## Server information
Information about the MarkLogic server version, hardware and index settings can be obtained with an info request.
- Request type: GET
View
22 config/endpoints.xqy
@@ -27,10 +27,30 @@ declare variable $endpoints:ENDPOINTS as element(rest:options) :=
</request>
<!-- Key value queryies -->
- <request uri="^/data/kvquery(/)?$" endpoint="/data/kvquery.xqy" user-params="allow"/>
+ <request uri="^/data/kvquery(/|/(\d+)/?|/(\d+)/(\d+)/?)?$" endpoint="/data/kvquery.xqy" user-params="allow">
+ <uri-param name="__MLJSONURL__:index">$2</uri-param>
+ <uri-param name="__MLJSONURL__:start">$3</uri-param>
+ <uri-param name="__MLJSONURL__:end">$4</uri-param>
+ </request>
<!-- Info request -->
<request uri="^/data/info(/)?$" endpoint="/data/info.xqy" user-params="ignore"/>
+
+ <request uri="^/data/manage/field/([A-Za-z0-9-]+)(/)?$" endpoint="/data/manage/field.xqy" user-params="allow">
+ <uri-param name="name" as="string">$1</uri-param>
+ <http method="GET"/>
+ <http method="POST"/>
+ <http method="PUT"/>
+ <http method="DELETE"/>
+ </request>
+
+ <request uri="^/data/manage/range/([A-Za-z0-9-]+)(/)?$" endpoint="/data/manage/range.xqy" user-params="allow">
+ <uri-param name="name" as="string">$1</uri-param>
+ <http method="GET"/>
+ <http method="POST"/>
+ <http method="PUT"/>
+ <http method="DELETE"/>
+ </request>
</options>;
declare function endpoints:options(
View
92 data/info.xqy
@@ -17,6 +17,7 @@ limitations under the License.
xquery version "1.0-ml";
import module namespace json="http://marklogic.com/json" at "lib/json.xqy";
+import module namespace manage="http://marklogic.com/mljson/manage" at "lib/manage.xqy";
import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
declare option xdmp:mapping "false";
@@ -24,58 +25,43 @@ declare option xdmp:mapping "false";
let $config := admin:get-configuration()
let $database := xdmp:database()
let $json :=
-<json type="object">
- <version type="string">{ xdmp:version() }</version>
- <architecture type="string">{ xdmp:architecture() }</architecture>
- <platform type="string">{ xdmp:platform() }</platform>
- <hosts type="array">{
- for $host in xdmp:hosts()
- return <item type="object">
- <id type="number">{ $host }</id>
- <name type="string">{ xdmp:host-name($host) }</name>
- </item>
- }</hosts>
- <indexes type="object">
- <stemming type="string">{ admin:database-get-stemmed-searches($config, $database) }</stemming>
- <uris boolean="{ admin:database-get-uri-lexicon($config, $database) }"/>
- <collectionLexicon boolean="{ admin:database-get-collection-lexicon($config, $database) }"/>
- <caseSensitive boolean="{ admin:database-get-fast-case-sensitive-searches($config, $database) }"/>
- <diacriticSensitive boolean="{ admin:database-get-fast-diacritic-sensitive-searches($config, $database) }"/>
- <keyValueCharacters boolean="{ admin:database-get-fast-element-character-searches($config, $database) }"/>
- <keyValueWords boolean="{ admin:database-get-fast-element-word-searches($config, $database) }"/>
- <keyValuePhrases boolean="{ admin:database-get-fast-element-phrase-searches($config, $database) }"/>
- <keyValueTrailingWildcards boolean="{ admin:database-get-fast-element-trailing-wildcard-searches($config, $database) }"/>
- <geo type="array">
- </geo>
- <keyValueRanges type="array">{
- for $index in admin:database-get-range-element-indexes($config, $database)
- for $key in tokenize(string($index/*:localname), " ")
- where string-length(string($index/*:namespace-uri)) = 0
- return <item type="object">
- <type type="string">{ string($index/*:scalar-type) }</type>
- <key type="string">{ json:unescapeNCName($key) }</key>
- { if($index/*:scalar-type = "string") then <collation type="string">{ string($index/*:collation) }</collation> else () }
- </item>
- }</keyValueRanges>
- <fields type="array">{
- for $field in admin:database-get-fields($config, $database)
- where string-length(string($field/*:name))
- return <item type="object">
- <name type="string">{ string($field/*:name) }</name>
- <includedKeys type="array">{
- for $key in tokenize(string($field/*:included-elements), " ")
- return <item type="string">{ json:unescapeNCName($key) }</item>
- }</includedKeys>
- <excludedKeys type="array">{
- for $key in tokenize(string($field/*:excluded-elements), " ")
- return <item type="string">{ json:unescapeNCName($key) }</item>
- }</excludedKeys>
- </item>
- }</fields>
- </indexes>
- <settings type="object">
- <directoryCreation type="string">{ admin:database-get-directory-creation($config, $database) }</directoryCreation>
- </settings>
-</json>
+json:document(
+ json:object((
+ "version", xdmp:version(),
+ "architecture", xdmp:architecture(),
+ "platform", xdmp:platform(),
+ "hosts", json:array((
+ for $host in xdmp:hosts()
+ return json:object((
+ "id", $host,
+ "name", xdmp:host-name($host)
+ ))
+ )),
+ "indexes", json:object((
+ "stemming", admin:database-get-stemmed-searches($config, $database),
+ "uris", admin:database-get-uri-lexicon($config, $database),
+ "collectionLexicon", admin:database-get-collection-lexicon($config, $database),
+ "caseSensitive", admin:database-get-fast-case-sensitive-searches($config, $database),
+ "diacriticSensitive", admin:database-get-fast-diacritic-sensitive-searches($config, $database),
+ "keyValueCharacters", admin:database-get-fast-element-character-searches($config, $database),
+ "keyValueWords", admin:database-get-fast-element-word-searches($config, $database),
+ "keyValuePhrases", admin:database-get-fast-element-phrase-searches($config, $database),
+ "keyValueTrailingWildcards", admin:database-get-fast-element-trailing-wildcard-searches($config, $database),
+ "geo", json:array(),
+ "ranges", json:array(manage:getRangeDefinitions()),
+ "fields", json:array(
+ for $field in admin:database-get-fields($config, $database)
+ where string-length($field/*:field-name) > 0
+ return manage:fieldDefinitionToJsonXml($field)
+ )
+ )),
+ "settings", json:object((
+ "directoryCreation", admin:database-get-directory-creation($config, $database)
+ )),
+ "statistics", json:object((
+ "documentCount", xdmp:estimate(collection())
+ ))
+ ))
+)
return json:xmlToJSON($json)
View
7 data/jsonquery.xqy
@@ -19,6 +19,8 @@ xquery version "1.0-ml";
import module namespace jsonquery="http://marklogic.com/json-query" at "lib/json-query.xqy";
import module namespace json="http://marklogic.com/json" at "lib/json.xqy";
+declare option xdmp:mapping "false";
+
let $requestMethod := xdmp:get-request-method()
let $query := string(xdmp:get-request-field("q", "{}")[1])
let $query :=
@@ -28,8 +30,5 @@ let $query :=
return
if($requestMethod = ("GET", "POST"))
- then
- let $docs := jsonquery:execute($query)
- let $jsonDocs := for $doc in $docs return json:xmlToJSON($doc)
- return concat("{""count"":", count($jsonDocs) , ", ""results"":[", string-join($jsonDocs, ","), "]}")
+ then jsonquery:execute($query)
else ()
View
44 data/kvquery.xqy
@@ -16,10 +16,50 @@ limitations under the License.
xquery version "1.0-ml";
+import module namespace common="http://marklogic.com/mljson/common" at "lib/common.xqy";
import module namespace json="http://marklogic.com/json" at "lib/json.xqy";
+declare option xdmp:mapping "false";
+
+let $index := xdmp:get-request-field("__MLJSONURL__:index")
+let $index :=
+ if($index castable as xs:integer)
+ then xs:integer($index)
+ else 1
+
+let $start := xdmp:get-request-field("__MLJSONURL__:start")
+let $start :=
+ if($start castable as xs:integer)
+ then xs:integer($start)
+ else $index
+
+let $end := xdmp:get-request-field("__MLJSONURL__:end")
+let $end :=
+ if($end castable as xs:integer)
+ then xs:integer($end)
+ else $start
+
let $query := cts:and-query(
for $key in xdmp:get-request-field-names()
- return cts:element-value-query(xs:QName(json:escapeNCName($key)), xdmp:get-request-field($key))
+ where not(starts-with($key, "__MLJSONURL__:"))
+ return cts:element-value-query(xs:QName(concat("json:", json:escapeNCName($key))), xdmp:get-request-field($key))
)
-return json:xmlToJSON(cts:search(/json, $query)[1])
+
+let $results :=
+ if(exists($start) and exists($end) and $end > $start)
+ then cts:search(/json:json, $query)[$start to $end]
+ else if(exists($start))
+ then cts:search(/json:json, $query)[$start]
+ else ()
+
+let $total :=
+ if(exists($results[1]))
+ then cts:remainder($results[1]) + $start - 1
+ else 0
+
+let $end :=
+ if($end > $total)
+ then $total
+ else $end
+
+return common:outputMultipleDocs($results, $start, $end, $total)
View
72 data/lib/common.xqy
@@ -0,0 +1,72 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+module namespace common="http://marklogic.com/mljson/common";
+import module namespace json="http://marklogic.com/json" at "json.xqy";
+
+declare default function namespace "http://www.w3.org/2005/xpath-functions";
+
+
+declare function common:error(
+ $statusCode as xs:integer,
+ $message as xs:string
+) as xs:string
+{
+ let $set := xdmp:set-response-code($statusCode, $message)
+ let $add := xdmp:add-response-header("Date", string(current-dateTime()))
+ let $response :=
+ json:document(
+ json:object((
+ "error", json:object((
+ "code", $statusCode,
+ "message", $message
+ ))
+ ))
+ )
+ return json:xmlToJSON($response)
+};
+
+declare function common:outputMultipleDocs(
+ $docs as element(json:json)*,
+ $start as xs:integer,
+ $end as xs:integer?,
+ $total as xs:integer
+) as xs:string
+{
+ let $end :=
+ if(empty($end))
+ then $start
+ else $end
+
+ return json:xmlToJSON(
+ json:object((
+ "meta", json:object((
+ "start", $start,
+ "end", $end,
+ "total", $total
+ )),
+ "results", json:array(
+ for $doc in $docs
+ return json:object((
+ "uri", base-uri($doc),
+ "data", $doc
+ ))
+ )
+ ))
+ )
+};
View
470 data/lib/json-query.xqy
@@ -16,328 +16,229 @@ limitations under the License.
module namespace jsonquery="http://marklogic.com/json-query";
+import module namespace common="http://marklogic.com/mljson/common" at "common.xqy";
import module namespace json="http://marklogic.com/json" at "json.xqy";
+
declare default function namespace "http://www.w3.org/2005/xpath-functions";
-declare function jsonquery:execute(
- $json as xs:string
-) as element(json)*
-{
- let $tree := json:jsonToXML($json)
- return
- if(exists($tree/fulltext))
- then jsonquery:executeFulltext($tree)
- else xdmp:eval(jsonquery:parsePath($tree))
-};
declare function jsonquery:parse(
$json as xs:string
) as xs:string
{
let $tree := json:jsonToXML($json)
- return
- if(exists($tree/fulltext))
- then jsonquery:parseFulltext($tree)
- else jsonquery:parsePath($tree)
-};
-
-declare private function jsonquery:parsePath(
- $tree as element(json)
-) as xs:string
-{
- let $basicPredicate := jsonquery:processStep($tree)
- let $path :=
- if($basicPredicate != "")
- then concat("/json[", $basicPredicate, "]")
- else "/json"
-
- let $orPredicate := string-join(
- for $item in $tree/or[@type = "array"]/item
- return jsonquery:processStep($item)
- , " or ")
- let $path :=
- if($orPredicate != "")
- then concat($path, "[", $orPredicate, "]")
- else $path
-
- let $andPredicate := string-join(
- for $item in $tree/and[@type = "array"]/item
- return jsonquery:processStep($item)
- , " and ")
- let $path :=
- if($andPredicate != "")
- then concat($path, "[", $andPredicate, "]")
- else $path
-
- let $position := jsonquery:extractPosition($tree)
- return
- if(exists($position))
- then concat("(", $path, ")[", $position, "]")
- else $path
-};
-
-declare private function jsonquery:processStep(
- $step as element()
-) as xs:string
-{
- if($step/@type = "number")
- then string($step)
- else if($step/@type = "string")
- then concat("""", string($step), """")
- else if($step/@type = "object")
- then concat(if(local-name($step) = "json" or local-name($step/..) = ("or", "and")) then "" else "/", jsonquery:generatePredicate($step))
- else ""
-};
-
-declare private function jsonquery:generatePredicate(
- $step as element()
-) as xs:string
-{
- let $key := $step/key[@type = "string"]
- let $innerKey := $step/innerKey[@type = "string"]
- let $key :=
- if(exists($innerKey) and local-name($step) = "json" and empty($key))
- then concat("//", string($innerKey))
- else if(exists($innerKey) and empty($key))
- then concat("/", string($innerKey))
- else $key
- let $value := $step/value
- let $operator := string(($step/comparison, "=")[1])
- return
- if(exists($key) and exists($value))
- then
- if($value/@type = "string")
- then concat(string($key), " ", $operator, " """, string($value), """")
- else if($value/@type = "number")
- then concat(string($key), " ", $operator, " ", string($value))
- else if($value/@type = "array")
- then
- let $bits :=
- for $item in $value/item
- where $item/@type = ("string", "number")
- return jsonquery:processStep($item)
- let $raw := concat(string($key), " = (", string-join($bits, ", "), ")")
- return
- if($operator = "!=")
- then concat("not(", $raw, ")")
- else $raw
- else if($value/@type = "object")
- then
- if(empty($value//value) and local-name($value/..) = "json")
- then concat("exists(", $key, jsonquery:processStep($value), ")")
- else concat($key, jsonquery:processStep($value))
- else ""
- else if(exists($key) and empty($value) and local-name($key/..) = "json")
- then concat("exists(", $key, ")")
- else if(exists($key) and empty($value))
- then string($key)
- else ""
-};
-
-
-declare private function jsonquery:parseFulltext(
- $tree as element(json)
-) as xs:string
-{
- let $cts := jsonquery:dispatchFulltextStep($tree/fulltext)
- let $options := jsonquery:extractOptions($tree/fulltext, "search")
- let $position := jsonquery:extractPosition($tree)
+ let $cts := jsonquery:dispatch($tree/query)
+ let $options := jsonquery:extractOptions($tree, "search")
+ let $start := jsonquery:extractStartIndex($tree)
+ let $end := jsonquery:extractEndIndex($tree)
let $weight := jsonquery:extractWeight($tree)
return
- if(exists($position))
- then concat("cts:search(/json, ", $cts, ", ", xdmp:describe($options), ", ", $weight, ")", "[", $position, "]")
- else concat("cts:search(/json, ", $cts, ", ", xdmp:describe($options), ", ", $weight, ")")
+ if(exists($start) and exists($end))
+ then concat("cts:search(/json:json, ", $cts, ", ", xdmp:describe($options), ", ", $weight, ")", "[", $start, " to ", $end, "]")
+ else if(exists($start))
+ then concat("cts:search(/json:json, ", $cts, ", ", xdmp:describe($options), ", ", $weight, ")", "[", $start, "]")
+ else concat("cts:search(/json:json, ", $cts, ", ", xdmp:describe($options), ", ", $weight, ")")
};
-declare private function jsonquery:executeFulltext(
- $tree as element(json)
+declare function jsonquery:execute(
+ $json as xs:string
)
{
- let $cts := jsonquery:dispatchFulltextStep($tree/fulltext)
- let $options := jsonquery:extractOptions($tree/fulltext, "search")
- let $position := jsonquery:extractPosition($tree)
+ let $tree := json:jsonToXML($json)
+ let $cts := jsonquery:dispatch($tree/json:query)
+ let $options := jsonquery:extractOptions($tree, "search")
+ let $start := jsonquery:extractStartIndex($tree)
+ let $end := jsonquery:extractEndIndex($tree)
let $weight := jsonquery:extractWeight($tree)
- let $bits := tokenize($position, " to ")
- let $positionLow := $bits[1]
- let $positionHigh := $bits[2]
let $debug :=
- if($tree/debug[@boolean = "true"])
+ if($tree/json:debug[@boolean = "true"])
then (xdmp:log(concat("Constructed search constraint: ", $cts)), xdmp:log(concat("Constructed search options: ", string-join($options, ", "))))
else ()
- return
- if(exists($positionLow) and empty($positionHigh))
- then cts:search(/json, $cts, $options, $weight)[xs:integer($positionLow)]
- else if(exists($positionLow) and exists($positionHigh) and $positionHigh = "last()")
- then cts:search(/json, $cts, $options, $weight)[xs:integer($positionLow) to last()]
- else if(exists($positionLow) and exists($positionHigh))
- then cts:search(/json, $cts, $options, $weight)[xs:integer($positionLow) to xs:integer($positionHigh)]
- else cts:search(/json, $cts, $options, $weight)
+
+ let $results :=
+ if(exists($start) and exists($end))
+ then cts:search(/json:json, $cts, $options, $weight)[$start to $end]
+ else if(exists($start))
+ then cts:search(/json:json, $cts, $options, $weight)[$start]
+ else cts:search(/json:json, $cts, $options, $weight)
+
+ let $total :=
+ if(exists($results[1]))
+ then cts:remainder($results[1]) + $start - 1
+ else 0
+
+ let $end :=
+ if($end > $total)
+ then $total
+ else $end
+
+ return common:outputMultipleDocs($results, $start, $end, $total)
};
-declare private function jsonquery:dispatchFulltextStep(
+declare private function jsonquery:dispatch(
$step as element()
)
{
let $precedent := (
- $step/and[@type = "array"],
- $step/or[@type = "array"],
- $step/not[@type = "object"],
- $step/andNot[@type = "object"],
- $step/property[@type = "object"],
- $step/range[@type = "object"],
- $step/equals[@type = "object"],
- $step/contains[@type = "object"],
- $step/collection[@type = "string"],
- $step/geo[@type = "object"],
- $step/point[@type = "object"],
- $step/circle[@type = "object"],
- $step/box[@type = "object"],
- $step/polygon[@type = "array"]
+ $step/json:and[@type = "array"],
+ $step/json:or[@type = "array"],
+ $step/json:not[@type = "object"],
+ $step/json:andNot[@type = "object"],
+ $step/json:property[@type = "object"],
+ $step/json:range[@type = "object"],
+ $step/json:equals[@type = "object"],
+ $step/json:contains[@type = "object"],
+ $step/json:collection[@type = "string"],
+ $step/json:geo[@type = "object"],
+ $step/json:point[@type = "object"],
+ $step/json:circle[@type = "object"],
+ $step/json:box[@type = "object"],
+ $step/json:polygon[@type = "array"]
)[1]
- return jsonquery:processFulltextStep($precedent)
+ return jsonquery:process($precedent)
};
-declare private function jsonquery:processFulltextStep(
+declare private function jsonquery:process(
$step as element()
)
{
typeswitch($step)
- case element(item) return jsonquery:dispatchFulltextStep($step)
- case element(and) return cts:and-query(for $item in $step/item[@type = "object"] return jsonquery:processFulltextStep($item))
- case element(or) return cts:or-query(for $item in $step/item[@type = "object"] return jsonquery:processFulltextStep($item))
- case element(not) return cts:not-query(jsonquery:dispatchFulltextStep($step))
- case element(andNot) return jsonquery:handleFulltextAndNot($step)
- case element(property) return cts:properties-query(jsonquery:dispatchFulltextStep($step))
- case element(range) return jsonquery:handleFulltextRange($step)
- case element(equals) return jsonquery:handleFulltextEquals($step)
- case element(contains) return jsonquery:handleFulltextContains($step)
- case element(collection) return jsonquery:handleFulltextCollection($step)
- case element(geo) return jsonquery:handleFulltextGeo($step)
- case element(region) return for $item in $step/item[@type = "object"] return jsonquery:processFulltextStep($item)
- case element(point) return jsonquery:handleFulltextGeoPoint($step)
- case element(circle) return jsonquery:handleFulltextGeoCircle($step)
- case element(box) return jsonquery:handleFulltextGeoBox($step)
- case element(polygon) return jsonquery:handleFulltextGeoPolygon($step)
+ case element(json:item) return jsonquery:dispatch($step)
+ case element(json:and) return cts:and-query(for $item in $step/json:item[@type = "object"] return jsonquery:process($item))
+ case element(json:or) return cts:or-query(for $item in $step/json:item[@type = "object"] return jsonquery:process($item))
+ case element(json:not) return cts:not-query(jsonquery:dispatch($step))
+ case element(json:andNot) return jsonquery:handleAndNot($step)
+ case element(json:property) return cts:properties-query(jsonquery:dispatch($step))
+ case element(json:range) return jsonquery:handleRange($step)
+ case element(json:equals) return jsonquery:handleEquals($step)
+ case element(json:contains) return jsonquery:handleContains($step)
+ case element(json:collection) return jsonquery:handleCollection($step)
+ case element(json:geo) return jsonquery:handleGeo($step)
+ case element(json:region) return for $item in $step/json:item[@type = "object"] return jsonquery:process($item)
+ case element(json:point) return jsonquery:handleGeoPoint($step)
+ case element(json:circle) return jsonquery:handleGeoCircle($step)
+ case element(json:box) return jsonquery:handleGeoBox($step)
+ case element(json:polygon) return jsonquery:handleGeoPolygon($step)
default return ()
};
-declare private function jsonquery:handleFulltextAndNot(
- $step as element(andNot)
+declare private function jsonquery:handleAndNot(
+ $step as element(json:andNot)
) as cts:and-not-query?
{
- let $positive := $step/positive[@type = "object"]
- let $negative := $step/negative[@type = "object"]
+ let $positive := $step/json:positive[@type = "object"]
+ let $negative := $step/json:negative[@type = "object"]
where exists($positive) and exists($negative)
- return cts:and-not-query(jsonquery:dispatchFulltextStep($positive), jsonquery:dispatchFulltextStep($negative))
+ return cts:and-not-query(jsonquery:dispatch($positive), jsonquery:dispatch($negative))
};
-declare private function jsonquery:handleFulltextRange(
- $step as element(range)
+declare private function jsonquery:handleRange(
+ $step as element(json:range)
) as cts:query?
{
- if(exists($step/from[@type = "string"]) and exists($step/to[@type = "string"]))
+ if(exists($step/json:from[@type = "string"]) and exists($step/json:to[@type = "string"]))
then
- let $key := $step/key[@type = "string"]
- let $weight := xs:double(($step/weight[@type = "number"], 1.0)[1])
+ let $key := $step/json:key[@type = "string"]
+ let $weight := xs:double(($step/json:weight[@type = "number"], 1.0)[1])
let $options := jsonquery:extractOptions($step, "range")
where exists($key)
return cts:and-query((
- cts:element-range-query(xs:QName($key), ">=", $step/from, $options, $weight),
- cts:element-range-query(xs:QName($key), "<=", $step/to, $options, $weight)
+ cts:element-range-query(xs:QName(concat("json:", $key)), ">=", $step/json:from, $options, $weight),
+ cts:element-range-query(xs:QName(concat("json:", $key)), "<=", $step/json:to, $options, $weight)
))
else
- let $key := $step/key[@type = "string"]
- let $operator := ($step/operator[@type = "string"][. = ("=", "!=", "<", ">", "<=", ">=")], "=")[1]
- let $value := jsonquery:stringOrArrayToSet($step/value)
- let $weight := xs:double(($step/weight[@type = "number"], 1.0)[1])
+ let $key := $step/json:key[@type = "string"]
+ let $operator := ($step/json:operator[@type = "string"][. = ("=", "!=", "<", ">", "<=", ">=")], "=")[1]
+ let $value := jsonquery:stringOrArrayToSet($step/json:value)
+ let $weight := xs:double(($step/json:weight[@type = "number"], 1.0)[1])
where exists($key) and exists($value)
- return cts:element-range-query(xs:QName($key), $operator, $value, jsonquery:extractOptions($step, "range"), $weight)
+ return cts:element-range-query(xs:QName(concat("json:", $key)), $operator, $value, jsonquery:extractOptions($step, "range"), $weight)
};
-declare private function jsonquery:handleFulltextEquals(
- $step as element(equals)
+declare private function jsonquery:handleEquals(
+ $step as element(json:equals)
) as cts:element-value-query?
{
- let $key := $step/key[@type = "string"]
- let $string := jsonquery:stringOrArrayToSet($step/string)
- let $weight := xs:double(($step/weight[@type = "number"], 1.0)[1])
+ let $key := $step/json:key[@type = "string"]
+ let $string := jsonquery:stringOrArrayToSet($step/json:string)
+ let $weight := xs:double(($step/json:weight[@type = "number"], 1.0)[1])
where exists($key) and exists($string)
- return cts:element-value-query(xs:QName($key), $string, jsonquery:extractOptions($step, "word"), $weight)
+ return cts:element-value-query(xs:QName(concat("json:", $key)), $string, jsonquery:extractOptions($step, "word"), $weight)
};
-declare private function jsonquery:handleFulltextContains(
- $step as element(contains)
+declare private function jsonquery:handleContains(
+ $step as element(json:contains)
) as cts:element-word-query?
{
- let $key := $step/key[@type = "string"]
- let $string := jsonquery:stringOrArrayToSet($step/string)
- let $weight := xs:double(($step/weight[@type = "number"], 1.0)[1])
+ let $key := $step/json:key[@type = "string"]
+ let $string := jsonquery:stringOrArrayToSet($step/json:string)
+ let $weight := xs:double(($step/json:weight[@type = "number"], 1.0)[1])
where exists($key) and exists($string)
- return cts:element-word-query(xs:QName($key), $string, jsonquery:extractOptions($step, "word"), $weight)
+ return cts:element-word-query(xs:QName(concat("json:", $key)), $string, jsonquery:extractOptions($step, "word"), $weight)
};
-declare private function jsonquery:handleFulltextCollection(
- $step as element(collection)
+declare private function jsonquery:handleCollection(
+ $step as element(json:collection)
) as cts:collection-query
{
cts:collection-query(jsonquery:stringOrArrayToSet($step))
};
-declare private function jsonquery:handleFulltextGeo(
- $step as element(geo)
+declare private function jsonquery:handleGeo(
+ $step as element(json:geo)
) as cts:query?
{
- let $parent := $step/parent[@type = "string"]
- let $key := $step/key[@type = "string"]
- let $latKey := $step/latKey[@type = "string"]
- let $longKey := $step/longKey[@type = "string"]
+ let $parent := $step/json:parent[@type = "string"]
+ let $key := $step/json:key[@type = "string"]
+ let $latKey := $step/json:latKey[@type = "string"]
+ let $longKey := $step/json:longKey[@type = "string"]
- let $weight := xs:double(($step/weight[@type = "number"], 1.0)[1])
+ let $weight := xs:double(($step/json:weight[@type = "number"], 1.0)[1])
where exists($key) or (exists($latKey) and exists($longKey))
return
if(exists($parent) and exists($latKey) and exists($longKey))
- then cts:element-pair-geospatial-query(xs:QName($parent), xs:QName($latKey), xs:QName($longKey), jsonquery:processFulltextStep($step/region), jsonquery:extractOptions($step, "geo"), $weight)
+ then cts:element-pair-geospatial-query(xs:QName(concat("json:", $parent)), xs:QName(concat("json:", $latKey)), xs:QName(concat("json:", $longKey)), jsonquery:process($step/json:region), jsonquery:extractOptions($step, "geo"), $weight)
else if(exists($parent) and exists($key))
- then cts:element-child-geospatial-query(xs:QName($parent), xs:QName($key), jsonquery:processFulltextStep($step/region), jsonquery:extractOptions($step, "geo"), $weight)
+ then cts:element-child-geospatial-query(xs:QName(concat("json:", $parent)), xs:QName(concat("json:", $key)), jsonquery:process($step/json:region), jsonquery:extractOptions($step, "geo"), $weight)
else if(exists($key))
- then cts:element-geospatial-query(xs:QName($key), jsonquery:processFulltextStep($step/region), jsonquery:extractOptions($step, "geo"), $weight)
+ then cts:element-geospatial-query(xs:QName(concat("json:", $key)), jsonquery:process($step/json:region), jsonquery:extractOptions($step, "geo"), $weight)
else ()
};
-declare private function jsonquery:handleFulltextGeoPoint(
+declare private function jsonquery:handleGeoPoint(
$step as element()
) as cts:point?
{
- if(exists($step/latitude) and exists($step/longitude))
- then cts:point($step/latitude, $step/longitude)
+ if(exists($step/json:latitude) and exists($step/json:longitude))
+ then cts:point($step/json:latitude, $step/json:longitude)
else ()
};
-declare private function jsonquery:handleFulltextGeoCircle(
- $step as element(circle)
+declare private function jsonquery:handleGeoCircle(
+ $step as element(json:circle)
) as cts:circle?
{
- if(exists($step/radius) and exists($step/latitude) and exists($step/longitude))
- then cts:circle($step/radius, jsonquery:handleFulltextGeoPoint($step))
+ if(exists($step/json:radius) and exists($step/json:latitude) and exists($step/json:longitude))
+ then cts:circle($step/json:radius, jsonquery:handleGeoPoint($step))
else ()
};
-declare private function jsonquery:handleFulltextGeoBox(
- $step as element(box)
+declare private function jsonquery:handleGeoBox(
+ $step as element(json:box)
) as cts:box
{
- if(exists($step/south) and exists($step/west) and exists($step/north) and exists($step/east))
- then cts:box($step/south, $step/west, $step/north, $step/east)
+ if(exists($step/json:south) and exists($step/json:west) and exists($step/json:north) and exists($step/json:east))
+ then cts:box($step/json:south, $step/json:west, $step/json:north, $step/json:east)
else ()
};
-declare private function jsonquery:handleFulltextGeoPolygon(
- $step as element(polygon)
+declare private function jsonquery:handleGeoPolygon(
+ $step as element(json:polygon)
) as cts:polygon
{
cts:polygon(
- for $point in $step/item
- return jsonquery:handleFulltextGeoPoint($point)
+ for $point in $step/json:item
+ return jsonquery:handleGeoPoint($point)
)
};
@@ -348,7 +249,7 @@ declare private function jsonquery:stringOrArrayToSet(
if($item/@type = "string")
then string($item)
else
- for $i in $item/item[@type = "string"]
+ for $i in $item/json:item[@type = "string"]
return string($i)
};
@@ -359,44 +260,44 @@ declare private function jsonquery:extractOptions(
{
if($optionSet = "word")
then (
- if(exists($item/caseSensitive))
+ if(exists($item/json:caseSensitive))
then
- if($item/caseSensitive/@boolean = "true")
+ if($item/json:caseSensitive/@boolean = "true")
then "case-sensitive"
else "case-insensitive"
else ()
,
- if(exists($item/diacriticSensitive))
+ if(exists($item/json:diacriticSensitive))
then
- if($item/diacriticSensitive/@boolean = "true")
+ if($item/json:diacriticSensitive/@boolean = "true")
then "diacritic-sensitive"
else "diacritic-insensitive"
else ()
,
- if(exists($item/punctuationSensitve))
+ if(exists($item/json:punctuationSensitve))
then
- if($item/punctuationSensitve/@boolean = "true")
+ if($item/json:punctuationSensitve/@boolean = "true")
then "punctuation-sensitive"
else "punctuation-insensitive"
else ()
,
- if(exists($item/whitespaceSensitive))
+ if(exists($item/json:whitespaceSensitive))
then
- if($item/whitespaceSensitive/@boolean = "true")
+ if($item/json:whitespaceSensitive/@boolean = "true")
then "whitespace-sensitive"
else "whitespace-insensitive"
else ()
,
- if(exists($item/stemmed))
+ if(exists($item/json:stemmed))
then
- if($item/stemmed/@boolean = "true")
+ if($item/json:stemmed/@boolean = "true")
then "stemmed"
else "unstemmed"
else ()
,
- if(exists($item/wildcarded))
+ if(exists($item/json:wildcarded))
then
- if($item/wildcarded/@boolean = "true")
+ if($item/json:wildcarded/@boolean = "true")
then "wildcarded"
else "unwildcarded"
else ()
@@ -405,92 +306,92 @@ declare private function jsonquery:extractOptions(
,
if($optionSet = ("word", "range"))
then (
- if(exists($item/minimumOccurances[@type = "number"]))
- then concat("min-occurs=", string($item/minimumOccurances[@type = "number"]))
+ if(exists($item/json:minimumOccurances[@type = "number"]))
+ then concat("min-occurs=", string($item/json:minimumOccurances[@type = "number"]))
else ()
,
- if(exists($item/maximumOccurances[@type = "number"]))
- then concat("max-occurs=", string($item/maximumOccurances[@type = "number"]))
+ if(exists($item/json:maximumOccurances[@type = "number"]))
+ then concat("max-occurs=", string($item/json:maximumOccurances[@type = "number"]))
else ()
)
else ()
,
if($optionSet = "search")
then (
- if(exists($item/filtered))
+ if(exists($item/json:filtered))
then
- if($item/filtered/@boolean = "true")
+ if($item/json:filtered/@boolean = "true")
then "filtered"
else "unfiltered"
else ()
,
- if(exists($item/score[@type = "string"]))
- then concat("score-", string($item/score[@type = "string"]))
+ if(exists($item/json:score[@type = "string"]))
+ then concat("score-", string($item/json:score[@type = "string"]))
else ()
)
else ()
,
if($optionSet = "search")
then (
- if(exists($item/coordinateType[@type = "string"]))
+ if(exists($item/json:coordinateType[@type = "string"]))
then
- if($item/coordinateType[@type = "string"] = "long-lat")
+ if($item/json:coordinateType[@type = "string"] = "long-lat")
then "type=long-lat-point"
else "type=point"
else ()
,
- if(exists($item/excludeBoundaries))
+ if(exists($item/json:excludeBoundaries))
then
- if($item/excludeBoundaries/@boolean = "true")
+ if($item/json:excludeBoundaries/@boolean = "true")
then "boundaries-excluded"
else "boundaries-included"
else ()
,
- if(exists($item/excludeLatitudeBoundaries))
+ if(exists($item/json:excludeLatitudeBoundaries))
then
if($item/excludeLatitudeBoundaries/@boolean = "true")
then "boundaries-latitude-excluded"
else ()
else ()
,
- if(exists($item/excludeLongitudeBoundaries))
+ if(exists($item/json:excludeLongitudeBoundaries))
then
- if($item/excludeLongitudeBoundaries/@boolean = "true")
+ if($item/json:excludeLongitudeBoundaries/@boolean = "true")
then "boundaries-longitude-excluded"
else ()
else ()
,
- if(exists($item/excludeSouthBoundaries))
+ if(exists($item/json:excludeSouthBoundaries))
then
- if($item/excludeSouthBoundaries/@boolean = "true")
+ if($item/json:excludeSouthBoundaries/@boolean = "true")
then "boundaries-south-excluded"
else ()
else ()
,
- if(exists($item/excludeWestBoundaries))
+ if(exists($item/json:excludeWestBoundaries))
then
- if($item/excludeWestBoundaries/@boolean = "true")
+ if($item/json:excludeWestBoundaries/@boolean = "true")
then "boundaries-west-excluded"
else ()
else ()
,
- if(exists($item/excludeNorthBoundaries))
+ if(exists($item/json:excludeNorthBoundaries))
then
- if($item/excludeNorthBoundaries/@boolean = "true")
+ if($item/json:excludeNorthBoundaries/@boolean = "true")
then "boundaries-north-excluded"
else ()
else ()
,
- if(exists($item/excludeEastBoundaries))
+ if(exists($item/json:excludeEastBoundaries))
then
- if($item/excludeEastBoundaries/@boolean = "true")
+ if($item/json:excludeEastBoundaries/@boolean = "true")
then "boundaries-east-excluded"
else ()
else ()
,
- if(exists($item/excludeCircleBoundaries))
+ if(exists($item/json:excludeCircleBoundaries))
then
- if($item/excludeCircleBoundaries/@boolean = "true")
+ if($item/json:excludeCircleBoundaries/@boolean = "true")
then "boundaries-circle-excluded"
else ()
else ()
@@ -499,39 +400,26 @@ declare private function jsonquery:extractOptions(
};
declare private function jsonquery:extractWeight(
- $tree as element(json)
+ $tree as element(json:json)
) as xs:double
{
- xs:double(($tree/fulltext/weight[@type = "number"], 1.0)[1])
+ xs:double(($tree/json:weight[@type = "number"], 1.0)[1])
};
-declare private function jsonquery:extractPosition(
- $tree as element(json)
-) as xs:string?
+declare private function jsonquery:extractStartIndex(
+ $tree as element(json:json)
+) as xs:integer
{
- let $position := normalize-space($tree/position)
- let $position :=
- if(empty($tree/position) or $position = "1 to last()")
- then ()
- else $position
- let $validatePosition :=
- if(not(jsonquery:validatePosition($position)))
- then error(xs:QName("JSON-INVALID-POSITION"), concat("Invalid position: '", $position, "'. Positions must be either integers, a range of integers (eg: 1 to 10). In place of an integer a position can also use the function 'last()'."))
- else ()
- return $position
+ if(exists($tree/json:start) and $tree/json:start castable as xs:integer)
+ then xs:integer($tree/json:start)
+ else 1
};
-declare private function jsonquery:validatePosition(
- $position as xs:string?
-) as xs:boolean
+declare private function jsonquery:extractEndIndex(
+ $tree as element(json:json)
+) as xs:integer?
{
- if(empty($position) or $position = "1 to last()" or $position castable as xs:integer)
- then true()
- else if(count(tokenize($position, " to ")) > 2)
- then false()
- else count(
- for $bit in tokenize($position, " to ")
- where not($bit = "last()" or $bit castable as xs:integer)
- return 1
- ) = 0
+ if(exists($tree/json:end) and $tree/json:end castable as xs:integer)
+ then xs:integer($tree/json:end)
+ else ()
};
View
272 data/lib/json.xqy
@@ -14,14 +14,45 @@ See the License for the specific language governing permissions and
limitations under the License.
:)
+(:
+ TODO:
+ Create cts wrappers for the following functions:
+ cts:element-child-geospatial-query
+ cts:element-geospatial-query
+ cts:element-pair-geospatial-query
+ cts:element-query
+ cts:element-range-query
+ cts:element-value-query
+ cts:element-word-query
+ cts:field-word-query
+ cts:word-query
+:)
+
xquery version "1.0-ml";
module namespace json="http://marklogic.com/json";
declare default function namespace "http://www.w3.org/2005/xpath-functions";
+(:
+ Converts a JSON string into an XML document that is highly indexable by
+ MarkLogic. The XML that is generated is intended to be treated like a black
+ box. In other words, it isn't something that you're encouraged to use
+ directly at this time. However, for those that are brave it is fairly
+ resonable to understand.
+
+ $json - A JSON string. This string can contain a number, a string, a
+ boolean value, an array or an object.
+
+ Examples:
+ json:jsonToXML('3.14159')
+ json:jsonToXML('"Hello World')
+ json:jsonToXML('true')
+ json:jsonToXML('[1, 2, 3, 4]')
+ json:jsonToXML('{"foo": "bar"}')
+:)
declare function json:jsonToXML(
$json as xs:string
-) as element(json)
+) as element(json:json)
{
let $tokens := json:tokenize($json)
let $value := json:parseValue($tokens, 1)
@@ -29,7 +60,217 @@ declare function json:jsonToXML(
if(xs:integer($value/@position) != fn:count($tokens) + 1)
then json:outputError($tokens, xs:integer($value/@position), "Unhandled tokens")
else ()
- return <json>{ $value/(@type, @boolean), $value/node() }</json>
+ return <json:json>{ $value/(@type, @boolean), $value/node() }</json:json>
+};
+
+(:
+ Converts an specially formatted XML document into a JSON string. It is HIGHLY
+ important to understand that this function does not accept arbitrary XML.
+ It is designed to accept the XML that is generated by the functions in this
+ module.
+
+ $element - A XML element that has been generated by the functions in this module
+
+ Examples:
+ json:xmlToJSON(json:array((1, 2, 3, 4))) -> "[1, 2, 3, 4]"
+:)
+declare function json:xmlToJSON(
+ $element as element()
+) as xs:string
+{
+ fn:string-join(json:processElement($element), "")
+};
+
+(:
+ Constructs a JSON document for storage in MarkLogic.
+
+ $value - A JSON item (see below for a description of what a JSON item can be).
+
+ Examples:
+ json:document(3.14159)
+ json:document("Hello World")
+ json:document(true())
+ json:document(json:array((1, 2, 3, 4)))
+ json:document(json:object(("foo", "bar")))
+
+
+ A word on JSON items.
+ The various functions in this module that accept JSON items (json:document,
+ json:object and json:array) examine the type of the passed in item and
+ convert it to the appropriate JSON type.
+
+ Here's how the casting works, most are obvious:
+ • XQuery string -> JSON string
+ • XQuery boolean -> JSON boolean
+ • XQuery integer or decimal -> JSON number
+ • Every other XQuery type -> JSON string
+
+ A JSON item may also be the result return value of json:array or json:object.
+:)
+declare function json:document(
+ $value as item()
+) as element(json:json)
+{
+ <json:json>{
+ json:untypedToJSONType($value)/(@*, node())
+ }</json:json>
+};
+
+(:
+ Constructs a JSON object in an XML format for use in json:document,
+ json:array or json:xmlToJSON. The return value is not a string but a JSON
+ item. For more information on JSON items, see the note in json:document.
+
+ There are also a convenience function json:o.
+
+ $keyValues - A sequence of alternating object keys and values. The keys
+ must be strings and the values can be JSON items. Keys must be unique.
+
+ Examples:
+ json:object(("foo", "bar")) -> {"foo": "bar"}
+ json:object(("foo", json:array((1, 2, 3, 4)))) - > {"foo": [1, 2, 3, 4]}
+ json:object(("foo", true(), "bar", false())) -> {"foo": true, "bar": false}
+:)
+declare function json:object(
+ $keyValues as item()*
+) as element(json:item)
+{
+ let $keys := map:map()
+ let $check :=
+ for $i at $pos in $keyValues
+ where $pos mod 2 != 0
+ return (
+ if(not($i castable as xs:string))
+ then error(xs:QName("json:OBJECTKEY"), concat("The object key at location ", ceiling($pos div 2), " isn't a string"))
+ else (),
+ if(map:get($keys, string($i)))
+ then error(xs:QName("json:OBJECTKEY"), concat("The object key at location ", ceiling($pos div 2), " is a duplicate"))
+ else (),
+ map:put($keys, string($i), true())
+ )
+ return
+ <json:item type="object">{
+ for $key at $pos in $keyValues
+ return
+ if($pos mod 2 != 0)
+ then element { xs:QName(concat("json:", json:escapeNCName($key))) } { json:untypedToJSONType($keyValues[$pos + 1])/(@*, node()) }
+ else ()
+ }</json:item>
+};
+
+declare function json:o(
+ $keyValues as item()*
+) as element(json:item)
+{
+ json:object($keyValues)
+};
+
+declare function json:object(
+) as element(json:item)
+{
+ json:object(())
+};
+
+declare function json:o(
+) as element(json:item)
+{
+ json:object(())
+};
+
+(:
+ Constructs a JSON array in an XML format for use in json:document,
+ json:object or json:xmlToJSON. The return value is not a string but a JSON
+ item. For more information on JSON items, see the note in json:document.
+
+ There are also a convenience function json:a.
+
+ $items - A sequence of JSON items to include in the array.
+
+ Examples:
+ json:array((1, 2, 3, 4)) -> [1, 2, 3, 4]
+ json:array((true(), false(), "foo")) -> [true, false, "foo"]
+ json:array((json:object(("foo", "bar")), json:object(("baz", "yaz")))) -> [{"foo": "bar"}, {"baz": "yaz"}]
+:)
+declare function json:array(
+ $items as item()*
+) as element(json:item)
+{
+ <json:item type="array">{
+ for $item in $items
+ return json:untypedToJSONType($item)
+ }</json:item>
+};
+
+declare function json:a(
+ $items as item()*
+) as element(json:item)
+{
+ json:array($items)
+};
+
+declare function json:array(
+) as element(json:item)
+{
+ json:array(())
+};
+
+declare function json:a(
+) as element(json:item)
+{
+ json:array(())
+};
+
+(:
+ Because XQuery doesn't have a stict null value, this function allows us to
+ construct a JSON null. This can be useful if you need objects or arrays
+ with null values.
+
+ Examples:
+ json:array((1, 2, json:null(), 4)) -> [1, 2, null, 4]
+ json:object(("foo", json:null())) -> {"foo": null}
+:)
+declare function json:null(
+) as element(json:item)
+{
+ <json:item type="null"/>
+};
+
+
+(:
+ Private functions
+:)
+declare private function json:untypedToJSONType(
+ $value as item()?
+) as element(json:item)
+{
+ <json:item>{
+ if(exists($value))
+ then
+ if($value instance of element(json:item) or $value instance of element(json:json))
+ then $value/(@*, node())
+ else if($value instance of xs:boolean and $value = true())
+ then attribute boolean { "true" }
+ else if($value instance of xs:boolean and $value = false())
+ then attribute boolean { "false" }
+ else if($value instance of xs:integer or $value instance of xs:decimal)
+ then (attribute type { "number" }, string($value))
+ else if($value instance of xs:string)
+ then (attribute type { "string" }, string($value))
+ else (attribute type { "string" }, xdmp:quote($value))
+ else attribute type { "null" }
+ }</json:item>
+};
+
+declare private function json:processElement(
+ $element as element()
+) as xs:string*
+{
+ if($element/@type = "object") then json:outputObject($element)
+ else if($element/@type = "array") then json:outputArray($element)
+ else if($element/@type = "null") then "null"
+ else if(fn:exists($element/@boolean)) then xs:string($element/@boolean)
+ else if($element/@type = "number") then xs:string($element)
+ else ('"', json:escapeJSONString($element), '"')
};
declare private function json:parseValue(
@@ -88,7 +329,7 @@ declare private function json:parseArray(
let $value := json:parseValue($tokens, $index)
let $set := xdmp:set($finalLocation, xs:integer($value/@position))
let $test := json:shouldBeOneOf($tokens, $finalLocation, ("comma", "rsquare"), "Expected either a comma or closing array")
- return <item>{ $value/(@type, @boolean), $value/node() }</item>
+ return <json:item>{ $value/(@type, @boolean), $value/node() }</json:item>
return <value type="array" position="{ $finalLocation }">{ $items }</value>
};
@@ -128,7 +369,7 @@ declare private function json:parseObject(
let $set := xdmp:set($finalLocation, xs:integer($value/@position))
let $test := json:shouldBeOneOf($tokens, $finalLocation, ("comma", "rbrace"), "Expected either a comma or closing object")
- return element { $key } { $value/(@type, @boolean), $value/node() }
+ return element { xs:QName(concat("json:", $key)) } { $value/(@type, @boolean), $value/node() }
return <value type="object" position="{ $finalLocation }">{ $items }</value>
};
@@ -230,31 +471,12 @@ declare private function json:createToken(
-declare function json:xmlToJSON(
- $element as element()
-) as xs:string
-{
- fn:string-join(json:processElement($element), "")
-};
-
-declare private function json:processElement(
- $element as element()
-) as xs:string*
-{
- if($element/@type = "object") then json:outputObject($element)
- else if($element/@type = "array") then json:outputArray($element)
- else if($element/@type = "null") then "null"
- else if(fn:exists($element/@boolean)) then xs:string($element/@boolean)
- else if($element/@type = "number") then xs:string($element)
- else ('"', json:escapeJSONString($element), '"')
-};
-
declare private function json:outputObject(
$element as element()
) as xs:string*
{
"{",
- for $child at $pos in $element/*
+ for $child at $pos in $element/json:*
return (
if($pos = 1) then () else ",",
'"', json:unescapeNCName(fn:local-name($child)), '":', json:processElement($child)
@@ -267,7 +489,7 @@ declare private function json:outputArray(
) as xs:string*
{
"[",
- for $child at $pos in $element/*
+ for $child at $pos in $element/json:item
return (
if($pos = 1) then () else ",",
json:processElement($child)
View
113 data/lib/manage.xqy
@@ -0,0 +1,113 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+module namespace manage="http://marklogic.com/mljson/manage";
+
+import module namespace json="http://marklogic.com/json" at "json.xqy";
+import module namespace prop="http://xqdev.com/prop" at "properties.xqy";
+import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
+
+declare namespace db="http://marklogic.com/xdmp/database";
+declare default function namespace "http://www.w3.org/2005/xpath-functions";
+
+
+declare function manage:fieldDefinitionToJsonXml(
+ $field as element(db:field)
+) as element(json:item)
+{
+ json:object((
+ "name", string($field/db:field-name),
+ "includedKeys", json:array(
+ for $include in $field/db:included-elements/db:included-element
+ for $key in tokenize(string($include/db:localname), " ")
+ return json:unescapeNCName($key)
+ ),
+ "excludedKeys", json:array(
+ for $include in $field/db:excluded-elements/db:exclude-element
+ for $key in tokenize(string($include/db:localname), " ")
+ return json:unescapeNCName($key)
+ )
+ ))
+};
+
+declare function manage:rangeDefinitionToJsonXml(
+ $index as element(db:range-element-index),
+ $name as xs:string,
+ $operator as xs:string
+) as element(json:item)
+{
+ json:object((
+ "name", $name,
+ "key", json:unescapeNCName(string($index/*:localname)),
+ "type", string($index/*:scalar-type),
+ "operator", $operator
+ ))
+};
+
+declare function manage:jsonTypeToSchemaType(
+ $type as xs:string?
+) as xs:string?
+{
+ if(empty($type))
+ then ()
+ else if($type = "string")
+ then "string"
+ else if($type = "date")
+ then "dateTime"
+ else "decimal"
+};
+
+declare function manage:getRangeIndexProperties(
+) as xs:string*
+{
+ for $key in prop:all()
+ let $value := prop:get($key)
+ where starts-with($key, "index-") and starts-with($value, "range/")
+ return $value
+};
+
+declare function manage:getPropertiesAssociatedWithRangeIndex(
+ $index as element(db:range-element-index)
+) as xs:string*
+{
+ for $value in manage:getRangeIndexProperties()
+ let $bits := tokenize($value, "/")
+ let $key := $bits[3]
+ let $type := $bits[4]
+ where $index/*:scalar-type = manage:jsonTypeToSchemaType($type) and $index/*:namespace-uri = "http://marklogic.com/json" and $index/*:localname = $key
+ return $value
+};
+
+declare function manage:getRangeDefinitions(
+) as element(json:item)*
+{
+ let $config := admin:get-configuration()
+ let $existingIndexes := admin:database-get-range-element-indexes($config, xdmp:database())
+
+ for $value in manage:getRangeIndexProperties()
+ let $bits := tokenize($value, "/")
+ let $name := $bits[2]
+ let $key := $bits[3]
+ let $type := $bits[4]
+ let $operator := $bits[5]
+ let $index :=
+ for $index in $existingIndexes
+ where $index/*:scalar-type = manage:jsonTypeToSchemaType($type) and $index/*:namespace-uri = "http://marklogic.com/json" and $index/*:localname = $key
+ return $index
+ return manage:rangeDefinitionToJsonXml($index, $name, $operator)
+};
View
187 data/lib/properties.xqy
@@ -0,0 +1,187 @@
+(:~
+ :
+ : Copyright 2011 Ryan Grimm
+ :
+ : Many times, an application needs to store some persistant configuration
+ : properties. Currently, the only way to really do this using MarkLogic is to
+ : create a document in the database that has this information. While this
+ : practice works fine, the overhead of fetching the document from the database on
+ : a frequent basis seems unnecessary.
+ :
+ : This module is essentially a hack to get around this limitation and provide
+ : similar functionality that Java gives with property sheets. MarkLogic has the
+ : ability to configure predefined namespaces in the server itself. We can use
+ : these namespaces as key value pairs.
+ :
+ : Note: Properties are visible to the entire application group inside of
+ : MarkLogic. That means that a property that is set in one app server will be
+ : visible to all the app servers in the same group.
+ :
+ :
+ :
+ : Licensed under the Apache License, Version 2.0 (the "License");
+ : you may not use this file except in compliance with the License.
+ : You may obtain a copy of the License at
+ :
+ : http://www.apache.org/licenses/LICENSE-2.0
+ :
+ : Unless required by applicable law or agreed to in writing, software
+ : distributed under the License is distributed on an "AS IS" BASIS,
+ : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ : See the License for the specific language governing permissions and
+ : limitations under the License.
+ :
+ : @author Ryan Grimm (grimm@xqdev.com)
+ : @version 0.2
+ :
+ :)
+
+xquery version "1.0-ml";
+
+module namespace prop="http://xqdev.com/prop";
+import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
+
+declare default function namespace "http://www.w3.org/2005/xpath-functions";
+
+(:~
+ : Sets a property.
+ :
+ : Note: Properties are visible to the entire application group inside of
+ : MarkLogic. That means that a property that is set in one app server will be
+ : visible to all the app servers in the same group.
+ :
+ : $key - The property key. Must be unique.
+ : $value - The property value can be of any simple type:
+ : xs:string, xs:boolean, xs:decimal, xs:float, xs:double, xs:duration,
+ : xs:dateTime, xs:time, xs:date, xs:gYearMonth, xs:gYear, xs:gMonthDay, xs:gDay
+ : xs:gMonth, xs:hexBinary, xs:base64Binary, xs:QName, xs:integer,
+ : xs:nonPositiveInteger, xs:negativeInteger, xs:long, xs:int, xs:short, xs:byte,
+ : xs:nonNegativeInteger, xs:unsignedLong, xs:unsignedInt, xs:unsignedShort,
+ : xs:unsignedByte, xs:positiveInteger
+ :
+ :)
+declare function prop:set(
+ $key as xs:string,
+ $value as xs:anySimpleType
+) as empty-sequence()
+{
+ let $config := admin:get-configuration()
+ let $group := xdmp:group()
+ let $existing := admin:group-get-namespaces($config, $group)[*:prefix = $key]
+ let $test :=
+ if(exists($existing))
+ then error("PROP:REDEFINE-PROPERTY", concat("A property with the key ", $key, " already exists"))
+ else ()
+
+ let $type :=
+ if($value instance of xs:string) then "string"
+ else if($value instance of xs:boolean) then "boolean"
+ else if($value instance of xs:decimal) then "decimal"
+ else if($value instance of xs:float) then "float"
+ else if($value instance of xs:double) then "double"
+ else if($value instance of xs:duration) then "duration"
+ else if($value instance of xs:dateTime) then "dateTime"
+ else if($value instance of xs:time) then "time"
+ else if($value instance of xs:date) then "date"
+ else if($value instance of xs:gYearMonth) then "gYearMonth"
+ else if($value instance of xs:gYear) then "gYear"
+ else if($value instance of xs:gMonthDay) then "gMonthDay"
+ else if($value instance of xs:gDay) then "gDay"
+ else if($value instance of xs:gMonth) then "gMonth"
+ else if($value instance of xs:hexBinary) then "hexBinary"
+ else if($value instance of xs:base64Binary) then "base64Binary"
+ else if($value instance of xs:QName) then "QName"
+ else if($value instance of xs:integer) then "integer"
+ else if($value instance of xs:nonPositiveInteger) then "nonPositiveInteger"
+ else if($value instance of xs:negativeInteger) then "negativeInteger"
+ else if($value instance of xs:long) then "long"
+ else if($value instance of xs:int) then "int"
+ else if($value instance of xs:short) then "short"
+ else if($value instance of xs:byte) then "byte"
+ else if($value instance of xs:nonNegativeInteger) then "nonNegativeInteger"
+ else if($value instance of xs:unsignedLong) then "unsignedLong"
+ else if($value instance of xs:unsignedInt) then "unsignedInt"
+ else if($value instance of xs:unsignedShort) then "unsignedShort"
+ else if($value instance of xs:unsignedByte) then "unsignedByte"
+ else if($value instance of xs:positiveInteger) then "positiveInteger"
+ else "string"
+
+ let $namespace := admin:group-namespace($key, concat("http://xqdev.com/prop/", $type, "/", $value))
+ return admin:save-configuration(admin:group-add-namespace($config, $group, $namespace))
+};
+
+(:~
+ : Deletes a property. If there isn't a property with the supplied $key, no action is performed.
+ :)
+declare function prop:delete(
+ $key as xs:string
+) as empty-sequence()
+{
+ let $config := admin:get-configuration()
+ let $group := xdmp:group()
+ let $namespace := admin:group-get-namespaces($config, $group)[*:prefix = $key]
+ where exists($namespace)
+ return admin:save-configuration(admin:group-delete-namespace($config, $group, $namespace))
+};
+
+
+(:~
+ : Returns the value of the property for $key. The return type is cast as the
+ : same type when the property was set.
+ :)
+declare function prop:get(
+ $key as xs:string
+) as xs:anySimpleType?
+{
+ try {
+ let $uri := namespace-uri-for-prefix($key, element { concat($key, ":foo") } { () })
+ let $bits := tokenize($uri, "/")
+ let $type := $bits[5]
+ let $value := xdmp:url-decode(string-join($bits[6 to count($bits)], "/"))
+ where starts-with($uri, "http://xqdev.com/prop/")
+ return
+ if($type = "string") then xs:string($value)
+ else if($type = "boolean") then xs:boolean($value)
+ else if($type = "decimal") then xs:decimal($value)
+ else if($type = "float") then xs:float($value)
+ else if($type = "double") then xs:double($value)
+ else if($type = "duration") then xs:duration($value)
+ else if($type = "dateTime") then xs:dateTime($value)
+ else if($type = "time") then xs:time($value)
+ else if($type = "date") then xs:date($value)
+ else if($type = "gYearMonth") then xs:gYearMonth($value)
+ else if($type = "gYear") then xs:gYear($value)
+ else if($type = "gMonthDay") then xs:gMonthDay($value)
+ else if($type = "gDay") then xs:gDay($value)
+ else if($type = "gMonth") then xs:gMonth($value)
+ else if($type = "hexBinary") then xs:hexBinary($value)
+ else if($type = "base64Binary") then xs:base64Binary($value)
+ else if($type = "QName") then xs:QName($value)
+ else if($type = "integer") then xs:integer($value)
+ else if($type = "nonPositiveInteger") then xs:nonPositiveInteger($value)
+ else if($type = "negativeInteger") then xs:negativeInteger($value)
+ else if($type = "long") then xs:long($value)
+ else if($type = "int") then xs:int($value)
+ else if($type = "short") then xs:short($value)
+ else if($type = "byte") then xs:byte($value)
+ else if($type = "nonNegativeInteger") then xs:nonNegativeInteger($value)
+ else if($type = "unsignedLong") then xs:unsignedLong($value)
+ else if($type = "unsignedInt") then xs:unsignedInt($value)
+ else if($type = "unsignedShort") then xs:unsignedShort($value)
+ else if($type = "unsignedByte") then xs:unsignedByte($value)
+ else if($type = "positiveInteger") then xs:positiveInteger($value)
+ else xs:string($value)
+ }
+ catch($e) {
+ ()
+ }
+};
+
+declare function prop:all(
+) as xs:string*
+{
+ let $config := admin:get-configuration()
+ for $ns in admin:group-get-namespaces($config, xdmp:group())
+ where starts-with($ns/*:namespace-uri, "http://xqdev.com/prop/")
+ return string($ns/*:prefix)
+};
View
96 data/lib/reststore.xqy
@@ -20,6 +20,7 @@ module namespace reststore="http://marklogic.com/reststore";
declare default function namespace "http://www.w3.org/2005/xpath-functions";
import module namespace json="http://marklogic.com/json" at "json.xqy";
+import module namespace common="http://marklogic.com/mljson/common" at "common.xqy";
(:
Functions to retrieve request information
@@ -90,28 +91,30 @@ declare function reststore:getDocument(
) as xs:string
{
if(empty(doc($uri)))
- then reststore:error(404, "Document not found")
+ then common:error(404, "Document not found")
else
if($includeContent and not($includeCollections) and not($includeProperties) and not($includePermissions) and not($includeQuality))
then json:xmlToJSON(doc($uri)/*)
- else json:xmlToJSON(<json type="object">{(
- if($includeContent)
- then <content>{ doc($uri)/(@*, *) }</content>
- else (),
- if($includeCollections)
- then reststore:getDocumentCollections($uri)
- else (),
- if($includeProperties)
- then reststore:getDocumentProperties($uri)
- else (),
- if($includePermissions)
- then reststore:getDocumentPermissions($uri)
- else (),
- if($includeQuality)
- then reststore:getDocumentQuality($uri)
- else ()
- )}</json>)
+ else json:xmlToJSON(json:document(
+ json:object((
+ if($includeContent)
+ then ("content", doc($uri)/json:json)
+ else (),
+ if($includeCollections)
+ then ("collections", reststore:getDocumentCollections($uri))
+ else (),
+ if($includeProperties)
+ then ("properties", reststore:getDocumentProperties($uri))
+ else (),
+ if($includePermissions)
+ then ("permissions", reststore:getDocumentPermissions($uri))
+ else (),
+ if($includeQuality)
+ then ("quality", reststore:getDocumentQuality($uri))
+ else ()
+ ))
+ ))
};
declare function reststore:insertDocument(
@@ -139,7 +142,7 @@ declare function reststore:insertDocument(
json:jsonToXML($content)
}
catch ($e) {
- reststore:error(500, "Invalid JSON"),
+ common:error(500, "Invalid JSON"),
xdmp:log($e)
}
return (
@@ -152,11 +155,11 @@ declare function reststore:insertDocument(
declare function reststore:deleteDocument(
$uri as xs:string
-) as empty-sequence()
+) as xs:string?
{
if(exists(doc($uri)))
then xdmp:document-delete($uri)
- else reststore:error(404, "Document not found")
+ else common:error(404, "Document not found")
};
declare function reststore:setProperties(
@@ -204,30 +207,30 @@ declare function reststore:setQuality(
declare private function reststore:getDocumentCollections(
$uri as xs:string
-) as element(collections)
+) as element(json:item)
{
- <collections type="array">{
+ json:array(
for $collection in xdmp:document-get-collections($uri)
- return <item type="string">{ $collection }</item>
- }</collections>
+ return $collection
+ )
};
declare private function reststore:getDocumentProperties(
$uri as xs:string
-) as element(properties)
+) as element(json:item)
{
- <properties type="array">{
+ json:object(
for $property in xdmp:document-properties($uri)/prop:properties/*
where namespace-uri($property) = "http://marklogic.com/reststore"
- return <item type="object">{ element { local-name($property) } { string($property) } }</item>
- }</properties>
+ return (local-name($property), string($property))
+ )
};
declare private function reststore:getDocumentPermissions(
$uri as xs:string
-) as element(permissions)
+) as element(json:item)
{
- <permissions type="array">{
+ json:array(
let $permMap := map:map()
let $populate :=
for $permission in xdmp:document-get-permissions($uri)
@@ -243,34 +246,15 @@ declare private function reststore:getDocumentPermissions(
", (
xs:QName("roleId"), xs:unsignedLong($key)
), <options xmlns="xdmp:eval"><database>{ xdmp:security-database() }</database></options>)
- let $capabilities :=
- for $i in map:get($permMap, $key)
- return <item type="string">{ $i }</item>
- return <item type="object">{ element { $role } {
- attribute type { "array" },
- $capabilities
- } }</item>
- }</permissions>
+ return json:object((
+ $role, json:array(map:get($permMap, $key))
+ ))
+ )
};
declare private function reststore:getDocumentQuality(
$uri as xs:string
-) as element(quality)
-{
- <quality type="number">{ xdmp:document-get-quality($uri) }</quality>
-};
-
-declare private function reststore:error(
- $statusCode as xs:integer,
- $message as xs:string
-) as xs:string
+) as xs:decimal
{
- let $set := xdmp:set-response-code($statusCode, $message)
- let $add := xdmp:add-response-header( "Date", string(current-dateTime()) )
- return <json type="object">
- <error type="object">
- <code type="number">{ $statusCode }</code>
- <message type="string">{ $message }</message>
- </error>
- </json>
+ xdmp:document-get-quality($uri)
};
View
83 data/manage/field.xqy
@@ -0,0 +1,83 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+import module namespace manage="http://marklogic.com/mljson/manage" at "../lib/manage.xqy";
+import module namespace common="http://marklogic.com/mljson/common" at "../lib/common.xqy";
+import module namespace json="http://marklogic.com/json" at "../lib/json.xqy";
+
+import module namespace prop="http://xqdev.com/prop" at "../lib/properties.xqy";
+import module namespace rest="http://marklogic.com/appservices/rest" at "../lib/rest/rest.xqy";
+import module namespace endpoints="http://marklogic.com/mljson/endpoints" at "/config/endpoints.xqy";
+import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
+
+declare option xdmp:mapping "false";
+
+
+let $params := rest:process-request(endpoints:request("/data/jsonstore.xqy"))
+let $name := map:get($params, "name")
+let $requestMethod := xdmp:get-request-method()
+
+let $database := xdmp:database()
+let $config := admin:get-configuration()
+let $existing :=
+ try {
+ admin:database-get-field($config, $database, $name)
+ }
+ catch ($e) {()}
+
+return
+ if($requestMethod = "GET")
+ then
+ if(exists($existing))
+ then json:xmlToJSON(manage:fieldDefinitionToJsonXml($existing))
+ else common:error(404, "Field not found")
+
+ else if($requestMethod = ("PUT", "POST"))
+ then
+ if(exists(prop:get(concat("index-", $name))))
+ then common:error(500, concat("An index, field or alias with the name '", $name, "' already exists"))
+ else (
+ if(exists($existing))
+ then xdmp:set($config, admin:database-delete-field($config, $database, $name))
+ else (),
+
+ let $setProp := prop:set(concat("index-", $name), concat("field/", $name))
+ let $config := admin:database-add-field($config, $database, admin:database-field($name, false()))
+ let $includes := xdmp:get-request-field("include")
+ let $excludes := xdmp:get-request-field("exclude")
+ let $add :=
+ for $include in $includes
+ let $include := json:escapeNCName($include)
+ let $el := admin:database-included-element("http://marklogic.com/json", $include, 1, (), "", "")
+ return xdmp:set($config, admin:database-add-field-included-element($config, $database, $name, $el))
+ let $add :=
+ for $exclude in $excludes
+ let $el := admin:database-excluded-element("http://marklogic.com/json", $exclude)
+ return xdmp:set($config, admin:database-add-field-excluded-element($config, $database, $name, $el))
+ return admin:save-configuration($config)
+ )
+
+ else if($requestMethod = "DELETE")
+ then
+ if(exists($existing))
+ then (
+ admin:save-configuration(admin:database-delete-field($config, $database, $name)),
+ prop:delete(concat("index-", $name))
+ )
+ else common:error(404, "Field not found")
+ else ()
View
94 data/manage/range.xqy
@@ -0,0 +1,94 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+import module namespace manage="http://marklogic.com/mljson/manage" at "../lib/manage.xqy";
+import module namespace common="http://marklogic.com/mljson/common" at "../lib/common.xqy";
+import module namespace json="http://marklogic.com/json" at "../lib/json.xqy";
+
+import module namespace prop="http://xqdev.com/prop" at "../lib/properties.xqy";
+import module namespace rest="http://marklogic.com/appservices/rest" at "../lib/rest/rest.xqy";
+import module namespace endpoints="http://marklogic.com/mljson/endpoints" at "/config/endpoints.xqy";
+import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
+
+declare option xdmp:mapping "false";
+
+
+let $params := rest:process-request(endpoints:request("/data/jsonstore.xqy"))
+let $name := map:get($params, "name")
+let $requestMethod := xdmp:get-request-method()
+
+let $database := xdmp:database()
+let $config := admin:get-configuration()
+
+let $property := prop:get(concat("index-", $name))
+let $property :=
+ if(starts-with($property, concat("range/", $name, "/")))
+ then $property
+ else ()
+
+let $bits := tokenize($property, "/")
+let $key := xdmp:get-request-field("key", $bits[3])
+let $type := xdmp:get-request-field("type", $bits[4])
+let $operator := xdmp:get-request-field("operator", $bits[5])
+let $xsType := manage:jsonTypeToSchemaType($type)
+
+let $existingIndexes := admin:database-get-range-element-indexes($config, $database)
+let $existing :=
+ for $index in $existingIndexes
+ where $index/*:scalar-type = $xsType and $index/*:namespace-uri = "http://marklogic.com/json" and $index/*:localname = $key
+ return $index
+
+return
+ if($requestMethod = "GET")
+ then
+ if(exists($existing))
+ then json:xmlToJSON(manage:rangeDefinitionToJsonXml($existing, $name, $operator))
+ else common:error(404, "Range index not found")
+
+ else if($requestMethod = ("PUT", "POST"))
+ then
+ if(exists(prop:get(concat("index-", $name))))
+ then common:error(500, concat("An index, field or alias with the name '", $name, "' already exists"))
+ else (
+ if(empty($existing))
+ then
+ let $colation :=
+ if($xsType = "string")
+ then "http://marklogic.com/collation/"
+ else ""
+ let $index := admin:database-range-element-index($xsType, "http://marklogic.com/json", $key, $colation, false())
+ let $config := admin:database-add-range-element-index($config, $database, $index)
+ return admin:save-configuration($config)
+ else (),
+
+ prop:set(concat("index-", $name), concat("range/", $name, "/", $key, "/", $type, "/", $operator))
+ )
+
+ else if($requestMethod = "DELETE")
+ then
+ if(exists($existing))
+ then
+ let $propertiesForIndex := manage:getPropertiesAssociatedWithRangeIndex($existing)
+ let $deleteIndex :=
+ if(count($propertiesForIndex) = 1)
+ then admin:save-configuration(admin:database-delete-range-element-index($config, $database, $existing))
+ else ()
+ return prop:delete(concat("index-", $name))
+ else common:error(404, "Range index not found")
+ else ()
+
View
38 test/js/mljson-tests.js
@@ -139,6 +139,44 @@ $(document).ready(function() {
mljson.jsonFromServerTest(mljson.validJSON[i]);
}
+ module("JSON Construction");
+ asyncTest("Array construction", function() {
+ $.ajax({
+ url: "/test/xq/array-construction.xqy",
+ success: function() {
+ ok(true, "Array construction");
+ },
+ error: function() {
+ ok(false, "Array construction");
+ },
+ complete: function() { start(); }
+ });
+ });
+ asyncTest("Object construction", function() {
+ $.ajax({
+ url: "/test/xq/object-construction.xqy",
+ success: function() {
+ ok(true, "Object construction");
+ },
+ error: function() {
+ ok(false, "Object construction");
+ },
+ complete: function() { start(); }
+ });
+ });
+ asyncTest("Object construction duplicate key should fail", function() {
+ $.ajax({
+ url: "/test/xq/object-construction-dup-keys.xqy",
+ success: function() {
+ ok(false, "Object construction duplicate key should fail");
+ },
+ error: function() {
+ ok(true, "Object construction duplicate key should fail");
+ },
+ complete: function() { start(); }
+ });
+ });
+
// Missing REST
// Missing Update Functions
});
View
36 test/xq/array-construction.xqy
@@ -0,0 +1,36 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+import module namespace json = "http://marklogic.com/json" at "/data/lib/json.xqy";
+
+
+let $doc := json:document(
+ json:array((
+ 1,
+ 1.2,
+ true(),
+ false(),
+ json:null(),
+ json:array(),
+ json:object((
+ "foo", "bar"
+ ))
+ ))
+)
+
+return json:xmlToJSON($doc)
View
16 test/xq/isomorphic.xqy
@@ -1,3 +1,19 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
xquery version "1.0-ml";
import module namespace json = "http://marklogic.com/json" at "/data/lib/json.xqy";
View
29 test/xq/object-construction-dup-keys.xqy
@@ -0,0 +1,29 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+import module namespace json = "http://marklogic.com/json" at "/data/lib/json.xqy";
+
+
+let $doc := json:document(
+ json:object((
+ "foo", "bar",
+ "foo", "baz"
+ ))
+)
+
+return json:xmlToJSON($doc)
View
34 test/xq/object-construction.xqy
@@ -0,0 +1,34 @@
+(:
+Copyright 2011 MarkLogic Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+:)
+
+xquery version "1.0-ml";
+
+import module namespace json = "http://marklogic.com/json" at "/data/lib/json.xqy";
+
+
+let $doc := json:document(
+ json:object((
+ "intvalue", 1,
+ "floatvalue", 1.2,
+ "boolvalue", true(),
+ "nullvalue", json:null(),
+ "arrayvalue", json:array((1, 2, 3)),
+ "objectvalue", json:object(("foo", "bar")),
+ "notrailingvalue"
+ ))
+)
+
+return json:xmlToJSON($doc)
Please sign in to comment.
Something went wrong with that request. Please try again.