Coverage report: /development/source/library/org/datagraph/spocq-shard/src/core/encoding/json-query.lisp

KindCoveredAll%
expression0142 0.0
branch016 0.0
Key
Not instrumented
Conditionalized out
Executed
Not executed
 
Both branches taken
One branch taken
Neither branch taken
1
 ;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.implementation; -*-
2
 
3
 (in-package :org.datagraph.spocq.implementation)
4
 
5
 ;;; json-encoded formulation for queries
6
 ;;; potentially as an alternative to sparql for schema-based prototypes.
7
 ;;; NOT as an encoding for sparql queries
8
 ;;;
9
 ;;; see https://www.semantics3.com/docs/
10
 ;;; http://lists.w3.org/Archives/Public/public-linked-json/2012Jul/0043.html
11
 ;;; jsoniq
12
 ;;;  http://www.jsoniq.org/ : uses '$' to indicate variables
13
 ;;;   flowr model manipulation with stream positioning, aggregation, modifiers, but little pattern support
14
 ;;; mongodb
15
 ;;;  http://community.jaspersoft.com/wiki/jaspersoft-mongodb-query-language
16
 ;;;  http://docs.mongodb.org/manual/tutorial/query-documents/
17
 ;;;  http://docs.mongodb.org/manual/reference/operator/query-modifier/
18
 ;;;   combines a control object, a prototype(FIndFields), possibley with conditional operators (FindQuery),
19
 ;;;   modifiers object and disposition
20
 ;;; marklogic
21
 ;;;  http://docs.marklogic.com/guide/rest-dev/appendixa
22
 ;;;   support "google-style" parsing
23
 ;;;   no prototypes, just a json-structured query declaration
24
 ;;; npm : json-query
25
 ;;;  https://npmjs.org/package/json-query
26
 ;;;   path-based selection
27
 ;;;   supports anonymous functions as constraint values
28
 ;;; coffeecode
29
 ;;;  http://coffeecode.net/egdocs/1.6/draft/html/JSON_Queries.html
30
 ;;;   uses table definitions to translate json into sql
31
 ;;;   carries over the sql idiom "<column> as <alias>"
32
 ;;;   uses the sql standard conditional operator names
33
 ;;; mql (freebase "metaweb query language")
34
 ;;;  http://wiki.freebase.com/wiki/MQL
35
 ;;;  http://wiki.freebase.com/images/e/e0/MQLcheatsheet-081208.pdf
36
 ;;;   uses 'null' and '[]' to indicate variables
37
 ;;;   inflects the field names with appended conditionals to indicate the constraint: "date_of_birth<" : "2000"
38
 ;;;   expresses filters on variables as reappearing, inflected field names
39
 ;;;   whereby, the filter can appear without the binding
40
 ;;;   aggregation, select assignment, and modifiers are not clear
41
 ;;; infosphere jaql
42
 ;;;  http://pic.dhe.ibm.com/infocenter/bigins/v1r1/index.jsp?topic=%2Fcom.ibm.swg.im.infosphere.biginsights.doc%2Fdoc%2Fc0057474.html
43
 ;;;  http://code.google.com/p/jaql/wiki/JaqlOverview
44
 ;;;   provides retrieval and manipulation for an active json object
45
 ;;; spahql
46
 ;;;  http://danski.github.io/spahql/
47
 ;;;   operates on an in-memory object
48
 ;;;   uses xpath syntax to select nodes in the tree for retrieval or modification
49
 ;;; jsonpath (jsonquery claims same capabilities, but more performance)
50
 ;;; jsonquery
51
 ;;;  http://livedocs.dojotoolkit.org/dojox/json/query
52
 ;;;    path-oriented procedural operators to to select nodes in the tree for retrieval or modification
53
 ;;;  20191102: reimplimented as json query
54
 ;;;            https://dojotoolkit.org/reference-guide/1.10/dojox/json/query.html
55
 ;;; ... (others)
56
 ;;;  http://orangevolt.blogspot.de/2012/12/8-ways-to-query-json-structures.html
