database.scm 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. ;;; database.scm -- store evaluation and build results
  2. ;;; Copyright © 2016, 2017 Mathieu Lirzin <mthl@gnu.org>
  3. ;;;
  4. ;;; This file is part of Cuirass.
  5. ;;;
  6. ;;; Cuirass is free software: you can redistribute it and/or modify
  7. ;;; it under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation, either version 3 of the License, or
  9. ;;; (at your option) any later version.
  10. ;;;
  11. ;;; Cuirass is distributed in the hope that it will be useful,
  12. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Cuirass. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (cuirass database)
  19. #:use-module (cuirass config)
  20. #:use-module (cuirass utils)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 rdelim)
  23. #:use-module (sqlite3)
  24. #:export (;; Procedures.
  25. assq-refs
  26. db-init
  27. db-open
  28. db-close
  29. db-add-specification
  30. db-get-specifications
  31. db-add-stamp
  32. db-get-stamp
  33. db-add-evaluation
  34. db-add-derivation
  35. db-get-derivation
  36. db-add-build
  37. read-sql-file
  38. read-quoted-string
  39. sqlite-exec
  40. ;; Parameters.
  41. %package-database
  42. %package-schema-file
  43. ;; Macros.
  44. with-database))
  45. (define (sqlite-exec db msg . args)
  46. "Wrap 'sqlite-prepare', 'sqlite-step', and 'sqlite-finalize'. Send message
  47. MSG to database DB. MSG can contain '~A' and '~S' escape characters which
  48. will be replaced by ARGS."
  49. (let* ((sql (apply simple-format #f msg args))
  50. (stmt (sqlite-prepare db sql))
  51. (res (let loop ((res '()))
  52. (let ((row (sqlite-step stmt)))
  53. (if (not row)
  54. (reverse! res)
  55. (loop (cons row res)))))))
  56. (sqlite-finalize stmt)
  57. res))
  58. (define %package-database
  59. ;; Define to the database file name of this package.
  60. (make-parameter (string-append %localstatedir "/" %package ".db")))
  61. (define %package-schema-file
  62. ;; Define to the database schema file of this package.
  63. (make-parameter (string-append (or (getenv "CUIRASS_DATADIR")
  64. (string-append %datadir "/" %package))
  65. "/schema.sql")))
  66. (define (read-sql-file file-name)
  67. "Return a list of string containing SQL instructions from FILE-NAME."
  68. (call-with-input-file file-name
  69. (λ (port)
  70. (let loop ((insts '()))
  71. (let ((inst (read-delimited ";" port 'concat)))
  72. (if (or (eof-object? inst)
  73. ;; Don't cons the spaces after the last instructions.
  74. (string-every char-whitespace? inst))
  75. (reverse! insts)
  76. (loop (cons inst insts))))))))
  77. (define* (db-init #:optional (db-name (%package-database))
  78. #:key (schema (%package-schema-file)))
  79. "Open the database to store and read jobs and builds informations. Return a
  80. database object."
  81. (when (file-exists? db-name)
  82. (format (current-error-port) "Removing leftover database ~a~%" db-name)
  83. (delete-file db-name))
  84. (let ((db (sqlite-open db-name (logior SQLITE_OPEN_CREATE
  85. SQLITE_OPEN_READWRITE))))
  86. (for-each (λ (sql) (sqlite-exec db sql))
  87. (read-sql-file schema))
  88. db))
  89. (define* (db-open #:optional (db (%package-database)))
  90. "Open database to store or read jobs and builds informations. Return a
  91. database object."
  92. (if (file-exists? db)
  93. (sqlite-open db SQLITE_OPEN_READWRITE)
  94. (db-init db)))
  95. (define (db-close db)
  96. "Close database object DB."
  97. (sqlite-close db))
  98. (define* (assq-refs alst keys #:optional default-value)
  99. (map (λ (key) (or (assq-ref alst key) default-value))
  100. keys))
  101. (define (last-insert-rowid db)
  102. (vector-ref (car (sqlite-exec db "SELECT last_insert_rowid();"))
  103. 0))
  104. (define (db-add-specification db spec)
  105. "Store specification SPEC in database DB and return its ID."
  106. (apply sqlite-exec db "\
  107. INSERT OR IGNORE INTO Specifications (repo_name, url, load_path, file, \
  108. proc, arguments, branch, tag, revision, no_compile_p) \
  109. VALUES ('~A', '~A', '~A', '~A', '~S', '~S', '~A', '~A', '~A', ~A);"
  110. (append
  111. (assq-refs spec '(#:name #:url #:load-path #:file #:proc #:arguments))
  112. (assq-refs spec '(#:branch #:tag #:commit) "NULL")
  113. (list (if (assq-ref spec #:no-compile?) "1" "0"))))
  114. (last-insert-rowid db))
  115. (define (db-get-specifications db)
  116. (let loop ((rows (sqlite-exec db "SELECT * FROM Specifications;"))
  117. (specs '()))
  118. (match rows
  119. (() specs)
  120. ((#(name url load-path file proc args branch tag rev no-compile?)
  121. . rest)
  122. (loop rest
  123. (cons `((#:name . ,name)
  124. (#:url . ,url)
  125. (#:load-path . ,load-path)
  126. (#:file . ,file)
  127. (#:proc . ,(with-input-from-string proc read))
  128. (#:arguments . ,(with-input-from-string args read))
  129. (#:branch . ,branch)
  130. (#:tag . ,(if (string=? tag "NULL") #f tag))
  131. (#:commit . ,(if (string=? rev "NULL") #f rev))
  132. (#:no-compile? . ,(positive? no-compile?)))
  133. specs))))))
  134. (define (db-add-derivation db job)
  135. "Store a derivation result in database DB and return its ID."
  136. (sqlite-exec db "\
  137. INSERT OR IGNORE INTO Derivations (derivation, job_name, evaluation)\
  138. VALUES ('~A', '~A', '~A');"
  139. (assq-ref job #:derivation)
  140. (assq-ref job #:job-name)
  141. (assq-ref job #:eval-id)))
  142. (define (db-get-derivation db id)
  143. "Retrieve a job in database DB which corresponds to ID."
  144. (car (sqlite-exec db "SELECT * FROM Derivations WHERE derivation='~A';" id)))
  145. (define (db-add-evaluation db eval)
  146. (sqlite-exec db "\
  147. INSERT INTO Evaluations (specification, revision) VALUES ('~A', '~A');"
  148. (assq-ref eval #:specification)
  149. (assq-ref eval #:revision))
  150. (last-insert-rowid db))
  151. (define-syntax-rule (with-database db body ...)
  152. "Run BODY with a connection to the database which is bound to DB in BODY."
  153. (let ((db (db-open)))
  154. (dynamic-wind
  155. (const #t)
  156. (λ () body ...)
  157. (λ () (db-close db)))))
  158. (define* (read-quoted-string #:optional (port (current-input-port)))
  159. "Read all of the characters out of PORT and return them as a SQL quoted
  160. string."
  161. (let loop ((chars '()))
  162. (let ((char (read-char port)))
  163. (cond ((eof-object? char) (list->string (reverse! chars)))
  164. ((char=? char #\') (loop (cons* char char chars)))
  165. (else (loop (cons char chars)))))))
  166. (define (db-add-build db build)
  167. "Store BUILD in database DB."
  168. (sqlite-exec db "\
  169. INSERT INTO Builds (derivation, evaluation, log, output)\
  170. VALUES ('~A', '~A', '~A', '~A');"
  171. (assq-ref build #:derivation)
  172. (assq-ref build #:eval-id)
  173. (assq-ref build #:log)
  174. (assq-ref build #:output))
  175. (last-insert-rowid db))
  176. (define (db-get-stamp db spec)
  177. "Return a stamp corresponding to specification SPEC in database DB."
  178. (let ((res (sqlite-exec db "SELECT * FROM Stamps WHERE specification='~A';"
  179. (assq-ref spec #:id))))
  180. (match res
  181. (() "")
  182. ((#(spec commit)) commit))))
  183. (define (db-add-stamp db spec commit)
  184. "Associate stamp COMMIT to specification SPEC in database DB."
  185. (if (string-null? (db-get-stamp db spec))
  186. (sqlite-exec db "\
  187. INSERT INTO Stamps (specification, stamp) VALUES ('~A', '~A');"
  188. (assq-ref spec #:id)
  189. commit)
  190. (sqlite-exec db "\
  191. UPDATE Stamps SET stamp='~A' WHERE specification='~A';"
  192. commit
  193. (assq-ref spec #:id))))