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

KindCoveredAll%
expression0233 0.0
branch024 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
 (:documentation "application/x-ndjson serializer"
6
                 "see https://github.com/ndjson/ndjson-spec
7
 ")
8
 
9
 
10
 ;;; writing - either boolean or bindings                             
11
 
12
 (defgeneric write-sparql-results-application+x-ndjson (results stream)
13
   (:documentation "Encode the result field to the stream as primitive json objects w/o any correlation,
14
    but without json array syntay-")
15
   
16
   (:method ((result symbol) (stream t))
17
     (format stream "{\"boolean\": ~a}"
18
             (spocq.e:boolean result))
19
     (incf-stat *statements-returned*))
20
 
21
   (:method ((results list-solution-field) (stream t))
22
     (write-sparql-results-application+json (cons (list-solution-field-dimensions results)
23
                                                  (list-solution-field-solutions results))
24
                                            stream))
25
   
26
   (:method ((results cons) (stream t))
27
     (let* ((variables (first results))
28
            (solutions (rest results))
29
            (index 0)
30
            (start (or (response-offset) 0))
31
            (end (response-end)))
32
       (dolist (result solutions)
33
         (when (>= index start)
34
           (when (and end (>= index end))
35
             (return))
36
           (format stream "~:[~;,~% ~] { " (> index start))
37
           (loop for name in variables for value in result
38
                 with first = t
39
                 do (typecase value
40
                      ;; emit non-null values and enable commas
41
                      ((or null spocq:unbound-variable (member etf:nil)))
42
                      (t (format stream "~:[, ~;~]\"~a\": " first name)
43
                         (setf first nil)
44
                         (encode-json-term-compact value stream))))
45
           (format stream " }"))
46
         (incf index))
47
       (format stream "~%")
48
       (incf-stat *statements-returned* index)
49
       index))
50
   
51
   (:method ((results boolean-generator) (stream t))
52
     (let* ((channel (boolean-generator-channel results)))
53
       (format stream "{\"boolean\": ~a}"
54
               (spocq:literal-lexical-form (if (get-field-page channel) spocq.a:|true| spocq.a:|false|)))
55
       (incf-stat *statements-returned*)))
56
 
57
   (:method ((results solution-generator) (stream t))
58
     (let* ((dimensions (solution-generator-dimensions results))
59
            (channel (solution-generator-channel results))
60
            (base-width (length dimensions))
61
            (index 0)
62
            (start (or (response-offset) 0))
63
            (end (response-end)))
64
       (rlmdb::with-string-database (sdb)
65
         (do-pages (page channel)
66
                   (if (and end (>= index end))
67
                       (return)
68
                       (if (>= (+ index (array-dimension page 0)) start)
69
                           (cond ((= base-width (array-dimension page 1))
70
                                  (trace-data write-sparql-results+json dimensions (term-value-field page))
71
                                  (setf index (write-sparql-results-field+ndjson page dimensions stream index start end)))
72
                                 (t
73
                                  (log-warn "field width mismatch: ~s : ~s."
74
                                            dimensions (array-dimension page 1))
75
                                  (incf index (array-dimension page 0))))
76
                           ; otherwise skip the entire page
77
                           (incf index (array-dimension page 0))))))
78
       (incf-stat *statements-returned* index)
79
       index))
80
   
81
 
82
   (:method ((result-field true-matrix-field) (stream t))
83
     (format stream "{\"boolean\": ~a}" (spocq:literal-lexical-form spocq.a:|true|))
84
     (incf-stat *statements-returned*))
85
 
86
   (:method ((result-field false-matrix-field) (stream t))
87
     (format stream "{\"boolean\": ~a}" (spocq:literal-lexical-form spocq.a:|false|))
88
     (incf-stat *statements-returned*))
89
 
90
   (:method ((result-field matrix-field) (stream t))
91
     (flet ((term-aspect-encoder (term-type term-literal term-language-tag term-datatype)
92
              (encode-json-term-aspects term-type term-literal term-language-tag term-datatype stream)))
93
       (declare (dynamic-extent #'term-aspect-encoder))
94
       (let ((term-deconstructor (repository-term-deconstructor *transaction*)))
95
         (let* ((dimensions (solution-field-dimensions result-field))
96
                (base-width (length dimensions))
97
                (start (or (response-offset) 0))
98
                (end (response-end))
99
                (result-count 0)
100
                (first t))
101
           (rlmdb::with-string-database (sdb)
102
             (with-input-fields (result-field)
103
               (let ((%source-data (cffi::null-pointer))
104
                     (source-row 0))
105
                 (setf (values %source-data source-row) (first-field-row result-field))
106
                 (loop until (and end (>= result-count (the fixnum end)))
107
                   until (cffi:null-pointer-p %source-data)
108
                   do (progn
109
                        (trace-matrix "~& write-sparql-results+json.next ~@{~a ~}" :source-row source-row)
110
                        (when (> (incf result-count) start)
111
                          (format stream "~:[,~;~]~:[~;~%~]     " (shiftf first nil) *print-pretty*)
112
                          (write-string " {" stream)
113
                          (loop with first = t
114
                            for term-offset from (* base-width source-row)
115
                            for name in dimensions
116
                            when (distinguished-variable-p name)
117
                            do (let ((term-id (#.+matrix-accessor+ (the sb-sys:system-area-pointer %source-data)
118
                                                                   (the fixnum (* #.(cffi:foreign-type-size +matrix-element-type+) term-offset)))))
119
                                 (unless (= term-id +null-term-id+)
120
                                   (if (shiftf first nil) (write-char #\space stream) (format stream ", "))
121
                                   (format stream "\"~a\": " name)
122
                                   (funcall term-deconstructor #'term-aspect-encoder *transaction* term-id))))
123
                          (write-string " }" stream))
124
                        (setf (values %source-data source-row) (next-field-row result-field)))))))
125
           (incf-stat *statements-returned* (- result-count start))
126
           result-count))))
127
   )
128
 
129
 ;;;
130
 ;;;
131
 
132
 (defmethod send-response-message ((operation t) (message t) (stream t) (content-type mime:application/x-ndjson))
133
   "Given a MESSAGE, and a STREAM with the application/x-ndjson CONTENT-TYPE, encode as json.
134
    This is intended to subsume application/rdf+json as well."
135
   (when *encoding-trace-output*
136
     (setf stream (make-broadcast-stream *encoding-trace-output* stream)))
137
   (let ((*package* *spocq-reader-package*))
138
     (write-sparql-results-application+x-ndjson message stream)))
139
 
140
 #|
141
 (write-sparql-results+x-ndjson '((?::a ?::s ?::z) (1 2 3) (<http://example/1> <http://example/2> <http://example/3>))
142
                            *trace-output*)
143
 |#