57
 ;;; json:select (20191102: domain has been retired
58
 ;;;  http://jsonselect.org/#overview
59
 ;;;   uses css patterns to extract data, but although it claims to have queries aganst a data store
60
 ;;;   among its uses cases, there was no example for that.
61
 
62
 
63
 ;;;??? how to express more than a construct
64
 ;;;??? aggregation, grouping, and binding in specific
65
 ;;;??? sub-queries in general
66
 ;;;
67
 
68
 
69
 (defparameter *parse-json.type-predicate* |rdf|:|type|)
70
 
71
 (defun parse-json-query (source &key ((:type-predicate *parse-json.type-predicate*) *parse-json.type-predicate*))
72
   (let* ((json-object (parse-json source))
73
          (sparql-form (rewrite-json-query-to-sparql json-object)))
74
     ;; if the expression is just a bgp, wrap it as a construct
75
     (if (bgp-form-p sparql-form)
76
         `(spocq.a:|construct| ,sparql-form ,sparql-form)
77
         sparql-form)))
78
 
79
 (defgeneric rewrite-json-query-to-sparql (json-object)
80
   (:documentation "convert a jon object/array into a program definition"
81
   ;;!! recognize json {"value" ...} rdf formulations 
82
   ;;!! recognize semantics3 value constrainst
83
   ;;!! recognize explicit filters
84
   ;;!! allow specific variables
85
   ;;!! arrays of atomic values imply the 'in' filter condition
86
                   )
87
   (:method ((value null))
88
     nil)
89
   (:method ((value string))
90
     (if (or (and (> (length value) 7) (string= "http://" value :end2 7))
91
             (and (> (length value) 4) (string= "urn:" value :end2 4)))
92
         (intern-iri value)
93
         value))
94
   (:method ((value number))
95
     value)
96
   (:method ((value symbol))
97
     (or (find-symbol (string value) :spocq.a)
98
         (intern (symbol-name value) :?)  ))
99
 
100
   (:method ((json-object cons))
101
     "Each object should have two levels.
102
     the first is the subject and a nested object.
103
     The second is the a-list of predicate and object pairs"
104
     (flet ((rewrite-name (name)
105
              (if (stringp name)
106
                  (construct-prefixed-name (if (position #\: name) name (concatenate 'string ":" name)))
107
                  (intern (symbol-name name) :?))))
108
       (destructuring-bind ((subject . po-pairs)) json-object
109
         (typecase subject
110
           (string (setf subject (rewrite-json-query-to-sparql subject))
111
                   (assert (iri-p subject) () "rewrite-json-query-to-sparql: invalid subject term: ~s" subject))
112
           (symbol (setf subject (intern (symbol-name subject) :?)))
113
           (t (error "rewrite-json-query-to-sparql: invalid subject term: ~s" subject)))
114
         (let* ((interned-json (loop for (name . value) in po-pairs
115
                                 for qname = (rewrite-name name)
116
                                 for interned-value = (rewrite-json-query-to-sparql value)
117
                                 if (equalp "a" qname)
118
                                 do (setf qname *parse-json.type-predicate*)
119
                                 collect (cons qname interned-value)))
120
                (statement-patterns (loop for (predicate . value) in interned-json
121
                                      append (typecase value
122
                                                (atom `((spocq.a:|triple| ,subject ,predicate ,value)))
123
                                                (cons (let ((value-subject (second (second value))))
124
                                                        (cons `(spocq.a:|triple| ,subject ,predicate ,value-subject)
125
                                                              (rest value))))
126
                                               (t (error "rewrite-json-query-to-sparql: invalid value term: ~s" value))))))
127
           `(spocq.a:|bgp| ,@statement-patterns)))))
128
 
129
   (:method ((json-array vector))
130
     (let ((interned-json (map 'list #'rewrite-json-query-to-sparql json-array)))
131
       interned-json)))
132
 
133
 (defmethod receive-message ((message string) (content-type mime:application/json-query) &rest args)
134
   "Given a STRING with application/sparql-query+json CONTENT-TYPE,
135
  parse the JSON content, transform into a SPARQL expression
136
  Returns an abbreviated property list which specifies just the query sse expression and the original text.
137
 
138
  If the query string fails to parse, signal a message syntax error which captures the string and the position
139
  to which the parse had progressed.
140
  "
141
   (declare (ignore args))
142
   (handler-bind
143
       ((spocq.e::message-syntax-error (lambda (c) c))
144
        (error (lambda (error)
145
                 (spocq.e:message-syntax-error
146
                  :task (make-instance 'query-error-task
147
                          :id *task-id*
148
                          :user-id *user-id*
149
                          :repository-id *repository-id*
150
                          :revision-id *revision-id*)
151
                  :operation error
152
                  :expression message))))
153
     (let ((sse (parse-json-query message)))
154
       (log-info "task ~s~@[/~s~], repository ~s, query[~d]: '~a'"
155
                     *task-id* *user-id* *repository-id* (length message) (substitute #\space #.(code-char #o012) message))
156
       (values (first sse)
157
               (list :query-expression message
158
                     :sse-expression sse)))))
159
 
160
 #+(or)
161
 (
162
 (tokenize-json "[select s p o [where {a : \"http://example.org\"}]]")
163
 
164
 (parse-json-query "[select, s, a, b, d, {s : {a : a, \"http://example.org/b\": b , \"http://example.org/c\": {c: {\"http://example.org/d\": d}}}}]")
165
 
166
 (receive-message "[select, s, a, b, c, d, {s : {\"http://example.org/a\" : a, \"http://example.org/b\": b , \"http://example.org/c\": {c: {\"http://example.org/d\": d}}}}]"
167
                  mime:application/json-query)
168
 
169
 (parse-sparql+json "[select, s, p, o]")
170
 )
171