Coverage report: /development/source/library/org/datagraph/spocq-shard/src/odbc/materialized-view.lisp

KindCoveredAll%
expression6322258 28.0
branch26172 15.1
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
 ;;; 20170220 now in build
5
 ;;; (load #p"patches/sql-truncate-exp.lisp")
6
 ;;; (load #p"patches/materialized-view.lisp")
7
 
8
 
9
 (:documentation "Materialized views based (odbc) sql processing."
10
   "Resource api definitions establish the correspondence between sparql predicates and sql relation attributes.
11
 This controls the translation between BGP expressions and SQL, with support for projection and equality
12
 constraints.
13
 The values are retained as integer term numbers, which avoids externalization/internalization overhead,
14
 but limits comparisons to 'same term'.
15
 Any value-based filters must be placed outside of the BGP.
16
 
17
 The process is enabled by a combination of factors
18
 - repository configuration or a creation request argument which specifies the class as materialized-repository;
19
 - by a BGP which specifies a static or dynamically bound type, single subject and constant predicates;
20
 - by a corresponding table in the SQL database to hold the respective class extent. (default schema: views)
21
 # sudo -u postgres psql "${RAILS_ENV}" 
22
 # create schema views;
23
 # \q
24
 
25
 
26
 When enabled, an api definition plays two roles:
27
 - it controls materialization, whereby the api definition translates into an exhaustive optional query,
28
   which projects the (class x property) extensions to be inserted into the relational cache.
29
   views are not created on-demand and the observed creation rates is limited, as 10^3 rows per second.
30
 - it controls general query interpretation, in which bgp forms are translated into sql queries on the
31
   view table.
32
 
33
 Resource api definitions are computed on-the-fly and retained in a repository-local cache.
34
 
35
 The materialization happens on a per-type basis - with allowance to include more than one class in a
36
 table. It is not feasible to materialize all classes into a single table as the arity is too high:
37
 for the example wikipedia dataset, there are several hundred properties and the full nested optional
38
 materialization query never completes.")
39
 
40
 
41
 (defparameter *materialized-view-db* nil)
42
 
43
 (defvar *bgp-compilation-mode* :rdf
44
   "Specifies the target storage form:
45
   :rdf compiles as (either joined or nested) triple-store scans
46
   :sql compiles as a query against an sql view")
47
 
48
 (defvar *materialized-repository-revision-id* "00000000-0000-0000-0000-111111111111")
49
 
50
 
51
 (defclass materialized-repository (multi-api-repository lmdb-repository)
52
   ((source-repository
53
     :initform nil :initarg :source-repository
54
     :accessor repository-source-repository
55
     :documentation "indicates the source for a materialized view.")
56
    (view-database
57
     :initform nil :initarg :view-database
58
     :accessor repository-view-database
59
     :documentation "Specify the database to hold the materialized view.")
60
    (transaction-class
61
     :initform 'materialized-transaction :allocation :class)
62
    (revision-class 
63
     :initform 'materialized-repository-revision :allocation :class))
64
   (:documentation "A materialized-repository combines a base rdf repository with a collection of
65
    relational tables in which the material representation of individual classes is cached.
66
    The collection is managed according to constant resource type identifiers - in the simple implementation
67
    without provision for inheritance, as specified by corresponding api definitions."))
68
 
69
 (defclass materialized-repository-revision (repository-revision materialized-repository)
70
   ())
71
 
72
 (defmethod initialize-instance :before ((instance materialized-repository-revision)
73
                                         &key repeat-limit repeat-interval window-interval max-revision-ordinal
74
                                         min-revision-ordinal max-revision-record min-revision-record)
75
   ;; just declare them legal even though that have no meaning for this revision
76
   (declare (ignore repeat-limit repeat-interval window-interval max-revision-ordinal
77
                    min-revision-ordinal max-revision-record min-revision-record)))
78
 
79
 (defclass sql-storage (rlmdb:repository)
80
   ((rlmdb.i::transaction-class
81
     :initform 'rlmdb:transaction :allocation :class)
82
    (name
83
     :initarg :name
84
     :reader storage-name)
85
    (view-database
86
     :initarg :view-database
87
     :reader storage-view-database))
88
   (:documentation "Specialize rlmdb:repository in order to includes the meta database.
89
    Provide information about the sql storage"))
90
 (defclass postgres-storage (sql-storage)
91
   ())
92
 (defclass mysql-storage (sql-storage)
93
   ())
94
 
95
 
96
 (defclass internal-materialized-repository (materialized-repository) ;; not rdfcache
97
   ((revision-class
98
     :allocation :class
99
     :initform 'internal-materialized-repository-revision)
100
    (storage-class
101
     :initform 'postgres-storage :allocation :class))
102
   (:documentation "The materialization variation where the relation contains term numbers"))
103
 
104
 (defclass internal-materialized-repository-revision (materialized-repository-revision internal-materialized-repository)
105
   ())
106
 
107
 (defmethod compute-instance-model ((repository internal-materialized-repository) (as t) &rest args)
108
     (declare (ignore args))
109
   `(("@type" . "repository")
110
     ("name" . ,(repository-name repository))
111
     ("location" . ,(resource-uri repository))
112
     ("identifier" . ,(repository-identifier repository))
113
     ("description" . ,(repository-description repository))))
114
 
115
 (defclass view-repository (materialized-repository)
116
   ((view
117
     :initarg :view :initform nil
118
     :reader get-materialized-repository-view :writer (setf materialized-repository-view))
119
    (view-name
120
     :initarg :view-name :initform nil
121
     :accessor materialized-repository-view-name)))
122
 
123
 (defclass view-database-repository (view-repository)
124
   ((view-table-name
125
     :initarg :view-table-name
126
     :reader repository-view-table-name)))
127
 
128
 
129
 (defclass internal-view-repository (view-database-repository internal-materialized-repository)
130
   ((revision-class
131
     :allocation :class
132
     :initform 'internal-view-repository-revision))
133
   (:documentation "extend internal materialized repository to combine it with a specific view.
134
    This adds the dimensions for the sql query - both the projection attributes and the request parameters.
135
    It changes the execution not to compile the query but instead to execute a select directly for
136
    the given arguments."))
137
 
138
 (defmethod repository-is-view ((repository internal-view-repository))
139
   t)
140
 
141
 (defmethod compute-instance-model ((repository internal-view-repository) (as t) &rest args)
142
     (declare (ignore args))
143
   `(("@type" . "repository")
144
     ("name" . ,(repository-name repository))
145
     ("location" . ,(resource-uri repository))
146
     ("identifier" . ,(repository-identifier repository))
147
     ("description" . ,(repository-description repository))))
148
 
149
 (defclass internal-view-repository-revision (internal-materialized-repository-revision internal-view-repository)
150
   ())
151
 
152
 (defclass external-materialized-repository (materialized-repository)
153
   ((revision-class
154
     :allocation :class
155
     :initform 'external-materialized-repository-revision)
156
    (storage-class
157
     :initform 'postgres-storage :allocation :class))
158
   (:documentation "The materialization variation where the relation contains external database values"))
159
 
160
 (defclass external-materialized-repository-revision (materialized-repository-revision external-materialized-repository)
161
   ())
162
 
163
 
164
 (defgeneric view-repository-p (designator)
165
   (:method ((repository repository))
166
     (view-repository-p (repository-id repository)))
167
   (:method ((repository view-repository))
168
     t)
169
   (:method ((object t))
170
     nil)
171
   (:method ((repository-id string))
172
     (let ((id-type (repository-id-type repository-id)))
173
       (when (and id-type (subtypep id-type 'view-repository))
174
         t)))
175
   (:method ((location pathname))
176
     nil))
177
 
178
 (pushnew (list *class.internal-view-repository* (cl-ppcre:create-scanner "[^/]+/[^_]__[^_]+__view"))
179
          *repository-id-type-map*
180
          :key #'first)
181
 
182
 (defun attribute-combinations (seq)
183
   (let ((result ()))
184
     (flet ((note-combination (c)
185
              (unless (loop for known in result
186
                        ;; redundant
187
                        when (null (set-exclusive-or c known))
188
                        return t)
189
                (push (copy-list c) result))))
190
       (declare (dynamic-extent #'note-combination))
191
       (dotimes (i (length seq))
192
         (alexandria:map-combinations #'note-combination seq :length (1+ i))))
193
     result))
194
 ;;; (attribute-combinations '(a s d))
195
 
196
 (defgeneric compute-repository-view-table-name (repository account-name repository-name view-name)
197
   (:method ((repository view-database-repository) account-name repository-name view-name)
198
     (format nil "~a_~a_~a" account-name repository-name view-name)))
199
 
200
 #+(or)
201
 (defmethod initialize-instance :before ((instance view-database-repository) &rest initargs)
202
   ;(print initargs)
203
   )
204
 
205
 ;;; !!! this ignores the source repository which may have been specififed in an admin request
206
 
207
 (defmethod initialize-instance ((instance view-database-repository) &rest initargs
208
                                 &key (id (error "id is required"))
209
                                 (source-repository nil) (view-name nil view-name-s))
210
   (let ((source-repository-name nil)
211
         (source-account nil)
212
         (table-name nil))
213
     (cond ((and source-repository view-name-s)
214
            (multiple-value-bind (account-name repository-name) (parse-repository-id source-repository)
215
              (setf source-repository-name repository-name)
216
              (setf source-account account-name)))
217
           ((and (setf source-repository (rlmdb:get-metadata-property id "source-repository"))
218
                 (setf view-name (rlmdb:get-metadata-property id "view-name")))
219
            (multiple-value-bind (id-account-name id-repository-name) (parse-repository-id source-repository)
220
              (setf source-repository-name id-repository-name)
221
              (setf source-account id-account-name)))
222
           (t
223
            (multiple-value-bind (id-account-name id-repository-name id-view-name)
224
                                 (parse-view-repository-id id)
225
              (unless (and id-account-name id-repository-name id-view-name)
226
                (spocq.e:request-error "Invalid view repository id ~s." id))
227
              (setf source-account id-account-name)
228
              (setf source-repository-name id-repository-name)
229
              (unless view-name-s (setf view-name id-view-name))
230
              (setf source-repository
231
                    (make-repository-id :account-name id-account-name :repository-name id-repository-name)))))
232
     (setf table-name (compute-repository-view-table-name instance
233
                                                          source-account
234
                                                          source-repository-name
235
                                                          view-name))
236
     (apply #'call-next-method instance
237
              :source-repository source-repository
238
              :account (account source-account)
239
              :view-database (spocq::make-postgresql-uri :ID NIL
240
                                                         :USER *postgres-user*
241
                                                         :PASSWORD *postgres-password*
242
                                                         :AUTHORITY *postgres-authority*
243
                                                         :PORT NIL
244
                                                         :DATABASE *MYSQL-DATABASE*
245
                                                         :PARAMETERS `((:TABLE . ,table-name)
246
                                                                       (:SCHEMA . "views")))
247
              :view-name view-name
248
              :view-table-name table-name
249
              initargs)))
250
 
251
 (defmethod repository-revision-write-date ((repository materialized-repository) (revision t))
252
   nil)
253
 
254
 (defgeneric materialized-repository-view (repository)
255
   (:documentation
256
    "Recompute the view and cache on each reference.
257
     Otherwise a change to the view text is not reflected in regenerated
258
     materialized cache content.")
259
   (:method ((repository view-repository))
260
     (or (get-materialized-repository-view repository)
261
         (setf (materialized-repository-view repository)
262
               (compute-materialized-repository-view repository)))))
263
 
264
 (defgeneric compute-materialized-repository-view (repository)
265
   (:method ((repository view-repository))
266
     (let* ((view-sparql (or (repository-view (repository-source-repository repository)
267
                                              (materialized-repository-view-name repository))
268
                             (spocq.e:resource-not-found-error :identifier (repository-id repository))))
269
            (view-parameters (sparql-query-parameters view-sparql))  ;; operates on text
270
            (index-parameters (attribute-combinations view-parameters)))
271
       (unless view-parameters
272
         (spocq.e:request-error "A materialized view requires parameters: ~a:~%~a"
273
                                (repository-id repository) view-sparql))
274
       (let ((view (make-internal-materialized-view :name (materialized-repository-view-name repository)
275
                                        :repository (repository (repository-source-repository repository))
276
                                        :query view-sparql
277
                                        :parameters index-parameters)))
278
         (unless (null (set-difference view-parameters (view-dimensions view)))
279
           (spocq.e:request-error "A materialized view must project all parameters: ~a:~%~a"
280
                                  (repository-id repository) view-sparql))
281
         view))))
282
 
283
 ;;; (compute-materialized-repository-view (gethash "openrdf-sesame/foaf__types__view" *repositories*))
284
 
285
 
286
 (defmethod initialize-repository-storage ((repository-id string) (prototype view-repository)
287
                                           &key source-repository view-name)
288
   (call-next-method)
289
   (let ((account-name nil)
290
         (repository-name nil))
291
     (multiple-value-bind (id-account-name id-repository-name id-view-name)
292
                          (parse-view-repository-id repository-id)
293
       (cond (id-account-name
294
              (setf account-name id-account-name
295
                    repository-name id-repository-name)
296
              (if (and view-name id-view-name)
297
                  (unless (equalp view-name id-view-name)
298
                    (log-warn "initialize-repository-storage: ~s: ~s supersedes ~s"
299
                              repository-id view-name id-view-name))
300
                  (setf view-name id-view-name)))
301
             (t
302
              (multiple-value-setq (account-name repository-name)
303
                (parse-repository-id repository-id)))))
304
     (cond ((and account-name repository-name source-repository view-name)
305
            (rlmdb:put-metadata repository-id
306
                                (list "source-repository" source-repository
307
                                      "view-name" view-name)))
308
           (t
309
            (spocq.e:request-error "Invalid view repository id ~s . ~s" repository-id view-name)))
310
     prototype))
311
 
312
 (defmethod repository-wildcard-term ((repository external-materialized-repository))
313
   nil)
314
 
315
 (defmethod repository-object-term-number ((repository materialized-repository) (object t))
316
   (rlmdb:value-term-number object))
317
 
318
 (defmethod repository-term-number-object ((repository materialized-repository) (id t))
319
   (rlmdb:term-number-value id))
320
 
321
 (defmethod repository-write-date ((repository materialized-repository))
322
   nil)
323
 
324
 (defmethod repository-write-date ((repository internal-materialized-repository))
325
   (let ((timestamp (rlmdb:get-metadata-timestamp repository)))
326
     (when timestamp
327
       (timeline-location-universal-time timestamp))))
328
 
329
 
330
 
331
 (defclass materialized-view (view)
332
   ())
333
 
334
 (defgeneric view-construct-pattern (view)
335
   (:method ((view materialized-view))
336
     (case (view-operation view)
337
       ((spocq.a:|select| spocq.a:|distinct|) nil)
338
       (spocq.a:|construct| (third (view-sse-expression view)))
339
       (t (error "invalid materialized-view: ~a: ~a" (view-name view) (view-operation view))))))
340
 
341
 (defclass external-materialized-view (materialized-view)
342
   ()
343
   (:documentation "An external materialized view mediates view access to an external materialized repository"))
344
 
345
 (defclass internal-materialized-view (materialized-view)
346
   ()
347
   (:documentation "An internal materialized view mediates access to an internal materialized repository"))
348
 
349
 (defmethod initialize-instance ((instance internal-materialized-view)
350
                                 &key (parameters nil) name query)
351
   (unless parameters
352
     (spocq.e:request-error "A materialized view requires parameters: ~a:%~a" name query))
353
   (call-next-method))
354
 
355
 
356
 (defun make-internal-materialized-view (&rest args)
357
   (apply #'make-instance 'internal-materialized-view args))
358
 
359
 (defstruct (view-solution-generator (:include solution-generator))
360
   view)
361
 (defmethod copy-generator :around ((generator view-solution-generator) &key (view (view-solution-generator-view generator))
362
                                    &allow-other-keys)
363
   (apply #'call-next-method generator :view view))
364
 (defmethod copy-generator ((generator view-solution-generator) &rest args)
365
     (apply #'make-view-solution-generator args))
366
  
367
   
368
 
369
 (defclass materialized-transaction (rdfcache-transaction)
370
   ())
371
 
372
 (defclass odbc-api (materialized-resource-api)
373
   ((predicates
374
     :initarg :predicates :initform nil
375
     :reader resource-api-predicates))
376
   (:documentation
377
    "An odbc api specializes a materialized resource api to mediate an odbc relation.
378
  the attribute definitins are drawn from the relation schema metadata and bgps are compiled
379
  for external materializations to convert on the basis of those types."))
380
 
381
 (defmethod initialize-instance ((instance odbc-api) &rest initargs
382
                                 &key input-definitions
383
                                 (predicates (mapcar #'first input-definitions)))
384
   (apply #'call-next-method instance
385
          :predicates predicates
386
          initargs))
387
 
388
 (defun sql-internal-type (external-type &optional constraints)
389
   (cond ((equalp external-type "timestamp") 'spocq:date-time)
390
         ((equalp external-type "date") 'spocq:date-time)
391
         ((equalp external-type "uuid") 'spocq:uuid)
392
         ((equalp external-type "numeric") 'number)
393
         ((equalp external-type "text") 'string)
394
         (t (clsql-sys::compute-lisp-type-from-specified-type external-type constraints))))
395
 
396
 (defgeneric decode-material-value (string internal-type db external-type)
397
   (:method ((null null) internal-type db external-type)
398
     (spocq:make-unbound-variable nil))
399
   (:method ((string string) internal-type db external-type)
400
     (case internal-type
401
       (spocq:uuid (intern-uuid string))
402
       (spocq:date-time (when (and (> (length string) 11(case (char string 10) ((#\space #\t) t) (t nil)))
403
                          (setf (char string 10) #\T))
404
                        (spocq.e:date-time string))
405
       (t (clsql-sys::read-sql-value string internal-type db external-type))))
406
   (:method ((value t) (internal-type t) (db t) (external-type t))
407
     value))
408
          
409
 (defgeneric intern-material-value (transaction term-value)
410
   (:documentation "yield the respective term number")
411
   (:method ((transaction rdfcache-transaction) value)
412
     (rdfcache-object-term-number transaction value)))
413
 
414
 
415
 ;;;
416
 
417
 (defmacro with-materialized-repository-database ((store location &rest args) repository &body body)
418
   (let ((op (gensym "materialized-op-")))
419
     `(flet ((,op (,store ,location)
420
               (declare (ignorable ,store ,location))
421
               ,@body))
422
        (declare (dynamic-extent #',op))
423
        (call-with-materialized-repository-database #',op ,repository ,@args))))
424
 
425
 (defgeneric call-with-materialized-repository-database (function repository &key verbose)
426
   (:method ((function t) (repository materialized-repository) &rest args)
427
     (apply #'call-with-materialized-repository-database function (repository-view-database repository) args))
428
 
429
   (:method ((function t) (database-designator spocq:mysql-uri) &key (verbose *sql-verbose*))
430
     (clsql:with-database (*materialized-view-db*
431
                           (list (spocq:mysql-uri-authority database-designator)
432
                                 (spocq:mysql-uri-database database-designator)
433
                                 (spocq:mysql-uri-user database-designator)
434
                                 (spocq:mysql-uri-password database-designator))
435
                           :if-exists :new ;; :old
436
                           :database-type :mysql
437
                           )
438
         (flet ((sql-operation ()
439
                  (funcall function *materialized-view-db* database-designator)))
440
           (if verbose
441
               (unwind-protect (progn (clsql:start-sql-recording :database *materialized-view-db*)
442
                                 (sql-operation))
443
                 (clsql:stop-sql-recording :database *materialized-view-db*))
444
               (sql-operation)))))
445
 
446
   (:method ((function t) (database-designator spocq:postgresql-uri) &key (verbose *sql-verbose*))
447
     (let ((schema (spocq:postgresql-uri-schema database-designator)))
448
       (clsql:with-database (*materialized-view-db*
449
                             (list (spocq:postgresql-uri-authority database-designator)
450
                                   (spocq:postgresql-uri-database database-designator)
451
                                   (spocq:postgresql-uri-user database-designator)
452
                                   (spocq:postgresql-uri-password database-designator))
453
                             :if-exists :new ;; :old
454
                             :database-type :postgresql-socket
455
                             )
456
         (flet ((sql-operation ()
457
                  (when schema
458
                    (clsql-sys:database-execute-command (format nil "SET search_path TO ~a" schema)
459
                                                        *materialized-view-db*))
460
                  (funcall function *materialized-view-db* database-designator)))
461
           (if verbose
462
               (unwind-protect (progn (clsql:start-sql-recording :database *materialized-view-db*)
463
                                 (sql-operation))
464
                 (clsql:stop-sql-recording :database *materialized-view-db*))
465
               (sql-operation)))))))
466
 
467
 ;;;
468
 #|
469
 (defmethod resolve-repository-revision-id ((repository materialized-repository) &key revision if-does-not-exist)
470
   (declare (ignore if-does-not-exist))
471
   (call-next-method))
472
 
473
   (unless (or (null revision) (string-equal revision "HEAD"))
474
     (error "repository revision not found: ~s ~s" repository revision))
475
   *materialized-repository-revision-id*)
476
 |#
477
 
478
 #+(or) ; these must allow for read/write transaction in order to intern external terms from the external source
479
 (defmethod transaction-open ((transaction materialized-transaction) &rest args)
480
   (declare (ignore args) (dynamic-extent args))
481
   (call-next-method))
482
 #+(or)
483
 (defmethod transaction-close ((transaction materialized-transaction) (disposition t))
484
   (call-next-method))
485
 
486
 
487
 
488
 #+(or)
489
 (defmethod compute-repository-revision ((repository materialized-repository) (revision string) &rest args &key
490
                                         if-does-not-exist
491
                                         ;; always specific a materialized revision, to allow other methods to observe themode
492
                                         (revision-class 'materialized-repository-revision))
493
   "For a materialized repository, specialize the revision instance class to
494
    introduce materialization in the query processing."
495
   (declare (ignore reference if-does-not-exist))
496
   (apply #'call-next-method repository revision
497
          :revision-class revision-class
498
          args))
499
 
500
 #+(or)
501
 (defmethod repository-resource-api :around ((revision t) name)
502
   (print (compute-applicable-methods #'repository-resource-api (list revision name)))
503
   (call-next-method))
504
 
505
 (defmethod repository-resource-api ((revision materialized-repository-revision) name)
506
   (repository-resource-api (repository-revision-reference revision) name))
507
 (defmethod repository-resource-apis ((revision materialized-repository-revision))
508
   (repository-resource-apis (repository-revision-reference revision)))
509
 (defmethod repository-view-database ((revision materialized-repository-revision))
510
   (repository-view-database (repository-revision-reference revision)))
511
 
512
 
513
 (defmethod repository-resource-api ((repository materialized-repository) (name null))
514
   (repository-resource-api repository (repository-name repository)))
515
 
516
 (defmethod repository-resource-api ((repository materialized-repository) (name t))
517
   (or (call-next-method)
518
       (setf (repository-resource-api repository name)
519
             (resource-retrieve-resource-api repository name))))
520
 
521
 (defgeneric repository-database-type (repository)
522
   (:method ((repository materialized-repository))
523
     (repository-database-type (repository-view-database repository)))
524
   (:method ((repository spocq:postgresql-uri))
525
     :postgresql)
526
   (:method ((repository spocq:mysql-uri))
527
     :mysql))
528
 
529
 (defmethod spocq.e:repository-clear-graph ((repository materialized-repository) (graph (eql :all))
530
                                            &key &allow-other-keys)
531
   (clear-materialized-view (repository-view-database repository) (repository-name repository))
532
   (update-materialized-view repository (resource-retrieve-resource-api repository (repository-name repository))))
533
 
534
 
535
 (defmethod spocq.e:repository-clear-graph ((repository internal-materialized-repository) (graph (eql :all))
536
                                            &key &allow-other-keys)
537
   "clearing all graphs in an internal materialized epository causes it to be regenerated.
538
    compute the sql update respective the view query, generate it into a temporary table,
539
    drop any existing table and swap the new table into place."
540
   (let* ((view (materialized-repository-view repository)))
541
     (update-materialized-view repository view)))
542
 
543
 
544
 #+(or) ;; if it is necessary to construct the view in stages, rename and replace it.
545
 (defmethod spocq.e:repository-clear-graph ((repository internal-materialized-repository) (graph (eql :all))
546
                                            &key &allow-other-keys)
547
   "clearing all graphs in an internal materialized epository causes it to be regenerated.
548
    compute the sql update respective the view query, generate it into a temporary table,
549
    drop any existing table and swap the new table into place."
550
   (let* ((view (materialized-repository-view repository))
551
          (view-name (view-name view))
552
          (tmp-view (clone-instance view :name  (concatenate 'string view-name "_tmp"))))
553
     (setf (view-query view) nil
554
           (view-dimensions view) nil)
555
     (let* ((tmp-name (view-name tmp-view))
556
            (table-name (clsql-sys::sql-expression :table view-name))
557
            (tmp-table-name (clsql-sys::sql-expression :table tmp-name))
558
            (db (repository-view-database repository)))
559
       (describe view)
560
       (describe tmp-view)
561
       (update-materialized-view repository tmp-view)
562
       (clsql:with-transaction (:database db)
563
         (when (clsql:table-exists-p table-name :database db :owner :all)
564
           (clsql:drop-table table-name :database db))
565
         (clsql:rename-table tmp-table-name table-name :database db)))))
566
 
567
 (defmethod spocq.e:repository-clear-graph ((repository materialized-repository) (graph t)
568
                                            &key &allow-other-keys)
569
   ;; is a no-op
570
   nil)
571
 
572
 (defmethod clear-materialized-view ((designator spocq:mysql-uri) (table string))
573
   (with-materialized-repository-database (schema-db location) designator
574
     (declare (ignore location))
575
     (let* ((query (format nil "truncate ~a" table)))
576
       (clsql-sys:query query :database schema-db))))
577
 
578
 
579
 (defmethod clear-materialized-view ((designator spocq:postgresql-uri)  (table string))
580
   (with-materialized-repository-database (schema-db location) designator
581
     (declare (ignore location))
582
     (let* ((query (format nil "truncate ~a" table)))
583
       (clsql-sys:query query :database schema-db))))
584
 
585
 
586
 (defgeneric compute-materialization-sparql (api)
587
   (:documentation "Generate a select which enumerates all resources from a graph, respective the api.
588
    Use the required properties as the base and extend that with all optionals.
589
    Project all object variables plus that for the subject.
590
    The projection order is (resource . (ordered by attribute name)), in order that the insert not need to shuffle.")
591
   (:method ((api resource-api))
592
     (let* ((input-defs (resource-api-input-definitions api))
593
            (output-defs (resource-api-output-definitions api))
594
            (full-defs (sort (remove-duplicates (append input-defs output-defs) :test #'equalp :key #'first)
595
                             #'string-lessp
596
                             :key #'second))
597
            (variables (loop for (predicate attribute . nil) in full-defs
598
                         collect  (case predicate
599
                                    (|rdf|:|type| '?::_type)
600
                                    (t (cons-variable attribute)))))
601
            (node-variable (resource-api-resource-variable api))
602
            ;; augment required properties with the type to ensure a left-join base
603
            (requireds (loop for (predicate nil . properties) in full-defs
604
                         for optional = (getf properties :optional nil)
605
                         for variable in variables
606
                         unless optional collect `(spocq.a:|triple| ,node-variable ,predicate ,variable)))
607
            (optionals (loop for (predicate nil . properties) in full-defs
608
                         for optional = (getf properties :optional nil)
609
                         for variable in variables
610
                         when optional collect `(spocq.a:|triple| ,node-variable ,predicate ,variable)))
611
            (read-query `(spocq.a:|bgp| ,@requireds)) )
612
       (assert requireds ()
613
               "API ~a must have a required base to be materialized." api)
614
       (loop for optional in optionals
615
         do (setf read-query `(spocq.a:|leftjoin| ,read-query (spocq.a:|bgp| ,optional))))
616
       (setf read-query `(spocq.a:|select| ,read-query ,(cons node-variable variables)))
617
       read-query)))
618
 
619
 (defgeneric compute-materialized-sparql (api)
620
   (:documentation "Generate a select which enumerates all resources from a relation, respective the api.
621
    Include all properties in a single bgp.
622
    Project all object variables plus that for the subject resource.
623
    The projection order is (resource . (ordered by attribute name)), in order that the insert not need to shuffle.")
624
   (:method ((api resource-api))
625
     (let* ((input-defs (resource-api-input-definitions api))
626
            (output-defs (resource-api-output-definitions api))
627
            (full-defs (sort (remove-duplicates (append input-defs output-defs) :test #'equalp :key #'first)
628
                             #'string-lessp
629
                             :key #'second))
630
            (variables (loop for (predicate attribute . nil) in full-defs
631
                         collect (case predicate
632
                                   (|rdf|:|type| '?::_type)
633
                                   (t (cons-variable attribute)))))
634
            (node-variable (resource-api-resource-variable api))
635
            ;; no type constraint
636
            (requireds (loop for (predicate nil &key optional &allow-other-keys) in full-defs
637
                         for variable in variables
638
                         unless optional collect `(spocq.a:|triple| ,node-variable ,predicate ,variable)))
639
            (optionals (loop for (predicate nil &key optional &allow-other-keys) in full-defs
640
                         for variable in variables
641
                         when optional collect `(spocq.a:|triple| ,node-variable ,predicate ,variable)))
642
            (read-query `(spocq.a:|bgp| ,@(append requireds optionals))))
643
       (setf read-query `(spocq.a:|select| ,read-query ,(cons node-variable variables)))
644
       read-query)))
645
 
646
 
647
 (defgeneric resource-api-attributes (api db)
648
   (:documentation "return sql attribute instances for the api field definitions.")
649
   (:method ((repository repository) (db clsql:database))
650
     (resource-api-attributes (repository-resource-api repository '?::_resourceId) db))
651
   (:method ((api resource-api) (db clsql:database))
652
     (let* ((input-defs (resource-api-input-definitions api))
653
            (full-defs (remove-duplicates (append input-defs (resource-api-output-definitions api))
654
                                             :test #'equalp :key #'first))
655
            (attributes (sort (loop for (nil attribute . nil) in full-defs collect attribute) #'string-lessp)))
656
       (loop for attribute in attributes
657
         collect (list (clsql-sys::sql-expression :attribute (clsql-sys::database-identifier attribute db))
658
                       'integer
659
                       "integer"))))
660
   (:method ((dimensions list) (db clsql:database))
661
     (loop for attribute in dimensions
662
         collect (list (clsql-sys::sql-expression :attribute (clsql-sys::database-identifier attribute db))
663
                       'integer
664
                       "integer"))))
665
 
666
 (defgeneric ensure-materialized-view (store api)
667
   (:documentation "Ensure that the cache database includes a view which represents the extension of the
668
    given resource as a relation. If no table corresponds to the source repository's view, create one.
669
    values : string : the view name")
670
   (:method ((repository materialized-repository) (named-for t))
671
     (ensure-materialized-view repository (repository-resource-api repository named-for)))
672
   (:method ((repository materialized-repository) (api materialized-resource-api))
673
     (let ((view-db-designator (repository-view-database repository))
674
           (view-name (resource-api-view-name api)))
675
       (with-materialized-repository-database (view-db location) view-db-designator
676
         (unless (clsql:table-exists-p view-name :database view-db :owner :all)
677
           (destructuring-bind (table-name attributes) (rest (compute-materialized-view-definition repository api view-db))
678
             (clsql:create-table table-name attributes
679
                                 :database view-db
680
                                 :transactions t)))
681
         view-name)))
682
   (:method ((repository materialized-repository) (view view))
683
     "create the table. create also any indices indicated for possible parameters."
684
     (let* ((view-db-designator (repository-view-database repository))
685
            (table-namestring (qualified-view-name view))
686
            ;;(table-name (clsql-sys::sql-expression :table table-namestring))
687
            (view-parameters (view-parameters view)))
688
       (flet ((compute-index-name (table-name parameter-set)
689
                (format nil "~a_~{_~a~}" table-name (mapcar #'string-downcase parameter-set))))
690
         (with-materialized-repository-database (view-db location) view-db-designator
691
           (destructuring-bind (sql-table-name attributes) (rest (compute-materialized-view-definition repository view view-db))
692
             (unless (when (clsql:table-exists-p sql-table-name :database view-db :owner :all)
693
                       ;; if the attributes and the indeces matche the new definition, keep the table
694
                       ;; otherwise drop the table and its indices
695
                       (cond ((and (every #'string-equal
696
                                          (mapcar #'(lambda (attr-def)
697
                                                      (CLSQL-SYS::UNESCAPED-DATABASE-IDENTIFIER (clsql:sql-name (first attr-def))))
698
                                                  attributes)
699
                                          (clsql:list-attributes sql-table-name :database view-db :owner :all))
700
                                   (let ((indexes (clsql:list-indexes :on sql-table-name :database view-db :owner :all))
701
                                         (parameter-indexes (loop for parameter-set in view-parameters
702
                                                              collect (compute-index-name table-namestring parameter-set))))
703
                                     (null (set-exclusive-or indexes parameter-indexes :test #'string-equal)))))
704
                             (t
705
                              (clsql:drop-table sql-table-name :database view-db)
706
                              nil)))
707
               (log-notice "ensure-materialized-view: create-table: ~s, db: ~s" table-namestring view-db)
708
               (clsql:create-table sql-table-name attributes :database view-db)
709
               ;; if the generation time becomes an issue, this step should be delayed until the table is filled.
710
               ;; likewise, drop and later recreate the index around a regeneration.
711
               ;; see https://stackoverflow.com/questions/12206600/how-to-speed-up-insertion-performance-in-postgresql
712
               (loop for index-dimensions in view-parameters
713
                 for index-attribute-names = (mapcar #'string-downcase index-dimensions)
714
                 for index-name = (compute-index-name table-namestring index-attribute-names)
715
                 do (clsql:create-index index-name :on table-namestring :attributes index-attribute-names
716
                                        :database view-db))))
717
           table-namestring)))))
718
 
719
 (defgeneric probe-materialized-view (store api)
720
   (:documentation "Test whether the database includes a view which represents the extension of the
721
    given resource as a relation. 
722
    values : boolean")
723
   (:method ((repository materialized-repository) (named-for t))
724
     (probe-materialized-view repository (repository-resource-api repository named-for)))
725
   (:method ((repository materialized-repository) (api materialized-resource-api))
726
     (let ((db (repository-view-database repository))
727
           (view-name (resource-api-view-name api)))
728
       (clsql:table-exists-p view-name :database db :owner :all))))
729
 
730
 (defgeneric compute-materialized-view-definition (store api db)
731
   (:documentation "Compute the table expression for a view which represents the extension of the
732
    given resource as a relation.
733
    In use, thiese are not evaluated. Their components are passed as argument to the operator.")
734
   (:method ((repository materialized-repository) (named-for t) (db t))
735
     (compute-materialized-view-definition repository (repository-resource-api repository named-for) db))
736
   (:method ((repository materialized-repository) (api materialized-resource-api) (db CLSQL-SYS:DATABASE))
737
     (let* ((view-name (resource-api-view-name api))
738
            (columns (resource-api-attributes api db)))
739
       `(clsql:create-table ,(clsql-sys::sql-expression :table view-name)
740
                            ,(cons (list (clsql-sys::sql-expression :attribute (clsql-sys::database-identifier "_resourceId" db))
741
                                         'integer
742
                                         "integer")
743
                                   columns))))
744
   (:method ((repository materialized-repository) (view view) (db CLSQL-SYS:DATABASE))
745
     (let* ((table-namestring (string-downcase (qualified-view-name view)))
746
            (view-dimensions  (view-dimensions view))
747
            (columns (resource-api-attributes view-dimensions db)))
748
       `(clsql:create-table ,(clsql-sys::sql-expression :table table-namestring)
749
                            ,columns))))
750
 
751
 
752
 (defgeneric update-materialized-view (database api &key channel-size-limit field-page-length)
753
   (:documentation "Given a repository as a materialized wrapper, update its relation.
754
    Generate and run the respective materialization query, which enumerates the respective class,
755
    Generate the corresponding sql to insert the content into the truncated view table.")
756
   (:method ((repository-id string) (named-for t) &rest args)
757
     (apply #'update-materialized-view (repository repository-id) named-for args))
758
   (:method ((repository internal-materialized-repository) (named-for t) &rest args)
759
     (apply #'update-materialized-view repository (repository-resource-api repository named-for) args))
760
 
761
   (:method ((repository internal-materialized-repository) (api materialized-resource-api) &key
762
             ((:channel-size-limit *channel-size-limit*) 2)
763
             ((:field-page-length *field-page-length*) 32))
764
     "enumerate the api resourece extension from the repository and insert each
765
      solution into the material view"
766
     (let* ((database (repository-view-database repository))
767
            (read-query (compute-materialization-sparql api))
768
            (view (ensure-materialized-view repository api))
769
            (count 0)
770
            (autocommit (clsql-sys:database-autocommit database))
771
            (*bgp-compilation-mode* :rdf))
772
       (clsql:truncate-table view)
773
       (flet ((numeric-solutions (field)
774
                ;;;INSERT INTO your table VALUES (1,2), (5,5), ...;
775
                (let ((values (loop with length = (array-dimension field 0)
776
                                with width = (array-dimension field 1)
777
                                for row from 0 below length
778
                                do (incf count)
779
                                collect (loop for column from 0 below width collect (aref field row column)))))
780
                  (clsql:insert-values :into view :values values :database database))
781
                count))
782
         (declare (dynamic-extent #'numeric-solutions))
783
         (unwind-protect
784
             (progn (clsql:set-autocommit nil :database database)
785
               ;; run the materialization query including all extensions
786
               ;;!!! this will cause problems if it requires non-standard metadata, eg. for entialment
787
               (run-sparql read-query
788
                           :repository-id (repository-id repository)
789
                           :solution-handler #'numeric-solutions
790
                           ;; without this, it materializes the entire repository respective the api projection
791
                           :dynamic-bindings (list '(?::_type) (resource-api-resource api))
792
                           ;; :library-path ? where would that be found ?
793
                           :agent (system-agent))
794
               (clsql:set-autocommit autocommit :database database)))
795
         count)))
796
 
797
   (:method ((repository internal-materialized-repository) (view view) &key
798
             ((:channel-size-limit *channel-size-limit*) 2)
799
             ((:field-page-length *field-page-length*) 512))
800
     "Ensure that the view table exists.
801
      If it does not yet exist, create it.
802
      If it exists, check that the schema agrees."
803
     (let* ((view-db-designator (repository-view-database repository))
804
            (view-query (view-query view))
805
            (sse-expression
806
             (case (view-operation view)
807
               ((spocq.a:|select| spocq.a:|distinct|) (view-sse-expression view))
808
               (spocq.a:|construct| (second (view-sse-expression view)))
809
               (t (error "invalid materialized-view: ~a: ~a" (view-name view) (view-operation view)))))
810
            (view-dimensions (view-dimensions view))
811
            (table-namestring (qualified-view-name view))
812
            (count 0)
813
            (insert-row-count 0)
814
            (insert-row-limit 4096)
815
            (autocommit nil)
816
            (*bgp-compilation-mode* :rdf)
817
            (values ()))
818
       (ensure-materialized-view repository view)
819
       (with-materialized-repository-database (view-db location) view-db-designator
820
         (flet ((numeric-solutions (field)
821
                  (unless (= (length view-dimensions) (array-dimension field 1))
822
                    (error "dimension mismatch: ~a" (list (length view-dimensions) (array-dimension field 1))))
823
                  ;;;INSERT INTO your table VALUES (1,2), (5,5), ...;
824
                  (loop with length = (array-dimension field 0)
825
                    with width = (array-dimension field 1)
826
                    for row from 0 below length
827
                    do (progn
828
                         (incf count)
829
                         (incf insert-row-count)
830
                         (push (loop for column from 0 below width collect (aref field row column)) values)
831
                         (when (>= insert-row-count insert-row-limit)
832
                           (clsql:insert-values :into table-namestring :values values :database view-db)
833
                           (setf values nil)
834
                           (setf insert-row-count 0))))
835
                  (when (zerop (mod count #.(* 32 512)))
836
                    ;;(print count)
837
                    (sb-ext:gc))
838
                  count))
839
           (declare (dynamic-extent #'numeric-solutions))
840
           (log-notice "update-materialized-view: ~s" table-namestring)
841
           (clsql:with-transaction (:database view-db)
842
             (clsql:truncate-table table-namestring :database view-db)
843
             (unwind-protect
844
                 (progn
845
                   (setf autocommit (clsql:set-autocommit nil :database view-db))
846
                   ;;!!! see above re extensions
847
                   (apply #'run-sparql sse-expression
848
                          :request-content view-query
849
                          :repository-id (repository-source-repository repository)
850
                          :solution-handler #'numeric-solutions
851
                          ;; without this, it materializes the entire repository respective the api projection
852
                          :dynamic-bindings ()
853
                          :agent (system-agent) ;; use system agent
854
                          ;; take the dataset freom the environment or from the query text
855
                          (case *dataset-source*
856
                            (:query (view-options view))
857
                            (t
858
                             (list* :dataset-graphs *dataset-graphs* (view-options view)))))
859
                   (when (plusp insert-row-count)
860
                     (clsql:insert-values :into table-namestring :values values :database view-db)))
861
               (clsql:set-autocommit autocommit :database view-db)))
862
           (log-notice "update-materialized-view: complete: ~a x~a" table-namestring count)
863
           (values count
864
                   (view-dimensions view)))))))
865
 
866
 (defmethod compile-query-for-repository ((query query) (repository internal-view-repository))
867
   "Use the query expression dimensions and its request arguments to control the sql retrieval"
868
   (let* ((view (materialized-repository-view repository))
869
          (view-name (view-name view))
870
          (table-namestring (qualified-view-name view))
871
          (arguments (query-dynamic-bindings query))
872
          (query-dimensions (expression-dimensions (query-sse-expression query)))
873
          (view-dimensions (view-dimensions view))
874
          (projection-dimensions (sort (if query-dimensions
875
                                           (intersection view-dimensions query-dimensions)
876
                                           (copy-list view-dimensions))
877
                                       #'string-lessp))
878
          (offset (shiftf *response-offset* 0))  ;; these should apply just when this is the outermost query
879
          (limit (shiftf *response-limit* nil))
880
          (where (let ((constraints (loop for dimension in (first arguments)
881
                                      for dca = (string-downcase dimension)
882
                                      for value in (rest arguments)
883
                                      ;; constraints are between an argument dimension and its value
884
                                      collect (clsql:sql-operation '=
885
                                                                   (sql-attribute dca table-namestring)
886
                                                                   (rdfcache-object-term-number *transaction* value)))))
887
                   ;; (print (list :constraints constraints))
888
                   (if (rest constraints)
889
                       (reduce #'(lambda (c1 c2) (clsql:sql-operation 'and c1 c2)) constraints)
890
                       (first constraints))))
891
          #+(or)
892
          (sql (apply #'clsql:sql-operation 'select
893
                      (append
894
                       ;; output dimension map is orded by dimension
895
                       (loop for attribute in projection-dimensions
896
                         collect (sql-attribute attribute table-namestring))
897
                       (list* :from (clsql:sql-expression :table table-namestring)
898
                              :flatp nil
899
                              :where where
900
                              (append (when limit `(:limit ,limit))
901
                                      (when offset `(:offset ,offset)))))))
902
           (sql `(clsql:sql-operation 'select
903
                                      ,@(loop for attribute in projection-dimensions
904
                                          for dca = (string-downcase attribute)
905
                                          collect `(sql-attribute ',dca ,table-namestring))
906
                                      :from (clsql:sql-expression :table ,table-namestring)
907
                                      :flatp nil
908
                                      :where ,where
909
                                      ,@(when limit `(:limit ,limit))
910
                                      ,@(when offset `(:offset ,offset))))
911
          (view-clause `(spocq.a:|view| (materialized-repository-view (repository ,(repository-id repository)))
912
                                        ,sql ,projection-dimensions))
913
          (pattern (view-construct-pattern view))
914
          (lambda-expression
915
           ;; the initialization function generates just the sql select
916
           ;; as per the view attributes and the query bindings
917
           `(lambda (query)
918
              (with-task-environment (:task query :normal-disposition :continue)
919
                ,(if pattern
920
                     `(spocq.a:|construct| ,view-clause ,pattern)
921
                     view-clause)
922
                ))))
923
                  
924
     (with-task-environment (:task query :normal-disposition :continue)
925
       (with-accounting
926
           (log-debug "compile-query view ~a: ~s (~s)" (task-id query) view-name table-namestring)
927
         (setf (query-patterns query) nil)
928
         (setf (task-operator-count query) 1)
929
         ;; compile the query form and collect the agp instances as a side-effect
930
         (let ((initialization-function 
931
                (spocq-compile lambda-expression)))
932
           (setf (task-initialization-function query) initialization-function)
933
           (generate-accounting-note :compile :task query)
934
           (values initialization-function
935
                   lambda-expression))))))
936
 
937
 
938
 (defmacro spocq.a:|view| (view sql dimensions)
939
   `(spocq.e::view ,view ,sql ',dimensions))
940
 
941
 (defun spocq.e::view (view sql dimensions)
942
   (view-generator view sql dimensions))
943
 
944
 (defun view-generator (view query projection-dimensions &key end start)
945
   (let ((result-channel (make-channel :name (list 'spocq.a:|view| (task-id *query*))
946
                                       :dimensions projection-dimensions
947
                                       :size (effective-channel-size :start start :end end)
948
                                       :page-length (effective-page-length :start start :end end))))
949
     (make-view-solution-generator :operator 'spocq.a:|view|
950
                                   :view view
951
                                   :dimensions projection-dimensions
952
                                   :expression (list #'run-materialized-view-thread result-channel query)
953
                                   :channel result-channel
954
                                   :constituents ())))
955
 
956
 (defun run-materialized-view-thread (destination query)
957
   (let* ((*thread-operations* (cons (list 'spocq.a::|view| (repository-id *repository*)) *thread-operations*)))
958
     (process-materialized-view destination *repository* query))
959
   'spocq.a::|view|)
960
            
961
 
962
 (defgeneric process-materialized-view (destination view-repository sql-query)
963
   (:documentation "Given a destination and a materialized source repository, 
964
    initiate the delegated query, accept the results and emit them to the destination.
965
    DESTINATION : (or channel function)
966
    VIEW-REPOSITORY : internal-materialized-repository
967
    SQL-QUERY : (or string sql-query-object)")
968
 
969
   (:method ((solution-destination array-page-channel) (repository internal-materialized-repository) sql-query)
970
     (let* ((view-db-designator (repository-view-database repository))
971
            (repository-id (repository-id repository))
972
            (revision-id (repository-revision-id *task*))
973
            (transaction *transaction*)
974
            (channel-dimensions (channel-dimensions solution-destination))
975
            (result-page nil)
976
            (result-page-length (channel-page-length solution-destination))
977
            (result-page-width (channel-page-width solution-destination))
978
            (result-index result-page-length)
979
            (result-count 0))
980
       (trace-bgp view.match.start repository-id revision-id transaction channel-dimensions)
981
       (block :view-scan
982
         (labels ((coerce-to-term-number (value)
983
                    (typecase value
984
                      (integer ; a materialized term
985
                       value)
986
                      (float ; a rank
987
                       (rdfcache-object-term-number transaction (round (* value 100))))
988
                      (t ; a text string
989
                       (rdfcache-object-term-number transaction value))))
990
                  (collect-solution (solution)
991
                    (trace-bgp view.collect-solution solution)
992
                    (next-solution-location)
993
                    (locally (declare (type (simple-array fixnum (* *)) result-page)
994
                                      (type fixnum result-index)
995
                                      (type (cons fixnum *) solution)
996
                                      (optimize (SPEED 3) (SAFETY 0)))
997
                      (loop for column-index from 0 below result-page-width
998
                        for value in solution
999
                        for integer-value = (coerce-to-term-number value)
1000
                        do (setf (aref result-page result-index column-index) integer-value))))
1001
                  (next-solution-location ()
1002
                    ;; return a page (possible newly created) and the next free location in that page
1003
                    (incf result-count)
1004
                    (when (>= (incf result-index) result-page-length)
1005
                      (when result-page (put-result result-page))
1006
                      (setf result-page (new-field-page solution-destination result-page-length result-page-width)
1007
                            result-index 0))
1008
                    (values result-page result-index))
1009
                  (complete-solutions ()
1010
                    (trace-bgp view.match.complete-solutions result-count)
1011
                    (incf *match-requests* 1)
1012
                    (incf *match-responses* result-count)
1013
                    (log-debug "view count: ~s: solutions: ~s" repository-id result-count)
1014
                    (when result-page
1015
                      (let ((page-result-count (1+ result-index)))
1016
                        (when (< page-result-count result-page-length)
1017
                          (setf result-page
1018
                                (adjust-page result-page (list page-result-count result-page-width)))))
1019
                      (put-result result-page))
1020
                    (complete-field solution-destination)
1021
                    (incf-stat *solutions-constructed* result-count)
1022
                    (return-from :view-scan result-count))
1023
                  (put-result (page)
1024
                    (trace-bgp view.put solution-destination channel-dimensions page)
1025
                    (put-field-page solution-destination page)
1026
                    (unless (task-active-p *query*)
1027
                      (complete-field solution-destination)
1028
                      (return-from :view-scan result-count))))
1029
           (declare (dynamic-extent #'collect-solution))
1030
           (with-materialized-repository-database (view-db location) view-db-designator
1031
             (clsql-sys::map-query-for-effect #'collect-solution
1032
                                              sql-query
1033
                                              view-db
1034
                                              :auto))
1035
           (complete-solutions))))))
1036
   
1037
 
1038
 
1039
 (defgeneric compute-materialized-extension (database api)
1040
   (:documentation "Given an repository as a materialized wrapper, return the api extension")
1041
   (:method ((repository internal-materialized-repository) (api-name t))
1042
     "Given the api name, lookup the api definitiaon and continue."
1043
     (let ((api (resource-api api-name)))
1044
       (cond (api
1045
              (compute-materialized-extension repository api))
1046
             (t
1047
              (log-warn "unknown api: ~a" api-name)
1048
              nil))))
1049
   (:method ((repository internal-materialized-repository) (api resource-api))
1050
     "enumerate the api resource extension from the repository and insert each
1051
      solution into the maerial view"
1052
     (let* ((read-query (compute-materialization-sparql api)))
1053
       ;;!!! see above re extensions
1054
       (run-sparql read-query
1055
                   :repository-id (repository-id repository)
1056
                   :agent (system-agent)))))
1057
 
1058
 (defun compute-resource-api-definition (repository type-resource-id
1059
                                         &key (class 'materialized-resource-api)
1060
                                         (view-name (if (variable-p type-resource-id)
1061
                                                        (repository-id repository)
1062
                                                        (iri-sql-name type-resource-id)))
1063
                                         (operation <http://stage.dydra.com/pg-view>)
1064
                                         (operation-class <http://www.w3.org/ns/hydra/core#PostgresView>)
1065
                                         (input-class <http://data.dydra.com/argsIn>)
1066
                                         (output-class <http://data.dydra.com/resultOut>)
1067
                                         ((:bgp-compilation-mode *bgp-compilation-mode*) :rdf))
1068
   "Compute an api definition by enumerating the properties associated with nodes of the given type.
1069
  Universal properites are deemed inputs and all others are optional outputs."
1070
   (setf repository (repository repository))
1071
   ;; (print (list :compute-resource-api-definition *bgp-compilation-mode*))
1072
   (let* ((resource-count (caar (run-sparql-internal `(spocq.a:|select|
1073
                                                               (spocq.a:|bgp| (spocq.a:|triple| ?::|node| |rdf|:|type| ,type-resource-id))
1074
                                                               ((?::c (spocq.a:|count| ?::|node| :DISTINCT spocq.a:|distinct|))))
1075
                                                     :repository-id (repository-id repository)
1076
                                                     :agent (system-agent))))
1077
          (counts (run-sparql-internal `(spocq.a:|select|
1078
                                                 (spocq.a:|bgp| (spocq.a:|triple| ?::|node| |rdf|:|type| ,type-resource-id)
1079
                                                          (spocq.a:|triple| ?::|node| ?::p ?::o))
1080
                                                 (:group-by (?::p) ?::p (?::c (spocq.a:|count| ?::|node| :DISTINCT spocq.a:|distinct|))))
1081
                              :repository-id (repository-id repository)
1082
                              :agent (system-agent)))
1083
          (required (loop for (property property-count) in counts
1084
                      when (= property-count resource-count)
1085
                      collect (list property property-count)))
1086
          (optional (loop for (property property-count) in counts
1087
                      when (/= property-count resource-count)
1088
                      collect (list property property-count))))
1089
     (when (plusp resource-count)
1090
       `(,class
1091
         :resource ,type-resource-id
1092
         :view-name ,view-name
1093
         :operation ,operation
1094
         :operation-class ,operation-class
1095
         :input-class ,input-class
1096
         :output-class ,output-class
1097
         :input-definitions ; this without the resource identifier, as the subject has no property
1098
         ,(sort (loop for (property property-count) in required
1099
                 ;; collect (list property (iri-local-part property) :selectivity 1))
1100
                  collect (list property (iri-sql-name property) :selectivity 1))
1101
                #'string-lessp :key #'second)
1102
         :output-definitions
1103
         ,(sort (loop for  (property property-count) in optional
1104
                  collect (list property (iri-sql-name property) :optional t
1105
                                :selectivity (float (/ property-count resource-count))))
1106
                #'string-lessp :key #'second)))))
1107
 
1108
 (defun compute-resource-api (repository resource)
1109
   (let ((definition (compute-resource-api-definition repository resource)))
1110
     (when definition
1111
       (apply #'make-instance definition))))
1112
 
1113
 ;;; (compute-resource-api-definition <http://schema.org/SportsTeam> :repository-id "james/dbpedia-en-short-extract")
1114
 
1115
 ;;; bgp -> view match function generation
1116
 
1117
 (defmethod compute-agp-pattern-lambda ((agp agp) (repository materialized-repository))
1118
   "Inspect BGP forms for a single constant type terms which matches an api definition,
1119
    Where a match occurs, compile the BGP against the API definition, respective the input
1120
    and output variables, as an SQL form.
1121
    If the respective SLQL table exists and contains content, execute the query
1122
    directly against the SQL table."
1123
   (let ((pattern-forms (rest (agp-form agp)))
1124
         (pattern-subject nil)
1125
         (pattern-type nil)
1126
         (api nil))
1127
     (if (and (eq *bgp-compilation-mode* :sql)
1128
              (setf pattern-subject (bgp-pattern-subject pattern-forms))
1129
              (setf pattern-type (bgp-pattern-type pattern-forms))
1130
              (notany #'variable-p (bgp-pattern-predicates pattern-forms))
1131
              ;; allow a dynamic binding exists, which should yield the term number
1132
              ;; as well as the term value in the query bgp pattern
1133
              (setf api (repository-resource-api repository pattern-type))
1134
              (probe-materialized-view repository api))
1135
         (let ((lambda (compute-view-lambda repository pattern-forms
1136
                                                         :api api
1137
                                                         :graph (agp-graph agp)
1138
                                                         :base-dimensions (agp-propagated-base-dimensions agp)
1139
                                                         :projection-dimensions (agp-projection-dimensions agp)
1140
                                                         :dataset-graphs (agp-dataset-graphs agp)
1141
                                                         :variables (if (variable-p (agp-graph agp))
1142
                                                                        (cons (agp-graph agp) (agp-variables agp))
1143
                                                                        (agp-variables agp))
1144
                                                         :dynamic-variables (first (query-dynamic-bindings (agp-query agp))))))
1145
           (when (and *trace-pattern-forms*
1146
                      (log-level-qualifies? :trace))
1147
             (log-trace "agp materialized: patterns: ~a lambda ~a" pattern-forms lambda))
1148
           lambda)
1149
         (call-next-method))))
1150
 
1151
 (defmethod compute-repository-pattern-partitions ((repository internal-materialized-repository) (patterns t))
1152
   (if (eq *bgp-compilation-mode* :sql)
1153
       (list patterns)
1154
       (call-next-method)))
1155
 
1156
 (defmethod compute-match-scan-partitions ((repository internal-materialized-repository) (patterns t) &key graph)
1157
   (declare (ignore graph))
1158
   (if (eq *bgp-compilation-mode* :sql)
1159
       (list patterns)
1160
       (call-next-method)))
1161
 
1162
 (defmethod compute-repository-pattern-partitions ((repository external-materialized-repository) (patterns t))
1163
   (list patterns))
1164
 
1165
 (defmethod compute-match-scan-partitions ((repository external-materialized-repository) (patterns t) &key graph)
1166
   (declare (ignore graph))
1167
   (list patterns))
1168
                                                   
1169
 (defgeneric compute-materialized-view-sql (repository api bgp-pattern-body)
1170
   (:documentation "Given a resource api and a bgp pattern body, generate the sql form which
1171
    would perform the equivalent query against the materialized view as described by the api.
1172
    context : (or materialized-repository resource-api)
1173
    bgp-pattern-body : (list statement-pattern)
1174
    values : sql-operation
1175
 
1176
    it recognizes any predicates which are present in i/o mappings and use those to
1177
    associate constants and dynamic variables with constraints and otherwise variables
1178
    with projection attributes.
1179
    in addition, if the the subject is an unbound variable it is added to the output mapping
1180
    with '#resourceId', while if it is bound sip or dynamically or a constant, it is added to
1181
    the constraints on that attribute.")
1182
    
1183
   (:method ((repository t) (context null) pattern-body)
1184
     (let* ((api (resource-api (bgp-pattern-type pattern-body))))
1185
       (when api
1186
         (compute-materialized-view-sql repository api pattern-body))))
1187
 
1188
   (:method ((repository t) (context internal-materialized-repository) pattern-body)
1189
     (compute-materialized-view-sql repository (repository-resource-api context pattern-body) pattern-body))
1190
 
1191
   (:method ((repository t) (context t) (pattern-body t))
1192
     (let* ((api (resource-api context)))
1193
       (when api
1194
         (compute-materialized-view-sql repository api pattern-body))))
1195
 
1196
   (:method ((repository t) (api resource-api) (pattern-api resource-api))
1197
     (compute-materialized-view-sql repository api (rest (second (compute-materialized-sparql pattern-api)))))
1198
 
1199
   (:method ((repository t) (api resource-api) (pattern-body list))
1200
     (let* ((patterns (remove-if-not #'elementary-bgp-statement-form-p pattern-body))
1201
            (subject (statement-subject (first patterns)))
1202
            (slice (rest (assoc 'spocq.a:|slice| pattern-body)))
1203
            (pattern-bindings (compute-pattern-bindings patterns))
1204
            (filter (second (assoc 'spocq.a:|filter| pattern-body)))
1205
            (view-name (resource-api-view-name api))
1206
            (input-attribute-map
1207
             ;; collect all pattern bindings - both variables and constant terms,
1208
             ;; which are defined as inputs
1209
             (loop for (predicate . object) in pattern-bindings
1210
               for attribute = (resource-api-input-attribute api predicate object)
1211
               when attribute
1212
               collect (cons object attribute)))
1213
            (output-attribute-map
1214
             ;; do the same for otputs, but in this case it makes sense for variables only
1215
             (loop for (predicate . object) in pattern-bindings
1216
               for attribute = (resource-api-output-attribute api predicate object)
1217
               when attribute
1218
               collect (cons object attribute))))
1219
       (if (and (variable-p subject(not (boundp subject)))
1220
           (setf output-attribute-map (acons subject "_resourceId" output-attribute-map))
1221
           (setf input-attribute-map (acons subject "_resourceId" input-attribute-map)))
1222
       ;; (print (list :input-attribute-map input-attribute-map))
1223
       ;; (print (list :output-attribute-map output-attribute-map))
1224
       (let* ((projection-dimensions (expression-variables pattern-body))
1225
              (constraint-map (loop for entry in (append input-attribute-map output-attribute-map)
1226
                                for (object . attribute) = entry
1227
                                unless (and (variable-p object(not (boundp object)))
1228
                                collect entry))
1229
              (projection-map (loop with map = (append input-attribute-map output-attribute-map)
1230
                                for variable in projection-dimensions
1231
                                for entry = (assoc variable map)
1232
                                when entry
1233
                                collect entry)))
1234
         ;; (print (list :constraint-map constraint-map))
1235
         ;; (print (list :projection-map projection-map))
1236
         (let* ((input-variables ())
1237
                ;; construct the actual sql
1238
                (where (let ((constraints (loop for (object . attribute) in constraint-map
1239
                                            ;; constraints are between an attribute and either a constant or a dynamic variable
1240
                                            collect (clsql:sql-operation '=
1241
                                                                         (sql-attribute attribute view-name)
1242
                                                                         (if (variable-p object)
1243
                                                                             ;; capture as a variable, to be encoded in sql as its term number
1244
                                                                             (rest (or (assoc object input-variables)
1245
                                                                                       (first (setf input-variables
1246
                                                                                                    (acons object (sql-variable object)
1247
                                                                                                           input-variables)))))
1248
                                                                             (rdfcache-object-term-number *transaction* object))))))
1249
                         ;; (print (list :constraints constraints))
1250
                         (if (rest constraints)
1251
                             (reduce #'(lambda (c1 c2) (clsql:sql-operation 'and c1 c2)) constraints)
1252
                             (first constraints)))))
1253
           (when filter
1254
             ;; any variable reference in a filter must be replaced with the respective attribute
1255
             (flet ((variable-to-attribute (object)
1256
                      (if (variable-p object)
1257
                          (let* ((predicate (first (rassoc object pattern-bindings)))
1258
                                 (attribute (resource-api-output-attribute api predicate object)))
1259
                            (if attribute
1260
                                (sql-attribute attribute)
1261
                                (sql-variable object)))
1262
                          object)))
1263
               (setf where (clsql:sql-operation :and where (compute-sql-expression (map-tree #'variable-to-attribute filter))))))
1264
           (setf projection-map (sort projection-map #'string-lessp :key #'first))
1265
           (let ((sql (apply #'clsql:sql-operation 'select
1266
                             (append
1267
                              ;; output dimension map is orded by dimension
1268
                              (loop for (nil . attribute) in projection-map
1269
                                collect (sql-attribute attribute view-name))
1270
                              (list* :from (clsql:sql-expression :table view-name)
1271
                                     :flatp nil
1272
                                     :where where
1273
                                     (append (when (first slice) `(:limit ,(first slice)))
1274
                                             (when (second slice) `(:offset ,(second slice)))))))))
1275
             ;;; do not need to cache attribute-dimension relation as this is never part of
1276
             ;;; of the generation for a join
1277
             sql))))))
1278
 
1279
 
1280
 (defmethod compute-view-lambda ((context internal-materialized-repository)
1281
                                              bgp-pattern-body &key
1282
                                                  (api (repository-resource-api context bgp-pattern-body))
1283
                                                  (base-dimensions ())
1284
                                                  (graph nil)
1285
                                                  (dataset-graphs ())
1286
                                                  (variables (if (variable-p graph)
1287
                                                                 (cons graph (expression-variables bgp-pattern-body))
1288
                                                                 (expression-variables bgp-pattern-body)))
1289
                                                  (projection-dimensions variables)
1290
                                                  (dynamic-variables ()))
1291
   (declare (ignore dataset-graphs dynamic-variables))
1292
   (assert (null (assoc 'spocq.a::|equivalents| bgp-pattern-body)) ()
1293
           "equivalents not permitted in matierialized view pattern: ~s" bgp-pattern-body)
1294
   (let* ((query (compute-materialized-view-sql context api bgp-pattern-body))
1295
          (projection-variable-count (length projection-dimensions)))
1296
     ;; (print projection-dimensions)
1297
     ;; (print query)    ;; (setf projection-dimensions (remove '?::_RESOURCEID projection-dimensions))
1298
     `(lambda (solution-destination ,@(when base-dimensions '(source)))
1299
        (assert-argument-types view-match
1300
                               (solution-destination (or channel function))
1301
                               ,@(when base-dimensions '((source (or channel function)))))
1302
        (let* ((repository-id (repository-id *repository*))
1303
               (revision-id (repository-revision-id *task*))
1304
               (transaction *transaction*)
1305
               (*thread-operations* (cons (list 'spocq.a::|view| ',bgp-pattern-body) *thread-operations*))
1306
               (result-page nil)
1307
               (result-page-length (channel-page-length solution-destination))
1308
               (result-index *field-page-length*)
1309
               (result-count 0))
1310
          ;; te revision id is not observed (yet)
1311
          (trace-bgp view.match.start repository-id revision-id transaction)
1312
          (block :view-scan
1313
            (labels ((collect-solution (solution)
1314
                       (trace-bgp view.collect-solution solution)
1315
                       (destructuring-bind ,projection-dimensions solution
1316
                         (next-solution-location)
1317
                         (locally (declare (type (simple-array fixnum (* ,projection-variable-count)) result-page)
1318
                                           (type fixnum result-index)
1319
                                           (optimize ,@*field-optimization*))
1320
                           (setf ,@(loop for var-index from 0
1321
                                     for variable in projection-dimensions
1322
                                     nconc `((aref result-page result-index ,var-index) ,variable)))))
1323
                       )
1324
                     (next-solution-location ()
1325
                       ;; return a page (possible newly created) and the next free location in that page
1326
                       (incf result-count)
1327
                       (when (>= (incf result-index) result-page-length)
1328
                         (when result-page (put-result result-page))
1329
                         (setf result-page (new-field-page solution-destination result-page-length ,projection-variable-count)
1330
                               result-index 0))
1331
                       (values result-page result-index))
1332
                     (complete-solutions ()
1333
                       (trace-bgp view.match.complete-solutions result-count)
1334
                       (incf *match-requests* 1)
1335
                       (incf *match-responses* result-count)
1336
                       (log-debug "view counts: ~s: solutions: ~s" repository-id result-count)
1337
                       (when result-page
1338
                         (let ((page-result-count (1+ result-index)))
1339
                           (when (< page-result-count result-page-length)
1340
                             (setf result-page
1341
                                   (adjust-page result-page (list page-result-count ,projection-variable-count)))))
1342
                         (put-result result-page))
1343
                       (complete-field solution-destination)
1344
                       (incf-stat *solutions-constructed* result-count)
1345
                       (return-from :view-scan result-count))
1346
                     (put-result (page)
1347
                       (trace-bgp bgp.put solution-destination ',projection-dimensions page)
1348
                       (put-field-page solution-destination page)
1349
                       (unless (task-active-p *query*)
1350
                         (complete-field solution-destination)
1351
                         (return-from :view-scan result-count))))
1352
              (declare (dynamic-extent #'collect-solution))
1353
              (clsql-sys::map-query-for-effect #'collect-solution ,query (repository-view-database *repository*) :auto)
1354
              (complete-solutions)
1355
              #+(or)
1356
              (clsql:map-query nil #'collect-solution ,query
1357
                               :database (repository-view-database *repository*)
1358
                               :result-types ',(make-list (length projection-dimensions) :initial-element 'integer))))))))
1359
 
1360
 
1361
 (defmethod compute-materialized-view-sql ((repository external-materialized-repository) (api t) (pattern-body list))
1362
   "The bgp matching operator must include provisions to externalize any filters and to intern the results"
1363
   (let* ((patterns (remove-if-not #'elementary-bgp-statement-form-p pattern-body))
1364
          (subject (statement-subject (first patterns)))
1365
          (slice-offset (second (assoc 'spocq.a:|slice| pattern-body)))
1366
          (slice-count (third (assoc 'spocq.a:|slice| pattern-body)))
1367
          (pattern-bindings (compute-pattern-bindings patterns))
1368
          (filter (second (assoc 'spocq.a:|filter| pattern-body)))
1369
          (view-name (resource-api-view-name api))
1370
          (view-identifier (sql-ident-table view-name))
1371
          #+(or)(database-type (repository-database-type repository))
1372
          (input-attribute-map
1373
           ;; collect all pattern bindings - both variables and constant terms,
1374
           ;; which are defined as inputs
1375
           (loop for (predicate . object) in pattern-bindings
1376
             for attribute = (resource-api-input-attribute api predicate object)
1377
             when attribute
1378
             collect (cons object attribute)))
1379
          (output-attribute-map
1380
             ;; do the same for otputs, but in this case it makes sense for variables only
1381
             (loop for (predicate . object) in pattern-bindings
1382
               for attribute = (resource-api-output-attribute api predicate object)
1383
               when attribute
1384
               collect (cons object attribute)))
1385
          ;; take as the subject attribute the first attribute which is a key, but not the record id,
1386
          ;; with otherwise the id as the default.
1387
          (key (loop for (property attribute . options) in (resource-api-input-definitions api)
1388
                 with id-key = nil
1389
                 when (getf options :is-key)
1390
                 do (if (string-equal "id" attribute)
1391
                        (setf id-key attribute)
1392
                        (return attribute))
1393
                 finally (return id-key)))
1394
          (*sql-environment* (make-sql-environment :as (gentemp (sql-ident-table-name view-identifier)))))
1395
     (when (and (variable-p subject(not (boundp subject)) key)
1396
       (let ((key-attribute key))
1397
         (setf output-attribute-map (acons subject key-attribute output-attribute-map))
1398
         (setf input-attribute-map (acons subject key-attribute input-attribute-map))))
1399
     (loop for (predicate . variable) in pattern-bindings
1400
       for attribute = (when (variable-p variable)
1401
                         (resource-api-output-attribute api predicate variable))
1402
       when attribute
1403
       do (setf (sql-variable-attribute variable *sql-environment*)
1404
                (sql-attribute attribute (sql-ident-table-name view-identifier))))
1405
     ; (print (list :input-attribute-map input-attribute-map))
1406
     ; (print (list :output-attribute-map output-attribute-map))
1407
     (let* ((projection-dimensions (expression-variables pattern-body))
1408
            (constraint-map (loop for entry in (remove-duplicates (append input-attribute-map output-attribute-map)
1409
                                                                  :key #'first)
1410
                              for (object . attribute) = entry
1411
                              unless (and (variable-p object(not (boundp object)))
1412
                              collect entry))
1413
            (projection-map (loop with map = (append input-attribute-map output-attribute-map)
1414
                              for variable in projection-dimensions
1415
                              for entry = (assoc variable map)
1416
                              when entry
1417
                              collect entry)))
1418
       ;; (print (list :constraint-map constraint-map))
1419
       ;; (print (list :projection-map projection-map))
1420
       (labels ((variable-to-attribute (object)
1421
                  (if (variable-p object)
1422
                      (let* ((predicate (first (rassoc object pattern-bindings)))
1423
                             (attribute (resource-api-output-attribute api predicate object)))
1424
                        (if attribute
1425
                            (sql-attribute attribute)
1426
                            (sql-variable object)))
1427
                      object)))
1428
         (when (> (length projection-map) (if (boundp subject) 0 1))
1429
           (let* ((input-variables ())
1430
                  ;; construct the actual sql
1431
                  (where (let ((constraints (loop for (object . attribute) in constraint-map
1432
                                              ;; constraints are between an attribute and either a constant or a dynamic variable
1433
                                              collect (clsql:sql-operation '=
1434
                                                                           (sql-attribute attribute view-name)
1435
                                                                           (if (variable-p object)
1436
                                                                               ;; capture as a variable, to be passed to sql as its object value
1437
                                                                               (rest (or (assoc object input-variables)
1438
                                                                                         (first (setf input-variables
1439
                                                                                                      (acons object (sql-variable object)
1440
                                                                                                             input-variables)))))
1441
                                                                               (compute-sql-expression object)
1442
                                                                               #+(or)
1443
                                                                               (clsql-sys:database-output-sql-as-type
1444
                                                                                (resource-api-input-type api attribute)
1445
                                                                                object
1446
                                                                                (repository-view-database repository)
1447
                                                                                database-type))))))
1448
                           ;; (print (list :constraints constraints))
1449
                           (if (rest constraints)
1450
                               (reduce #'(lambda (c1 c2) (clsql:sql-operation 'and c1 c2)) constraints)
1451
                               (first constraints)))))
1452
             (when filter
1453
               ;; any variable reference in a filter must be replaced with the respective attribute
1454
               (setf where 
1455
                     (if where
1456
                         (clsql:sql-operation :and where (compute-sql-expression (map-tree #'variable-to-attribute filter)))
1457
                         (compute-sql-expression (map-tree #'variable-to-attribute filter)))))
1458
             (setf projection-map (sort projection-map #'string-lessp :key #'first))
1459
             (let ((sql (apply #'clsql:sql-operation 'select
1460
                               (append
1461
                                ;; output dimension map is orded by dimension
1462
                                (loop for (nil . attribute) in projection-map
1463
                                  collect (sql-attribute attribute view-name))
1464
                                (list* :from (clsql:sql-expression :table view-name)
1465
                                       :flatp nil
1466
                                       :where where
1467
                                       (append (when slice-count `(:limit ,slice-count))
1468
                                               (when slice-offset `(:offset ,slice-offset))))))))
1469
               ;;; do not need to cache attribute-dimension relation as this is never part of
1470
               ;;; of the generation for a join
1471
               (values sql patterns input-attribute-map output-attribute-map constraint-map))))))))
1472
 
1473
 
1474
 (defmethod compute-bgp-lambda ((repository external-materialized-repository) pattern &rest args &key &allow-other-keys)
1475
   (apply #'compute-view-lambda repository pattern args))
1476
 
1477
 (defmethod compute-view-lambda ((context external-materialized-repository)
1478
                                 bgp-pattern-body &key
1479
                                 (api (repository-resource-api context bgp-pattern-body))
1480
                                 (base-dimensions ())
1481
                                 (graph nil)
1482
                                 (dataset-graphs ())
1483
                                 (variables (if (variable-p graph)
1484
                                                (cons graph (expression-variables bgp-pattern-body))
1485
                                                (expression-variables bgp-pattern-body)))
1486
                                 (projection-dimensions variables)
1487
                                 (dynamic-variables ())
1488
                                 (environment nil))
1489
   "Compute the pattern lambda for an external materialized view.
1490
  In this case the function must map between internal term identifiers and external term values."
1491
   (declare (ignore environment))
1492
   (declare (ignore dataset-graphs dynamic-variables))
1493
   (assert (null (assoc 'spocq.a::|equivalents| bgp-pattern-body)) ()
1494
           "equivalents not permitted in materialized view pattern: ~s" bgp-pattern-body)
1495
   (labels ((compute-null-field ()
1496
            (log-warn "NULL materialied query: ~s . ~s" context bgp-pattern-body)
1497
            `(lambda (solution-destination ,@(when base-dimensions '(source)))
1498
               ,@(when base-dimensions '((declare (ignore source))))
1499
               (log-debug "null materialized bgp: ~s" (repository-id *repository*))
1500
               (complete-field solution-destination)
1501
               0))
1502
          (compute-predicate-scan ()
1503
            ;; compute a pseudo table scan which actually just emits the predicates defined for the view
1504
            ;; set solution content to accounts for the s-p-o 'p' variable and the
1505
            ;; projection dimensions
1506
            (let* ((predicates (resource-api-predicates api))
1507
                   (variable (third (find-if #'triple-form-p bgp-pattern-body)))
1508
                   (variable-index (position variable projection-dimensions))
1509
                   (slice-offset (second (assoc 'spocq.a:|slice| bgp-pattern-body)))
1510
                   (slice-count (third (assoc 'spocq.a:|slice| bgp-pattern-body))))
1511
              (when slice-offset
1512
                (setf predicates (subseq predicates (min slice-offset (length predicates)))))
1513
              (when slice-count
1514
                (setf predicates (subseq predicates 0 (min slice-count (length predicates)))))
1515
              `(lambda (solution-destination ,@(when base-dimensions '(source)))
1516
                 ,@(when base-dimensions '((declare (ignore source))))
1517
                 (let* ((repository-id (repository-id *repository*))
1518
                        (transaction *transaction*)
1519
                        (*thread-operations* (cons (list 'spocq.a::|view| ',bgp-pattern-body) *thread-operations*))
1520
                        (result-page nil)
1521
                        (result-page-length (channel-page-length solution-destination))
1522
                        (result-page-width ,(length projection-dimensions))
1523
                        (result-index *field-page-length*)
1524
                        (result-count 0))
1525
                   (block :view-scan
1526
                     (labels ((next-solution-location ()
1527
                                ;; return a page (possible newly created) and the next free location in that page
1528
                                (incf result-count)
1529
                                (when (>= (incf result-index) result-page-length)
1530
                                  (when result-page (put-result result-page))
1531
                                  (setf result-page (new-field-page solution-destination result-page-length result-page-width)
1532
                                        result-index 0)))
1533
                              (collect-predicate (predicate)
1534
                                (next-solution-location)
1535
                                (locally (declare (type (simple-array fixnum (* ,(length projection-dimensions))) result-page)
1536
                                                  (type fixnum result-index)
1537
                                                  (optimize ,@*field-optimization*))
1538
                                  (let ((term-number (intern-material-value transaction predicate)))
1539
                                    (loop for index below ,(length projection-dimensions)
1540
                                      do (setf (aref result-page result-index index)
1541
                                               (if (= index ,variable-index) term-number 0))))))
1542
                              (complete-solutions ()
1543
                                (trace-bgp view.match.complete-solutions result-count)
1544
                                (incf *match-requests* 1)
1545
                                (incf *match-responses* result-count)
1546
                                (log-debug "view counts: ~s: solutions: ~s" repository-id result-count)
1547
                                (when result-page
1548
                                  (let ((page-result-count (1+ result-index)))
1549
                                    (when (< page-result-count result-page-length)
1550
                                      (setf result-page
1551
                                            (adjust-page result-page (list page-result-count result-page-width)))))
1552
                                  (put-result result-page))
1553
                                (complete-field solution-destination)
1554
                                (incf-stat *solutions-constructed* result-count)
1555
                                (return-from :view-scan result-count))
1556
                              (put-result (page)
1557
                                (trace-bgp bgp.put solution-destination ',projection-dimensions page)
1558
                                (put-field-page solution-destination page)
1559
                                (unless (task-active-p *query*)
1560
                                  (complete-field solution-destination)
1561
                                  (return-from :view-scan result-count))))
1562
                       (loop for predicate in ',predicates do (collect-predicate predicate))
1563
                       (complete-solutions)))))))
1564
          (compute-sql-map-field (query input-map)
1565
            (let* ((projection-variable-count (length projection-dimensions)))
1566
                  (flet ((variable-attribute (v) (rest (assoc v input-map))))
1567
                    ;; (print projection-dimensions)
1568
                    ;; (print query)    ;; (setf projection-dimensions (remove '?::_RESOURCEID projection-dimensions))
1569
                    `(lambda (solution-destination ,@(when base-dimensions '(source)))
1570
                       (assert-argument-types view-match
1571
                                              (solution-destination (or channel function))
1572
                                              ,@(when base-dimensions '((source (or channel function)))))
1573
                       (let* ((repository-id (repository-id *repository*))
1574
                              (revision-id (repository-revision-id *task*))
1575
                              (transaction *transaction*)
1576
                              (*thread-operations* (cons (list 'spocq.a::|view| ',bgp-pattern-body) *thread-operations*))
1577
                              (result-page nil)
1578
                              (result-page-length (channel-page-length solution-destination))
1579
                              (result-index *field-page-length*)
1580
                              (result-count 0))
1581
                         ;; te revision id is not observed (yet)
1582
                         (trace-bgp view.match.start repository-id revision-id transaction)
1583
                         (with-materialized-repository-database (%db designator) *repository*
1584
                           (block :view-scan
1585
                             (labels ((collect-solution (solution)
1586
                                        ;; internal soution values
1587
                                        (trace-bgp view.collect-solution solution)
1588
                                        (destructuring-bind ,projection-dimensions solution
1589
                                          (next-solution-location)
1590
                                          (locally (declare (type (simple-array fixnum (* ,projection-variable-count)) result-page)
1591
                                                            (type fixnum result-index)
1592
                                                            (optimize ,@*field-optimization*))
1593
                                            (setf ,@(loop for var-index from 0
1594
                                                      for variable in projection-dimensions
1595
                                                      for attribute = (variable-attribute variable)
1596
                                                      for external-type = (resource-api-input-type api attribute)
1597
                                                      for internal-type = (sql-internal-type external-type)
1598
                                                      nconc `((aref result-page result-index ,var-index)
1599
                                                              (if ,variable
1600
                                                                  (intern-material-value transaction
1601
                                                                                         (decode-material-value ,variable ',internal-type %db ',external-type))
1602
                                                                  0)))))))
1603
                                      (next-solution-location ()
1604
                                        ;; return a page (possible newly created) and the next free location in that page
1605
                                        (incf result-count)
1606
                                        (when (>= (incf result-index) result-page-length)
1607
                                          (when result-page (put-result result-page))
1608
                                          (setf result-page (new-field-page solution-destination result-page-length ,projection-variable-count)
1609
                                                result-index 0)))
1610
                                      (complete-solutions ()
1611
                                        (trace-bgp view.match.complete-solutions result-count)
1612
                                        (incf *match-requests* 1)
1613
                                        (incf *match-responses* result-count)
1614
                                        (log-debug "view counts: ~s: solutions: ~s" repository-id result-count)
1615
                                        (when result-page
1616
                                          (let ((page-result-count (1+ result-index)))
1617
                                            (when (< page-result-count result-page-length)
1618
                                              (setf result-page
1619
                                                    (adjust-page result-page (list page-result-count ,projection-variable-count)))))
1620
                                          (put-result result-page))
1621
                                        (complete-field solution-destination)
1622
                                        (incf-stat *solutions-constructed* result-count)
1623
                                        (return-from :view-scan result-count))
1624
                                      (put-result (page)
1625
                                        (trace-bgp bgp.put solution-destination ',projection-dimensions page)
1626
                                        (put-field-page solution-destination page)
1627
                                        (unless (task-active-p *query*)
1628
                                          (complete-field solution-destination)
1629
                                          (return-from :view-scan result-count))))
1630
                               (declare (dynamic-extent #'collect-solution))
1631
                               (clsql-sys::map-query-for-effect #'collect-solution ,query %db :auto)
1632
                               (complete-solutions))))))))))
1633
     (if (and (= (count-if #'triple-form-p bgp-pattern-body) 1)
1634
              (variable-p (third (find-if #'triple-form-p bgp-pattern-body))))
1635
         (compute-predicate-scan)
1636
         (multiple-value-bind (query patterns input-map output-map)
1637
                              (compute-materialized-view-sql context api bgp-pattern-body)
1638
           (declare (ignore patterns output-map))
1639
           (unless (loop for var in projection-dimensions
1640
                     unless (assoc var input-map)
1641
                     return nil
1642
                     finally (return t))
1643
             (spocq.e:request-error "some projection variable is not an attribute: ~s: ~s" projection-dimensions input-map))
1644
         (if query
1645
             (compute-sql-map-field query input-map)
1646
             (compute-null-field))))))
1647
 
1648
 ;;; (compute-view-lambda (repository "operations/transaction_events") '((spocq.a:|triple| ?::s <http://dydra.com/operations#timestamp_start> ?::ts)))
1649
 ;;; (compute-view-lambda (repository "operations/query_events") '((spocq.a:|triple| ?::s <http://dydra.com/operations#timestamp> ?::ts)))
1650
 ;;; (compute-view-lambda (repository "operations/query_events") '((spocq.a:|triple| ?::s ?::p ?::ts)))
1651
 
1652
 (defun compute-sql-slot-name (name)
1653
   (setq name (string-upcase name))
1654
   (let ((name- (substitute #\- #\_ name)))
1655
     (cond ((or (find-symbol name :spocq.i)
1656
                (find-symbol name- :spocq.i)))
1657
           (t
1658
            (warn "unknown slot name: ~s/~s" name name-)
1659
            (intern name- :spocq.i)))))
1660
 
1661
 (defun compute-sql-type-name (name)
1662
   (setq name (string-upcase name))
1663
   (cond ((equalp name "BPCHAR") :VARCHAR)
1664
         ((equalp name "datetime") :timestamp)
1665
         ((equalp name "uuid") :uuid)
1666
         ((equalp name "text") :varchar)
1667
         ((find-symbol name :spocq.i))
1668
         ((find-symbol name :clsql-sys))
1669
         ((find-symbol name :keyword))
1670
         (t
1671
          (log-warn "unknown type name: ~s" name)
1672
          (intern name :spocq.i))))
1673
 
1674
 (defmethod decode-api-declaration ((encoding (eql :odbc)) (declaration cons) resource-name)
1675
   (decode-api-declaration :sql declaration resource-name))
1676
 
1677
 (defmethod decode-api-declaration ((encoding (eql :postgresql)) (declaration cons) resource-name)
1678
   (decode-api-declaration :sql declaration resource-name))
1679
 
1680
 (defmethod decode-api-declaration ((encoding (eql :mysql)) (declaration cons) resource-name)
1681
   (decode-api-declaration :sql declaration resource-name))
1682
 
1683
 (defmethod decode-api-declaration ((encoding (eql :sql)) (declaration cons) resource-name)
1684
   "Translate an odbc schema declaration into API definitions.
1685
  As expressed, the declaration comprises the description for a single class."
1686
   (let ((initargs ())
1687
         (table-name nil))
1688
     (loop for element in declaration
1689
         for (table attname typname attlen is-key) = element
1690
         for property = (merge-and-intern-iri (concatenate 'string "#" attname) :base-iri resource-name)
1691
         for slot-name = (compute-sql-slot-name attname)
1692
         for external-type = (let ((type (compute-sql-type-name typname)))
1693
                               (or (find type '(uuid clsql-sys::int2 clsql-sys::int4 clsql-sys::int8 CLSQL-SYS::TIMESTAMP)
1694
                                         :test #'string-equal)
1695
                                   (if (typep attlen '(integer 1)) (list type attlen) type)))
1696
         for map-entry = (list* property attname :type external-type :slot-name slot-name (when is-key '(:is-key t)))
1697
         if initargs
1698
         do (assert (equalp table table-name) ()
1699
                    "api declaration must comprise a single class: ~s" declaration)
1700
         else do (setf table-name table)
1701
         do (progn
1702
              (push map-entry (getf initargs :input-definitions))
1703
              (push map-entry (getf initargs :output-definitions))))
1704
       (apply #'make-odbc-api
1705
              :resource resource-name
1706
              :operation nil
1707
              :operation-class 'hydra::|ODBCView|
1708
              :input-class nil :output-class nil
1709
              :templates nil
1710
              :view-name table-name
1711
              initargs)))
1712
 
1713
 (defun make-odbc-api (&rest args)
1714
   (apply #'make-instance 'odbc-api args))
1715
 
1716
 
1717
 (defmethod resource-retrieve-resource-api ((repository external-materialized-repository) (name t))
1718
   (resource-retrieve-resource-api repository (iri-local-part name)))
1719
 
1720
 (defmethod resource-retrieve-resource-api ((repository external-materialized-repository) (table string))
1721
   (resource-retrieve-resource-api (repository-view-database repository) table))
1722
 
1723
 
1724
 (defmethod resource-retrieve-resource-api ((designator spocq:mysql-uri) (table string))
1725
   "For a mysql api, extract the table from the resource identifier, retrieve the table schema and decode it."
1726
   (with-materialized-repository-database (schema-db location) designator
1727
     (let* ((schema-name (spocq:mysql-uri-schema location))
1728
            (schema-query (format nil "SELECT table_name,column_name,data_type,character_maximum_length,column_key FROM `INFORMATION_SCHEMA`.`columns` WHERE table_name = '~a' and table_schema = '~a';"
1729
                                  table schema-name))
1730
            (base-iri (if (member table '("accounts" "import_events") :test #'equalp)
1731
                          (merge-and-intern-iri "/operations" :base-iri (site-namespace))
1732
                          (site-namespace)))
1733
            (declaration (clsql-sys:query schema-query :database schema-db)))
1734
       (when (equalp table "accounts")
1735
         (setf declaration (remove-if #'(lambda (def) (member (second def) '("authentication_token" "encrypted_password" "reset_password_token")
1736
                                                              :test #'equalp))
1737
                                      declaration)))
1738
       (if declaration
1739
           (decode-api-declaration :sql declaration base-iri)
1740
           (spocq.e:resource-not-found-error :identifier designator)))))
1741
 
1742
 (defmethod resource-retrieve-resource-api ((designator spocq:postgresql-uri) (table string))
1743
   "For a postgres api, extract the table from the resource identifier, retrieve the table schema and decode it."
1744
   (with-materialized-repository-database (schema-db location) designator
1745
     (let* ((schema-query (format nil "SELECT pg_class.relname,pg_attribute.attname,pg_type.typname,pg_attribute.attlen FROM pg_type,pg_class,pg_attribute WHERE pg_class.oid=pg_attribute.attrelid AND pg_class.relname='~a' AND pg_attribute.atttypid=pg_type.oid AND pg_attribute.attnum > 0;"
1746
                                  table))
1747
            ;; for the type as well: format_type(a.atttypid, a.atttypmod) AS data_type
1748
            ;; for primaries AND    i.indisprimary
1749
            (index-query (format nil "SELECT a.attname FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE  i.indrelid = '~a'::regclass;"
1750
                                 table))
1751
            (schema-name (spocq:postgresql-uri-schema location))
1752
            (base-iri (if (member table '("transaction_events" "query_events") :test #'equalp)
1753
                          (merge-and-intern-iri (concatenate 'string "/" schema-name) :base-iri (site-namespace))
1754
                          (site-namespace)))
1755
            (schema (clsql-sys:query schema-query :database schema-db))
1756
            (keys (loop for (key) in (clsql-sys:query index-query :database schema-db) collect key))
1757
            (declaration (loop for attribute-decl in schema
1758
                           for name = (second attribute-decl)
1759
                           collect (append attribute-decl
1760
                                           (when (find name keys :test #'string-equal) '(t))))))
1761
       (if declaration
1762
           (decode-api-declaration :sql declaration base-iri)
1763
           (spocq.e:resource-not-found-error :identifier designator)))))
1764
 
1765
 
1766
 ;;; (resource-retrieve-resource-api (repository "operations/accounts")  <http://example.org#accounts>)
1767
 ;;; (resource-retrieve-resource-api (repository "operations/import_events")  <http://example.org#import_events>)
1768
 ;;; (resource-retrieve-resource-api (repository "operations/transaction_events")  <http://example.org#transaction_events>)
1769
 ;;; (resource-retrieve-resource-api (repository "operations/query_events")  <http://example.org#query_events>)
1770
 ;;; or
1771
 ;;; (resource-retrieve-resource-api <postgresql://postgres:postgres@localhost/test?schema=accounting> "query_events")
1772
 
1773
 
1774
 ;;; not used in standard compilation as that control-flow uses compute-agp-pattern-lambda
1775
 (defun compute-materialized-view-function (context bgp-pattern-body &rest args &key base-dimensions)
1776
   (declare (ignore base-dimensions)
1777
            (dynamic-extent args))
1778
   (let* ((key (list 'compute-materialized-view-function bgp-pattern-body))
1779
          (function (get-aspect-cache key)))
1780
     (or function
1781
         (let* ((lambda (apply #'compute-view-lambda context bgp-pattern-body args))
1782
                (function (spocq-compile lambda)))
1783
           (setf (get-aspect-cache key) function)
1784
           (values function lambda)))))
1785
 #+(or)
1786
 (defmethod repository-service-description ((repository materialized-repository))
1787
   (let* ((api (resource-retrieve-resource-api (repository-view-database repository) (repository-name repository)))
1788
          (solutions (compute-repository-service-description-solutions api)))
1789
     (make-service-description :solutions solutions :timestamp (get-universal-time))))
1790
 
1791
     
1792
     
1793
     
1794
 #|
1795
 (run-sparql-internal "select count(*) where {?s ?p ?o}" :repository-id "jhacker/test")
1796
 (run-sparql-internal "select * where {?s ?p ?o}" :repository-id "jhacker/test")
1797
 
1798
 (COMPUTE-MATERIALIZED-VIEW-SQL
1799
   (repository "operations/import_events")
1800
   (resource-retrieve-resource-api (repository "operations/import_events")  <http://example.org#import_events>)
1801
   '((ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1802
                                            <http://dydra.com/operations#job_id>
1803
                                            ?::|uuid|)
1804
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1805
                                            <http://dydra.com/operations#created_at>
1806
                                            ?::|timestamp|)
1807
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1808
                                            <http://dydra.com/operations#agent_key>
1809
                                            ?::|agent|)
1810
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1811
                                            <http://dydra.com/operations#repository_key>
1812
                                            ?::|repository|)
1813
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1814
                                            <http://dydra.com/operations#account_key>
1815
                                            ?::|account|))
1816
   )
1817
 
1818
 (COMPUTE-MATERIALIZED-VIEW-SQL
1819
   (repository "operations/import_events")
1820
   (resource-retrieve-resource-api (repository "operations/import_events")  <http://example.org#import_events>)
1821
   '((ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1822
                                            <http://dydra.com/operations#job_id>
1823
                                            ?::|uuid|)
1824
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1825
                                            <http://dydra.com/operations#repository_key>
1826
                                            "plm"))
1827
   )
1828
 
1829
 (parse-sparql "prefix ops: <http://dydra.com/operations#>
1830
 prefix dc: <http://purl.org/dc/elements/1.1/>
1831
 select ?uuid ?agent ?account ?repository ?timestamp
1832
 where {
1833
    [ <http://dydra.com/operations#job_id> ?uuid ;
1834
      <http://dydra.com/operations#repository_key> 'plm' ]
1835
 } order by desc (?timestamp) limit 20 
1836
 ")
1837
 
1838
 (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|bgp|
1839
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1840
                                            <http://dydra.com/operations#job_id>
1841
                                            ?::|uuid|)
1842
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1843
                                            <http://dydra.com/operations#created_at>
1844
                                            ?::|timestamp|)
1845
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1846
                                            <http://dydra.com/operations#agent_key>
1847
                                            ?::|agent|)
1848
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1849
                                            <http://dydra.com/operations#repository_key>
1850
                                            ?::|repository|)
1851
      (ORG.DATAGRAPH.SPOCQ.ALGEBRA:|triple| ?::?1
1852
                                            <http://dydra.com/operations#account_key>
1853
                                            ?::|account|))
1854
 |#