unofficial mirror of guix-devel@gnu.org 
 help / color / mirror / code / Atom feed
blob ae4a1d27d0caa091461e981e05445037580a1eb4 9077 bytes (raw)
name: guix/sql.scm 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
 
(define-module (guix sql)
  #:use-module (sqlite3)
  #:use-module (system foreign)
  #:use-module (rnrs bytevectors)
  #:use-module (srfi srfi-9)
  #:export (sqlite-register))

;; Miscellaneous SQL stuff, currently just setup for sqlite-register. Mostly
;; macros.

;; This really belongs in guile-sqlite3, as can be seen from the @@s.
(define sqlite-last-insert-rowid
  "Gives the row id of the last inserted row in DB."
  (let ((last-rowid (pointer->procedure
                     int
                     (dynamic-func "sqlite3_last_insert_rowid"
                                   (@@ (sqlite3) libsqlite3))
                     (list '*))))
    (lambda (db)
      (last-rowid ((@@ (sqlite3) db-pointer) db)))))


;; Should I go from key->index here or try to change that in guile-sqlite3?
(define-syntax sql-parameters
  "Converts key-value pairs into sqlite bindings for a specific statement."
  (syntax-rules ()
    ((sql-parameters statement (name1 val1) (name2 val2) (name3 val3) ...)
     (begin (sqlite-bind statement name1 val1)
            (sql-parameters statement (name2 val2) (name3 val3) ...)))
    ((sql-parameters statement (name value))
     (sqlite-bind statement name value))))

(define* (step-all statement #:optional (callback noop))
  "Step until statement is completed. Return number of rows."
  ;; Where "number of rows" is assumed to be number of steps taken, excluding
  ;; the last one.
  (let maybe-step ((ret (sqlite-step statement))
                   (count 0))
    (if ret
        (maybe-step ret (+ count 1))
        count)))

;; I get the feeling schemers have probably already got this "with" business
;; much more automated than this...
(define-syntax with-sql-statement
  "Automatically prepares statements and then finalizes statements once the
scope of this macro is left. Also with built-in sqlite parameter binding via
key-value pairs."
  (syntax-rules ()
    ((with-sql-statement db sql statement-var
                         ((name1 val1) (name2 val2) ...)
                         exps ...)
     (let ((statement-var (sqlite-prepare db sql)))
       (dynamic-wind noop
                     (lambda ()
                       (sql-parameters statement-var
                                            (name1 val1)
                                            (name2 val2) ...)
                       exps ...)
                     (lambda ()
                       (sqlite-finalize statement-var)))))
    ((with-sql-statement db sql statement-var () exps ...)
     (let ((statement-var (sqlite-prepare db sql)))
       (dynamic-wind noop
                     (lambda ()
                       exps ...)
                     (lambda ()
                       (sqlite-finalize statement-var)))))))

(define-syntax with-sql-database
  "Automatically closes the database once the scope of this macro is left."
  (syntax-rules ()
    ((with-sql-database location db-var exps ...)
     (let ((db-var (sqlite-open location)))
       (dynamic-wind noop
                     (lambda ()
                       exps ...)
                     (lambda ()
                       (sqlite-close db-var)))))))

(define-syntax run-sql
  (syntax-rules ()
    "For one-off queries that don't get repeated on the same
database. Everything after database and sql source should be 2-element lists
containing the sql placeholder name and the value to use. Returns the number
of rows."
    ((run-sql db sql (name1 val1) (name2 val2) ...)
     (let ((statement (sqlite-prepare db sql)))
       (dynamic-wind noop
                     (lambda ()
                       (sql-parameters statement
                                            (name1 val1)
                                            (name2 val2) ...)
                       (step-all statement))
                     (lambda ()
                       (sqlite-finalize statement)))))
    ((run-sql db sql)
     (let ((statement (sqlite-prepare db sql)))
       (dynamic-wind noop
                     (lambda ()
                       (step-all statement))
                     (lambda ()
                       (sqlite-finalize statement)))))))

(define-syntax run-statement
  (syntax-rules ()
    "For compiled statements that may be run multiple times. Everything after
database and sql source should be 2-element lists containing the sql
placeholder name and the value to use. Returns the number of rows."
    ((run-sql db statement (name1 val1) (name2 val2) ...)
     (dynamic-wind noop
                   (lambda ()
                     (sql-parameters statement
                                     (name1 val1)
                                     (name2 val2) ...)
                     (step-all statement))
                   (lambda ()
                     (sqlite-reset statement))))
    ((run-sql db statement)
     (dynamic-wind noop
                   (lambda ()
                     (step-all statement))
                   (lambda ()
                     (sqlite-reset statement))))))

(define path-id-sql
  "SELECT id FROM ValidPaths WHERE path = $path")

(define (single-result statement)
  "Gives the first element of the first row returned by statement."
  (let ((row (sqlite-step statement)))
    (if row
        (vector-ref row 0)
        #f)))

(define* (path-id db path)
  "If the path \"path\" exists in the ValidPaths table, return its
id. Otherwise, return #f. If you already have a compiled statement for this
purpose, you can give it as statement."
  (with-sql-statement db path-id-sql statement
                      (;("$path" path)
                       (1 path))
                      (single-result statement)))


(define update-sql
  "UPDATE ValidPaths SET hash = $hash, registrationTime = $time, deriver =
$deriver, narSize = $size WHERE id = $id")

(define insert-sql
  "INSERT INTO ValidPaths (path, hash, registrationTime, deriver, narSize)
VALUES ($path, $hash, $time, $deriver, $size)")

(define (update-or-insert #:key db path deriver hash nar-size time)
  "The classic update-if-exists and insert-if-doesn't feature that sqlite
doesn't exactly have... they've got something close, but it involves deleting
and re-inserting instead of updating, which causes problems with foreign keys,
of course. Returns the row id of the row that was modified or inserted."
  (let ((id (path-id db path)))
    (if id
        (begin
          (run-sql db update-sql
                   ;; As you may have noticed, sqlite-bind doesn't behave
                   ;; exactly how I was expecting...
                   ;; ("$id" id)
                   ;; ("$deriver" deriver)
                   ;; ("$hash" hash)
                   ;; ("$size" nar-size)
                   ;; ("$time" time)
                   (5 id)
                   (3 deriver)
                   (1 hash)
                   (4 nar-size)
                   (2 time))
          id)
        (begin
          (run-sql db insert-sql
                   ;; ("$path" path)
                   ;; ("$deriver" deriver)
                   ;; ("$hash" hash)
                   ;; ("$size" nar-size)
                   ;; ("$time" time)
                   (1 path)
                   (4 deriver)
                   (2 hash)
                   (5 nar-size)
                   (3 time))
          (sqlite-last-insert-rowid db)))))

(define add-reference-sql
  "INSERT OR IGNORE INTO Refs (referrer, reference) SELECT $referrer, id
FROM ValidPaths WHERE path = $reference")

(define (add-references db referrer references)
  "referrer is the id of the referring store item, references is a list
containing store item paths being referred to. Note that all of the store
items in \"references\" should already be registered."
  (with-sql-statement db add-reference-sql add-reference-statement ()
                      (for-each (lambda (reference)
                                  (run-statement db
                                                 add-reference-statement
                                                 ;("$referrer" referrer)
                                                 ;("$reference" reference)
                                                 (1 referrer)
                                                 (2 reference)))
                                references)))

;; XXX figure out caching of statement and database objects... later
(define* (sqlite-register #:key dbpath path references deriver hash nar-size)
  "Registers this stuff in a database specified by DBPATH. PATH is the string
path of some store item, REFERENCES is a list of string paths which the store
item PATH refers to (they need to be already registered!), DERIVER is a string
path of the derivation that created the store item PATH, HASH is the
base16-encoded sha256 hash of the store item denoted by PATH (prefixed with
\"sha256:\") after being converted to nar form, and nar-size is the size in
bytes of the store item denoted by PATH after being converted to nar form."
  (with-sql-database dbpath db
   (let ((id (update-or-insert db path deriver hash nar-size (current-time))))
     (add-references db id references))))

debug log:

solving ae4a1d27d ...
found ae4a1d27d in https://yhetil.org/guix-devel/87a85m96dg.fsf@cune.org/ ||
	https://yhetil.org/guix-devel/87efv18nkt.fsf@cune.org/

applying [1/1] https://yhetil.org/guix-devel/87a85m96dg.fsf@cune.org/
diff --git a/guix/sql.scm b/guix/sql.scm
new file mode 100644
index 000000000..ae4a1d27d

Checking patch guix/sql.scm...
Applied patch guix/sql.scm cleanly.

skipping https://yhetil.org/guix-devel/87efv18nkt.fsf@cune.org/ for ae4a1d27d
index at:
100644 ae4a1d27d0caa091461e981e05445037580a1eb4	guix/sql.scm

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).