database: Factorize 'sqlite-error' handling.

* src/cuirass/database.scm (catch-sqlite-error): New macro.
(db-add-checkout, db-add-output, db-add-build): Use it instead of custom
'catch' block'.
This commit is contained in:
Ludovic Courtès 2018-11-16 21:47:18 +01:00
parent 0b40dca734
commit 09afb02528
1 changed files with 67 additions and 62 deletions

View File

@ -125,6 +125,19 @@ This ensures that (1) SQL injection is impossible, and (2) the number of
question marks matches the number of arguments to bind."
(sqlite-exec/bind db () "" query args ...))
(define-syntax catch-sqlite-error
(syntax-rules (on =>)
"Run EXP..., catching SQLite error and handling the given code as
specified."
((_ exp ... (on error => handle ...))
(catch 'sqlite-error
(lambda ()
exp ...)
(lambda (key who code message . rest)
(if (= code error)
(begin handle ...)
(apply throw key who code rest)))))))
(define %package-database
;; Define to the database file name of this package.
(make-parameter (string-append %localstatedir "/lib/" %package
@ -277,24 +290,21 @@ tag, revision, no_compile_p) VALUES ("
"Insert CHECKOUT associated with SPEC-NAME and EVAL-ID. If a checkout with
the same revision already exists for SPEC-NAME, return #f."
(with-db-critical-section db
(catch 'sqlite-error
(lambda ()
(sqlite-exec db "\
(catch-sqlite-error
(sqlite-exec db "\
INSERT INTO Checkouts (specification, revision, evaluation, input,
directory) VALUES ("
spec-name ", "
(assq-ref checkout #:commit) ", "
eval-id ", "
(assq-ref checkout #:input) ", "
(assq-ref checkout #:directory) ");")
(last-insert-rowid db))
(lambda (key who code message . rest)
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same checkout. That happens for each input
;; that doesn't change between two evaluations.
(if (= code SQLITE_CONSTRAINT_PRIMARYKEY)
#f
(apply throw key who code rest))))))
spec-name ", "
(assq-ref checkout #:commit) ", "
eval-id ", "
(assq-ref checkout #:input) ", "
(assq-ref checkout #:directory) ");")
(last-insert-rowid db)
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same checkout. That happens for each input
;; that doesn't change between two evaluations.
(on SQLITE_CONSTRAINT_PRIMARYKEY => #f))))
(define (db-add-specification spec)
"Store SPEC in database the database. SPEC inputs are stored in the INPUTS
@ -437,61 +447,56 @@ string."
"Insert OUTPUT associated with DERIVATION. If an output with the same path
already exists, return #f."
(with-db-critical-section db
(catch 'sqlite-error
(lambda ()
(match output
((name . path)
(sqlite-exec db "\
(catch-sqlite-error
(match output
((name . path)
(sqlite-exec db "\
INSERT INTO Outputs (derivation, name, path) VALUES ("
derivation ", " name ", " path ");")))
(last-insert-rowid db))
(lambda (key who code message . rest)
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same output. That happens with fixed-output
;; derivations.
(if (= code SQLITE_CONSTRAINT_PRIMARYKEY)
#f
(apply throw key who code rest))))))
derivation ", " name ", " path ");")))
(last-insert-rowid db)
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same output. That happens with fixed-output
;; derivations.
(on SQLITE_CONSTRAINT_PRIMARYKEY => #f))))
(define (db-add-build build)
"Store BUILD in database the database only if one of its outputs is new.
Return #f otherwise. BUILD outputs are stored in the OUTPUTS table."
(with-db-critical-section db
(catch 'sqlite-error
(lambda ()
(sqlite-exec db "BEGIN TRANSACTION;")
(sqlite-exec db "
(catch-sqlite-error
(sqlite-exec db "BEGIN TRANSACTION;")
(sqlite-exec db "
INSERT INTO Builds (derivation, evaluation, job_name, system, nix_name, log,
status, timestamp, starttime, stoptime)
VALUES ("
(assq-ref build #:derivation) ", "
(assq-ref build #:eval-id) ", "
(assq-ref build #:job-name) ", "
(assq-ref build #:system) ", "
(assq-ref build #:nix-name) ", "
(assq-ref build #:log) ", "
(or (assq-ref build #:status)
(build-status scheduled)) ", "
(or (assq-ref build #:timestamp) 0) ", "
(or (assq-ref build #:starttime) 0) ", "
(or (assq-ref build #:stoptime) 0) ");")
(let* ((derivation (assq-ref build #:derivation))
(outputs (assq-ref build #:outputs))
(new-outputs (filter-map (cut db-add-output derivation <>)
outputs)))
(if (null? new-outputs)
(begin (sqlite-exec db "ROLLBACK;")
#f)
(begin (sqlite-exec db "COMMIT;")
derivation))))
(lambda (key who code message . rest)
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same build. That happens when several jobs
;; produce the same derivation, and we can ignore it.
(if (= code SQLITE_CONSTRAINT_PRIMARYKEY)
(begin (sqlite-exec db "ROLLBACK;")
#f)
(apply throw key who code rest))))))
(assq-ref build #:derivation) ", "
(assq-ref build #:eval-id) ", "
(assq-ref build #:job-name) ", "
(assq-ref build #:system) ", "
(assq-ref build #:nix-name) ", "
(assq-ref build #:log) ", "
(or (assq-ref build #:status)
(build-status scheduled)) ", "
(or (assq-ref build #:timestamp) 0) ", "
(or (assq-ref build #:starttime) 0) ", "
(or (assq-ref build #:stoptime) 0) ");")
(let* ((derivation (assq-ref build #:derivation))
(outputs (assq-ref build #:outputs))
(new-outputs (filter-map (cut db-add-output derivation <>)
outputs)))
(if (null? new-outputs)
(begin (sqlite-exec db "ROLLBACK;")
#f)
(begin (sqlite-exec db "COMMIT;")
derivation)))
;; If we get a unique-constraint-failed error, that means we have
;; already inserted the same build. That happens when several jobs
;; produce the same derivation, and we can ignore it.
(on SQLITE_CONSTRAINT_PRIMARYKEY
=>
(sqlite-exec db "ROLLBACK;") #f))))
(define* (db-update-build-status! drv status #:key log-file)
"Update the database so that DRV's status is STATUS. This also updates the