2
0
Fork 0
mirror of git://git.savannah.gnu.org/guix/data-service.git synced 2023-12-14 03:23:03 +01:00
data-service/guix-data-service/comparison.scm
Christopher Baines 83c86431ae Better split up the comparision functionality
The packages comparison was getting confused by differences in the
derivations, so split the data used to make the comparison more sensible.

This resolves an issue comparing 8dd723f5… and 365892e9… which coinsided with
the fix for importing foreign architecture derivations, meaning that a whole
lot of new derivations appeared in the database. Prior to these changes, it
appeared like every package was new, and with these changes, the list is more
sensible.
2020-01-02 20:41:24 +00:00

829 lines
32 KiB
Scheme

;;; Guix Data Service -- Information about Guix over time
;;; Copyright © 2019 Christopher Baines <mail@cbaines.net>
;;;
;;; This program is free software: you can redistribute it and/or
;;; modify it under the terms of the GNU Affero General Public License
;;; as published by the Free Software Foundation, either version 3 of
;;; the License, or (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; Affero General Public License for more details.
;;;
;;; You should have received a copy of the GNU Affero General Public
;;; License along with this program. If not, see
;;; <http://www.gnu.org/licenses/>.
(define-module (guix-data-service comparison)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
#:use-module (ice-9 vlist)
#:use-module (ice-9 match)
#:use-module (squee)
#:use-module (json)
#:use-module (guix-data-service database)
#:use-module (guix-data-service model utils)
#:use-module (guix-data-service model derivation)
#:export (derivation-differences-data
package-differences-data
package-data->package-data-vhashes
package-data-vhashes->new-packages
package-data-vhashes->removed-packages
package-data-version-changes
package-derivation-differences-data
package-derivation-data->package-derivation-data-vhashes
package-derivation-data->names-and-versions
package-derivation-data-vhash->derivations
package-derivation-data-vhash->derivations-and-build-status
package-derivation-data-changes
lint-warning-differences-data
channel-news-differences-data))
(define (derivation-differences-data conn
base-derivation-file-name
target-derivation-file-name)
(define base-derivation
(select-derivation-by-file-name conn base-derivation-file-name))
(define target-derivation
(select-derivation-by-file-name conn target-derivation-file-name))
(define group-by-last-element
(lambda (vals)
(let ((groups (last vals)))
(cons (if (eq? (length groups) 2)
'common
(first groups))
(drop-right vals 1)))))
(define (fetch-value alist key)
(assq-ref (find (lambda (env-var)
(if (string=? key (assq-ref env-var 'key))
(assq-ref env-var 'value)
#f))
alist)
'value))
`((outputs
. ,(group-to-alist
group-by-last-element
(derivation-outputs-differences-data conn
(first base-derivation)
(first target-derivation))))
(inputs
. ,(group-to-alist
group-by-last-element
(derivation-inputs-differences-data conn
(first base-derivation)
(first target-derivation))))
(sources
. ,(group-to-alist
group-by-last-element
(derivation-sources-differences-data conn
(first base-derivation)
(first target-derivation))))
,@(match base-derivation
((_ _ base-builder base-args base-env-vars base-system)
(match target-derivation
((_ _ target-builder target-args target-env-vars target-system)
`((system
. ,(if (string=? base-system target-system)
`((common . ,base-system))
`((base . ,base-system)
(target . ,target-system))))
(builder
. ,(if (string=? base-builder target-builder)
`((common . ,base-builder))
`((base . ,base-builder)
(target . ,target-builder))))
(arguments
. ,(if (eq? base-args target-args)
`((common . ,base-args))
`((base . ,base-args)
(target . ,target-args))))
(environment-variables
. ,(map (lambda (key)
(let ((base-value (fetch-value base-env-vars key))
(target-value (fetch-value target-env-vars key)))
(if (and base-value target-value)
`(,key
. ,(if (string=? base-value target-value)
`((common . ,base-value))
`((base . ,base-value)
(target . ,target-value))))
(if base-value
`(,key . ((base . ,base-value)))
`(,key . ((target . ,target-value)))))))
(delete-duplicates
(map (lambda (env-var)
(assq-ref env-var 'key))
(append base-env-vars
target-env-vars))
string=?))))))))))
(define (derivation-outputs-differences-data conn
base-derivation-id
target-derivation-id)
(define query
(string-append
"
SELECT derivation_outputs.name,
derivation_output_details.path,
derivation_output_details.hash_algorithm,
derivation_output_details.hash,
derivation_output_details.recursive,
ARRAY_AGG(derivation_outputs.derivation_id) AS derivation_ids
FROM derivation_outputs
INNER JOIN derivation_output_details
ON derivation_output_details_id = derivation_output_details.id
WHERE derivation_outputs.derivation_id IN ("
(simple-format #f "~A,~A"
base-derivation-id
target-derivation-id) "
)
GROUP BY 1, 2, 3, 4, 5"))
(map (match-lambda
((output-name path hash-algorithm hash recursive
derivation_ids)
(let ((parsed-derivation-ids
(map string->number
(parse-postgresql-array-string derivation_ids))))
(list output-name
path
hash-algorithm
hash
recursive
(append (if (memq base-derivation-id
parsed-derivation-ids)
'(base)
'())
(if (memq target-derivation-id
parsed-derivation-ids)
'(target)
'()))))))
(exec-query conn query)))
(define (derivation-inputs-differences-data conn
base-derivation-id
target-derivation-id)
(define query
(string-append
"
SELECT derivations.file_name,
derivation_outputs.name,
relevant_derivation_inputs.derivation_ids
FROM derivation_outputs
INNER JOIN (
SELECT derivation_output_id,
ARRAY_AGG(derivation_id) AS derivation_ids
FROM derivation_inputs
WHERE derivation_id IN (" (simple-format #f "~A,~A"
base-derivation-id
target-derivation-id)
") GROUP BY derivation_output_id
) AS relevant_derivation_inputs
ON derivation_outputs.id = relevant_derivation_inputs.derivation_output_id
INNER JOIN derivations ON derivation_outputs.derivation_id = derivations.id
"))
(map (match-lambda
((derivation_file_name derivation_output_name
derivation_ids)
(let ((parsed-derivation-ids
(map string->number
(parse-postgresql-array-string derivation_ids))))
(list derivation_file_name
derivation_output_name
(append (if (memq base-derivation-id
parsed-derivation-ids)
'(base)
'())
(if (memq target-derivation-id
parsed-derivation-ids)
'(target)
'()))))))
(exec-query conn query)))
(define (derivation-sources-differences-data conn
base-derivation-id
target-derivation-id)
(define query
(string-append
"
SELECT derivation_source_files.store_path, ARRAY_AGG(derivation_sources.derivation_id)
FROM derivation_source_files
INNER JOIN derivation_sources
ON derivation_source_files.id = derivation_sources.derivation_source_file_id
WHERE derivation_sources.derivation_id IN (" (simple-format #f "~A,~A"
base-derivation-id
target-derivation-id)
")
GROUP BY derivation_source_files.store_path"))
(map (match-lambda
((store_path derivation_ids)
(let ((parsed-derivation-ids
(map string->number
(parse-postgresql-array-string derivation_ids))))
(list store_path
(append (if (memq base-derivation-id
parsed-derivation-ids)
'(base)
'())
(if (memq target-derivation-id
parsed-derivation-ids)
'(target)
'()))))))
(exec-query conn query)))
(define* (package-derivation-differences-data conn
base_guix_revision_id
target_guix_revision_id
#:key
(systems #f)
(targets #f))
(define extra-constraints
(string-append
(if systems
(string-append
" AND package_derivations.system IN ("
(string-join (map
(lambda (s)
(string-append "'" s "'"))
systems)
", ")
")")
"")
(if targets
(string-append
" AND package_derivations.target IN ("
(string-join (map
(lambda (s)
(string-append "'" s "'"))
targets)
", ")
")")
"")))
(define query
(string-append "
WITH base_packages AS (
SELECT packages.*, derivations.file_name,
package_derivations.system, package_derivations.target
FROM packages
INNER JOIN package_derivations
ON packages.id = package_derivations.package_id
INNER JOIN derivations
ON package_derivations.derivation_id = derivations.id
WHERE package_derivations.id IN (
SELECT guix_revision_package_derivations.package_derivation_id
FROM guix_revision_package_derivations
WHERE revision_id = $1
)" extra-constraints
"), target_packages AS (
SELECT packages.*, derivations.file_name,
package_derivations.system, package_derivations.target
FROM packages
INNER JOIN package_derivations
ON packages.id = package_derivations.package_id
INNER JOIN derivations
ON package_derivations.derivation_id = derivations.id
WHERE package_derivations.id IN (
SELECT guix_revision_package_derivations.package_derivation_id
FROM guix_revision_package_derivations
WHERE revision_id = $2
)" extra-constraints
")
SELECT base_packages.name, base_packages.version,
base_packages.package_metadata_id, base_packages.file_name,
base_packages.system, base_packages.target,
target_packages.name, target_packages.version,
target_packages.package_metadata_id, target_packages.file_name,
target_packages.system, target_packages.target
FROM base_packages
FULL OUTER JOIN target_packages
ON base_packages.name = target_packages.name
AND base_packages.version = target_packages.version
AND base_packages.system = target_packages.system
AND base_packages.target = target_packages.target
WHERE
base_packages.id IS NULL OR
target_packages.id IS NULL OR
base_packages.id != target_packages.id OR
base_packages.file_name != target_packages.file_name
ORDER BY coalesce(base_packages.name, target_packages.name) ASC, base_packages.version, target_packages.version"))
(exec-query conn query (list base_guix_revision_id target_guix_revision_id)))
(define* (package-differences-data conn
base_guix_revision_id
target_guix_revision_id)
(define query
(string-append "
WITH base_packages AS (
SELECT *
FROM packages
WHERE id IN (
SELECT package_id
FROM package_derivations
INNER JOIN guix_revision_package_derivations
ON package_derivations.id =
guix_revision_package_derivations.package_derivation_id
WHERE guix_revision_package_derivations.revision_id = $1
)
), target_packages AS (
SELECT *
FROM packages
WHERE id IN (
SELECT package_id
FROM package_derivations
INNER JOIN guix_revision_package_derivations
ON package_derivations.id =
guix_revision_package_derivations.package_derivation_id
WHERE guix_revision_package_derivations.revision_id = $2
)
)
SELECT base_packages.name, base_packages.version,
base_packages.package_metadata_id,
target_packages.name, target_packages.version,
target_packages.package_metadata_id
FROM base_packages
FULL OUTER JOIN target_packages
ON base_packages.name = target_packages.name
AND base_packages.version = target_packages.version
WHERE
base_packages.id IS NULL OR
target_packages.id IS NULL OR
base_packages.id != target_packages.id
ORDER BY coalesce(base_packages.name, target_packages.name) ASC, base_packages.version, target_packages.version"))
(exec-query conn query (list base_guix_revision_id target_guix_revision_id)))
(define (package-data->package-data-vhashes package-data)
(define (add-data-to-vhash data vhash)
(let ((key (first data)))
(if (string-null? key)
vhash
(vhash-cons key
(drop data 1)
vhash))))
(apply values
(fold (lambda (row result)
(let-values (((base-row-part target-row-part) (split-at row 3)))
(match result
((base-package-data target-package-data)
(list (add-data-to-vhash base-row-part base-package-data)
(add-data-to-vhash target-row-part target-package-data))))))
(list vlist-null vlist-null)
package-data)))
(define (package-derivation-data->package-derivation-data-vhashes package-data)
(define (add-data-to-vhash data vhash)
(let ((key (first data)))
(if (string-null? key)
vhash
(vhash-cons key
(drop data 1)
vhash))))
(apply values
(fold (lambda (row result)
(let-values (((base-row-part target-row-part) (split-at row 6)))
(match result
((base-package-data target-package-data)
(list (add-data-to-vhash base-row-part base-package-data)
(add-data-to-vhash target-row-part target-package-data))))))
(list vlist-null vlist-null)
package-data)))
(define (package-derivation-data->names-and-versions package-data)
(reverse
(pair-fold
(lambda (pair result)
(match pair
(((name . version))
(cons (cons name version)
result))
(((name1 . version1) (name2 . version2) rest ...)
(if (and (string=? name1 name2)
(string=? version1 version2))
result
(cons (cons name1 version1)
result)))))
'()
(map (match-lambda
((base-name base-version _ _ _ _ target-name target-version _ _ _ _)
(if (string-null? base-name)
(cons target-name target-version)
(cons base-name base-version))))
package-data))))
(define (package-derivation-data-vhash->derivations conn packages-vhash)
(define (vhash->derivation-ids vhash)
(vhash-fold (lambda (key value result)
(cons (third value)
result))
'()
vhash))
(let* ((derivation-ids
(vhash->derivation-ids packages-vhash))
(derivation-data
(select-derivations-by-id conn derivation-ids)))
derivation-data))
(define (package-derivation-data-vhash->derivations-and-build-status
conn
package-derivation-data-vhash
systems
targets
build-statuses)
(define (vhash->derivation-file-names vhash)
(vhash-fold (lambda (key value result)
(cons (third value)
result))
'()
vhash))
(let* ((derivation-file-names
(vhash->derivation-file-names package-derivation-data-vhash)))
(if (null? derivation-file-names)
'()
(select-derivations-and-build-status
conn
#:file-names derivation-file-names
#:systems (if (null? systems) #f systems)
#:targets (if (null? targets) #f targets)
#:build-statuses (if (null? build-statuses) #f build-statuses)))))
(define (package-data-vhash->package-name-and-version-hash-table vhash)
(vhash-fold (lambda (name details result)
(let ((key (cons name (first details))))
(hash-set! result
key
(cons (cdr details)
(or (hash-ref result key)
'())))
result))
(make-hash-table)
vhash))
(define (package-data-vhashes->new-packages base-packages-vhash target-packages-vhash)
(hash-map->list
(match-lambda*
(((name . version) metadata ...)
`((name . ,name)
(version . ,version))))
(package-data-vhash->package-name-and-version-hash-table
(vlist-filter (match-lambda
((name . details)
(not (vhash-assoc name base-packages-vhash))))
target-packages-vhash))))
(define (package-data-vhashes->removed-packages base-packages-vhash target-packages-vhash)
(hash-map->list
(match-lambda*
(((name . version) metadata ...)
`((name . ,name)
(version . ,version))))
(package-data-vhash->package-name-and-version-hash-table
(vlist-filter (match-lambda
((name . details)
(not (vhash-assoc name target-packages-vhash))))
base-packages-vhash))))
(define (package-data-vhash->package-versions-hash-table package-data-vhash)
(vhash-fold (lambda (name details result)
(let ((version (first details))
(known-versions (or (hash-ref result name)
'())))
(hash-set! result
name
(cons version known-versions))
result))
(make-hash-table)
package-data-vhash))
(define (package-data-version-changes base-packages-vhash target-packages-vhash)
(let ((base-versions
(package-data-vhash->package-versions-hash-table
base-packages-vhash))
(target-versions
(package-data-vhash->package-versions-hash-table
target-packages-vhash)))
>
(hash-fold (lambda (name target-versions result)
(let ((base-versions (hash-ref base-versions name)))
(if base-versions
(let ((base-version-numbers base-versions)
(target-version-numbers target-versions))
(if (equal? base-version-numbers target-version-numbers)
result
(cons
`(,name . ((base . ,(list->vector base-version-numbers))
(target . ,(list->vector target-version-numbers))))
result)))
result)))
'()
target-versions)))
(define (package-derivation-data-changes names-and-versions
base-packages-vhash
target-packages-vhash)
(define base-package-details-by-name-and-version
(package-data-vhash->package-name-and-version-hash-table base-packages-vhash))
(define target-package-details-by-name-and-version
(package-data-vhash->package-name-and-version-hash-table target-packages-vhash))
(define (derivation-system-and-target-list->alist lst)
(if (null? lst)
'()
`(,(match (first lst)
((derivation-file-name system target)
`((system . ,system)
(target . ,target)
(derivation-file-name . ,derivation-file-name))))
,@(derivation-system-and-target-list->alist (cdr lst)))))
(list->vector
(filter-map
(lambda (name-and-version)
(let ((base-packages-entry
(hash-ref base-package-details-by-name-and-version
name-and-version))
(target-packages-entry
(hash-ref target-package-details-by-name-and-version
name-and-version)))
(cond
((and base-packages-entry target-packages-entry)
(let ((base-derivations (map cdr base-packages-entry))
(target-derivations (map cdr target-packages-entry)))
(if (equal? base-derivations target-derivations)
#f
`((name . ,(car name-and-version))
(version . ,(cdr name-and-version))
(base . ,(list->vector
(derivation-system-and-target-list->alist
base-derivations)))
(target . ,(list->vector
(derivation-system-and-target-list->alist
target-derivations)))))))
(base-packages-entry
(let ((base-derivations (map cdr base-packages-entry)))
`((name . ,(car name-and-version))
(version . ,(cdr name-and-version))
(base . ,(list->vector
(derivation-system-and-target-list->alist
base-derivations)))
(target . ,(list->vector '())))))
(else
(let ((target-derivations (map cdr target-packages-entry)))
`((name . ,(car name-and-version))
(version . ,(cdr name-and-version))
(base . ,(list->vector '()))
(target . ,(list->vector
(derivation-system-and-target-list->alist
target-derivations)))))))))
names-and-versions)))
(define (lint-warning-differences-data conn
base-guix-revision-id
target-guix-revision-id)
(define query
(string-append "
WITH base_lint_warnings AS (
SELECT lint_warnings.id,
packages.name, packages.version,
lint_checkers.name AS lint_checker_name,
lint_checkers.description AS lint_checker_description,
lint_checkers.network_dependent AS lint_checker_network_dependent,
locations.file, locations.line, locations.column_number,
lint_warning_messages.message
FROM lint_warnings
INNER JOIN packages
ON lint_warnings.package_id = packages.id
INNER JOIN lint_checkers
ON lint_warnings.lint_checker_id = lint_checkers.id
INNER JOIN locations
ON lint_warnings.location_id = locations.id
INNER JOIN lint_warning_message_sets
ON lint_warnings.lint_warning_message_set_id = lint_warning_message_sets.id
INNER JOIN lint_warning_messages
ON lint_warning_messages.id = ANY (lint_warning_message_sets.message_ids) AND
lint_warning_messages.locale = 'en_US.utf8'
WHERE lint_warnings.id IN (
SELECT lint_warning_id
FROM guix_revision_lint_warnings
WHERE guix_revision_id = $1
)
), target_lint_warnings AS (
SELECT lint_warnings.id,
packages.name, packages.version,
lint_checkers.name AS lint_checker_name,
lint_checkers.description AS lint_checker_description,
lint_checkers.network_dependent AS lint_checker_network_dependent,
locations.file, locations.line, locations.column_number,
lint_warning_messages.message
FROM lint_warnings
INNER JOIN packages
ON lint_warnings.package_id = packages.id
INNER JOIN lint_checkers
ON lint_warnings.lint_checker_id = lint_checkers.id
INNER JOIN locations
ON lint_warnings.location_id = locations.id
INNER JOIN lint_warning_message_sets
ON lint_warnings.lint_warning_message_set_id = lint_warning_message_sets.id
INNER JOIN lint_warning_messages
ON lint_warning_messages.id = ANY (lint_warning_message_sets.message_ids) AND
lint_warning_messages.locale = 'en_US.utf8'
WHERE lint_warnings.id IN (
SELECT lint_warning_id
FROM guix_revision_lint_warnings
WHERE guix_revision_id = $2
)
)
SELECT coalesce(
base_lint_warnings.name,
target_lint_warnings.name
) AS package_name,
coalesce(
base_lint_warnings.version,
target_lint_warnings.version
) AS package_version,
coalesce(
base_lint_warnings.lint_checker_name,
target_lint_warnings.lint_checker_name
) AS lint_checker_name,
coalesce(
base_lint_warnings.message,
target_lint_warnings.message
) AS message,
coalesce(
base_lint_warnings.lint_checker_description,
target_lint_warnings.lint_checker_description
) AS lint_checker_description,
coalesce(
base_lint_warnings.lint_checker_network_dependent,
target_lint_warnings.lint_checker_network_dependent
) AS lint_checker_network_dependent,
coalesce(
base_lint_warnings.file,
target_lint_warnings.file
) AS file,
coalesce(
base_lint_warnings.line,
target_lint_warnings.line
) AS line,
coalesce(
base_lint_warnings.column_number,
target_lint_warnings.column_number
) AS column_number,
CASE
WHEN base_lint_warnings.name IS NULL THEN 'new'
WHEN target_lint_warnings.name IS NULL THEN 'removed'
ELSE 'moved'
END AS change
FROM base_lint_warnings
FULL OUTER JOIN target_lint_warnings
ON base_lint_warnings.name = target_lint_warnings.name
AND base_lint_warnings.lint_checker_name = target_lint_warnings.lint_checker_name
AND (
base_lint_warnings.message = target_lint_warnings.message OR
-- TODO Some lint warnings include the line number in the message, so
-- they'll appear to be altered if the package definition moves within the
-- file, therefore try replacing the line number to see if the message matches
-- that way as well
replace(base_lint_warnings.message,base_lint_warnings.line::varchar,target_lint_warnings.line::varchar) = target_lint_warnings.message
)
WHERE
(
base_lint_warnings.id IS NULL OR
target_lint_warnings.id IS NULL OR
base_lint_warnings.id != target_lint_warnings.id
) AND (
base_lint_warnings.name IS NULL OR
target_lint_warnings.name IS NULL
)
ORDER BY coalesce(base_lint_warnings.name, target_lint_warnings.name) ASC, base_lint_warnings.version, target_lint_warnings.version, change"))
(exec-query conn query
(list base-guix-revision-id
target-guix-revision-id)))
(define (channel-news-differences-data conn
base-guix-revision-id
target-guix-revision-id)
(define query
"
WITH base_news_entries AS (
SELECT channel_news_entries.id,
channel_news_entries.commit,
channel_news_entries.tag,
(
SELECT JSON_AGG(ARRAY[lang,text])
FROM channel_news_entry_text
INNER JOIN channel_news_entry_titles
ON channel_news_entry_text.id = channel_news_entry_titles.channel_news_entry_text_id
WHERE channel_news_entry_titles.channel_news_entry_id = channel_news_entries.id
) AS title_text,
(
SELECT JSON_AGG(ARRAY[lang,text])
FROM channel_news_entry_text
INNER JOIN channel_news_entry_bodies
ON channel_news_entry_text.id = channel_news_entry_bodies.channel_news_entry_text_id
WHERE channel_news_entry_bodies.channel_news_entry_id = channel_news_entries.id
) AS body_text
FROM channel_news_entries
WHERE id IN (
SELECT channel_news_entry_id
FROM guix_revision_channel_news_entries
WHERE guix_revision_channel_news_entries.guix_revision_id = $1
)
), target_news_entries AS (
SELECT channel_news_entries.id,
channel_news_entries.commit,
channel_news_entries.tag,
(
SELECT JSON_AGG(ARRAY[lang,text])
FROM channel_news_entry_text
INNER JOIN channel_news_entry_titles
ON channel_news_entry_text.id = channel_news_entry_titles.channel_news_entry_text_id
WHERE channel_news_entry_titles.channel_news_entry_id = channel_news_entries.id
) AS title_text,
(
SELECT JSON_AGG(ARRAY[lang,text])
FROM channel_news_entry_text
INNER JOIN channel_news_entry_bodies
ON channel_news_entry_text.id = channel_news_entry_bodies.channel_news_entry_text_id
WHERE channel_news_entry_bodies.channel_news_entry_id = channel_news_entries.id
) AS body_text
FROM channel_news_entries
WHERE id IN (
SELECT channel_news_entry_id
FROM guix_revision_channel_news_entries
WHERE guix_revision_channel_news_entries.guix_revision_id = $2
)
)
SELECT coalesce(
base_news_entries.commit,
target_news_entries.commit
) AS commit,
coalesce(
base_news_entries.tag,
target_news_entries.tag
) AS tag,
coalesce(
base_news_entries.title_text,
target_news_entries.title_text
) AS title_text,
coalesce(
base_news_entries.body_text,
target_news_entries.body_text
) AS body_text,
CASE
WHEN base_news_entries.id IS NULL THEN 'new'
WHEN target_news_entries.id IS NULL THEN 'removed'
ELSE 'changed'
END AS change
FROM base_news_entries
FULL OUTER JOIN target_news_entries
ON base_news_entries.commit = target_news_entries.commit
WHERE (
base_news_entries.id IS NULL OR
target_news_entries.id IS NULL OR
base_news_entries.id != target_news_entries.id
)")
(map
(match-lambda
((commit tag title_text body_text change)
(list commit
tag
(map (match-lambda
(#(lang text)
(cons lang text)))
(vector->list
(json-string->scm title_text)))
(map (match-lambda
(#(lang text)
(cons lang text)))
(vector->list
(json-string->scm body_text)))
(string->symbol change))))
(exec-query-with-null-handling conn query
(list base-guix-revision-id
target-guix-revision-id))))