1#! /usr/bin/env python3
3"""
4Remove obsolete source and binary associations from suites.
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2009 Torsten Werner <twerner@debian.org>
8@license: GNU General Public License version 2 or later
9"""
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25from daklib.dbconn import *
26from daklib.config import Config
27from daklib import daklog, utils
28import apt_pkg
29import sys
31from sqlalchemy.sql import exists, text
32from tabulate import tabulate
35Options = None
36Logger = None
39def retrieve_associations(suites, session):
40 return session.execute(text('''
41WITH
42 -- Provide (source, suite) tuple of all source packages to remain
43 remain_source AS (
44 SELECT
45 *
46 FROM (
47 SELECT
48 source.id AS source_id,
49 src_associations.suite AS suite_id,
50 -- generate rank over versions of a source package in one suite
51 -- "1" being the newest
52 dense_rank() OVER (
53 PARTITION BY source.source, src_associations.suite
54 ORDER BY source.version DESC
55 ) AS version_rank
56 FROM
57 source
58 INNER JOIN src_associations ON
59 src_associations.source = source.id
60 AND src_associations.suite = ANY(:suite_ids)
61 ) AS source_ranked
62 WHERE
63 version_rank = 1
64 ),
65 -- Provide (source, arch, suite) tuple of all binary packages to remain
66 remain_binaries AS (
67 SELECT
68 *
69 FROM (
70 SELECT
71 binaries.id,
72 binaries.architecture AS arch_id,
73 bin_associations.suite AS suite_id,
74 source.id AS source_id,
75 architecture.arch_string AS arch,
76 -- arch of newest version
77 first_value(architecture.arch_string) OVER (
78 PARTITION BY binaries.package, bin_associations.suite
79 ORDER BY binaries.version DESC
80 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
81 ) as arch_first,
82 -- generate rank over versions of a source package in one suite
83 -- "1" being the newest
84 -- if newest package is arch-any, we use the rank only over current arch
85 dense_rank() OVER (
86 PARTITION BY binaries.package, binaries.architecture, bin_associations.suite
87 ORDER BY binaries.version DESC
88 ) AS version_rank_any,
89 -- if newest package is arch-all, we use the rank over all arches
90 -- this makes it possible to replace all by any and any by all
91 dense_rank() OVER (
92 PARTITION BY binaries.package, bin_associations.suite
93 ORDER BY binaries.version DESC
94 ) AS version_rank_all
95 FROM
96 binaries
97 INNER JOIN source ON source.id = binaries.source
98 INNER JOIN bin_associations ON
99 bin_associations.bin = binaries.id
100 AND bin_associations.suite = ANY(:suite_ids)
101 INNER JOIN architecture ON architecture.id = binaries.architecture
102 ) AS source_rank
103 WHERE
104 -- we only want to retain the newest of each
105 CASE
106 WHEN arch != 'all' AND arch_first != 'all' THEN version_rank_any = 1
107 ELSE version_rank_all = 1
108 END
109 ),
110 -- Figure out which source we should remove
111 -- A binary forces the corresponding source to remain
112 dominate_source AS (
113 SELECT
114 source.source AS source_package,
115 source.version AS source_version,
116 source.source AS package,
117 source.version,
118 'source'::text AS arch,
119 suite.suite_name AS suite,
120 src_associations.id AS assoc_id
121 FROM
122 source
123 INNER JOIN src_associations ON
124 src_associations.source = source.id
125 AND src_associations.suite = ANY(:suite_ids)
126 INNER join suite ON suite.id = src_associations.suite
127 LEFT JOIN remain_binaries ON
128 remain_binaries.source_id = source.id
129 AND remain_binaries.suite_id = src_associations.suite
130 LEFT JOIN remain_source ON
131 remain_source.source_id = source.id
132 AND remain_source.suite_id = src_associations.suite
133 WHERE
134 remain_binaries.source_id IS NULL
135 AND remain_source.source_id IS NULL
136 ),
137 -- Figure out which arch-any binaries we should remove
138 dominate_binaries AS (
139 SELECT
140 source.source AS source_package,
141 source.version AS source_version,
142 binaries.package AS package,
143 binaries.version,
144 architecture.arch_string AS arch,
145 suite.suite_name AS suite,
146 bin_associations.id AS assoc_id
147 FROM
148 binaries
149 INNER JOIN source ON source.id = binaries.source
150 INNER JOIN bin_associations ON
151 bin_associations.bin = binaries.id
152 AND bin_associations.suite = ANY(:suite_ids)
153 INNER JOIN architecture ON architecture.id = binaries.architecture
154 INNER join suite ON suite.id = bin_associations.suite
155 LEFT JOIN remain_binaries ON
156 remain_binaries.id = binaries.id
157 AND remain_binaries.arch_id = binaries.architecture
158 AND remain_binaries.suite_id = bin_associations.suite
159 WHERE
160 remain_binaries.source_id IS NULL
161 AND binaries.architecture != (SELECT id from architecture WHERE arch_string = 'all')
162 ),
163 -- Figure out which arch-all binaries we should remove
164 -- A arch-any binary forces the related arch-all binaries to remain
165 dominate_binaries_all AS (
166 SELECT
167 source.source AS source_package,
168 source.version AS source_version,
169 binaries.package AS package,
170 binaries.version,
171 architecture.arch_string AS arch,
172 suite.suite_name AS suite,
173 bin_associations.id AS assoc_id
174 FROM
175 binaries
176 INNER JOIN source ON source.id = binaries.source
177 INNER JOIN bin_associations ON
178 bin_associations.bin = binaries.id
179 AND bin_associations.suite = ANY(:suite_ids)
180 INNER JOIN architecture ON architecture.id = binaries.architecture
181 INNER join suite ON suite.id = bin_associations.suite
182 LEFT JOIN remain_binaries ON
183 remain_binaries.id = binaries.id
184 AND remain_binaries.arch_id = binaries.architecture
185 AND remain_binaries.suite_id = bin_associations.suite
186 LEFT JOIN remain_binaries AS remain_binaries_any ON
187 remain_binaries_any.source_id = source.id
188 AND remain_binaries_any.suite_id = bin_associations.suite
189 AND remain_binaries_any.arch_id != (SELECT id from architecture WHERE arch_string = 'all')
190 WHERE
191 remain_binaries.source_id IS NULL
192 AND remain_binaries_any.source_id IS NULL
193 AND binaries.architecture = (SELECT id from architecture WHERE arch_string = 'all')
194 )
195SELECT
196 *
197 FROM
198 dominate_source
199 UNION SELECT
200 *
201 FROM
202 dominate_binaries
203 UNION SELECT
204 *
205 FROM
206 dominate_binaries_all
207 ORDER BY
208 source_package, source_version, package, version, arch, suite
209''').params(
210 suite_ids=[s.suite_id for s in suites],
211))
214def delete_associations_table(table, ids, session):
215 result = session.execute(text('''
216 DELETE
217 FROM {}
218 WHERE id = ANY(:assoc_ids)
219 '''.format(table)).params(
220 assoc_ids=list(ids),
221 ))
223 assert result.rowcount == len(ids), 'Rows deleted are not equal to deletion requests'
226def delete_associations(assocs, session):
227 ids_bin = set()
228 ids_src = set()
230 for e in assocs:
231 Logger.log(['newer', e.package, e.version, e.suite, e.arch, e.assoc_id])
233 if e.arch == 'source':
234 ids_src.add(e.assoc_id)
235 else:
236 ids_bin.add(e.assoc_id)
238 delete_associations_table('bin_associations', ids_bin, session)
239 delete_associations_table('src_associations', ids_src, session)
242def usage():
243 print("""Usage: dak dominate [OPTIONS]
244Remove obsolete source and binary associations from suites.
246 -s, --suite=SUITE act on this suite
247 -h, --help show this help and exit
248 -n, --no-action don't commit changes
249 -f, --force also clean up untouchable suites
251SUITE can be comma (or space) separated list, e.g.
252 --suite=testing,unstable""")
253 sys.exit()
256def main():
257 global Options, Logger
258 cnf = Config()
259 Arguments = [('h', "help", "Obsolete::Options::Help"),
260 ('s', "suite", "Obsolete::Options::Suite", "HasArg"),
261 ('n', "no-action", "Obsolete::Options::No-Action"),
262 ('f', "force", "Obsolete::Options::Force")]
263 cnf['Obsolete::Options::Help'] = ''
264 cnf['Obsolete::Options::No-Action'] = ''
265 cnf['Obsolete::Options::Force'] = ''
266 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
267 Options = cnf.subtree("Obsolete::Options")
268 if Options['Help']:
269 usage()
271 if not Options['No-Action']: 271 ↛ 273line 271 didn't jump to line 273, because the condition on line 271 was never false
272 Logger = daklog.Logger("dominate")
273 session = DBConn().session()
275 suites_query = (session
276 .query(Suite)
277 .order_by(Suite.suite_name)
278 .filter(~exists().where(Suite.suite_id == PolicyQueue.suite_id)))
279 if 'Suite' in Options: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true
280 suites_query = suites_query.filter(Suite.suite_name.in_(utils.split_args(Options['Suite'])))
281 if not Options['Force']: 281 ↛ 283line 281 didn't jump to line 283, because the condition on line 281 was never false
282 suites_query = suites_query.filter_by(untouchable=False)
283 suites = suites_query.all()
285 assocs = list(retrieve_associations(suites, session))
287 if Options['No-Action']: 287 ↛ 288line 287 didn't jump to line 288, because the condition on line 287 was never true
288 headers = ('source package', 'source version', 'package', 'version', 'arch', 'suite', 'id')
289 print((tabulate(assocs, headers, tablefmt="orgtbl")))
290 session.rollback()
292 else:
293 delete_associations(assocs, session)
294 session.commit()
296 if Logger: 296 ↛ exitline 296 didn't return from function 'main', because the condition on line 296 was never false
297 Logger.close()
300if __name__ == '__main__':
301 main()