]> git.decadent.org.uk Git - dak.git/blob - daklib/DBConn.py
Content generation seems to be working now
[dak.git] / daklib / DBConn.py
1 #!/usr/bin/env python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import psycopg2
38
39 from Singleton import Singleton
40 from Config import Config
41
42 ################################################################################
43
44 class Cache(object):
45     def __init__(self, hashfunc=None):
46         if hashfunc:
47             self.hashfunc = hashfunc
48         else:
49             self.hashfunc = lambda x: x['value']
50
51         self.data = {}
52
53     def SetValue(self, keys, value):
54         self.data[self.hashfunc(keys)] = value
55
56     def GetValue(self, keys):
57         return self.data.get(self.hashfunc(keys))
58
59 ################################################################################
60
61 class DBConn(Singleton):
62     """
63     database module init.
64     """
65     def __init__(self, *args, **kwargs):
66         super(DBConn, self).__init__(*args, **kwargs)
67
68     def _startup(self, *args, **kwargs):
69         self.__createconn()
70         self.__init_caches()
71
72     ## Connection functions
73     def __createconn(self):
74         cnf = Config()
75         connstr = "dbname=%s" % cnf["DB::Name"]
76         if cnf["DB::Host"]:
77            connstr += " host=%s" % cnf["DB::Host"]
78         if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
79            connstr += " port=%s" % cnf["DB::Port"]
80
81         self.db_con = psycopg2.connect(connstr)
82
83     def reconnect(self):
84         try:
85             self.db_con.close()
86         except psycopg2.InterfaceError:
87             pass
88
89         self.db_con = None
90         self.__createconn()
91
92     ## Cache functions
93     def __init_caches(self):
94         self.caches = {'suite':         Cache(),
95                        'section':       Cache(),
96                        'priority':      Cache(),
97                        'override_type': Cache(),
98                        'architecture':  Cache(),
99                        'archive':       Cache(),
100                        'component':     Cache(),
101                        'content_path_names':     Cache(),
102                        'content_file_names':     Cache(),
103                        'location':      Cache(lambda x: '%s_%s_%s' % (x['location'], x['component'], x['location'])),
104                        'maintainer':    {}, # TODO
105                        'keyring':       {}, # TODO
106                        'source':        Cache(lambda x: '%s_%s_' % (x['source'], x['version'])),
107                        'files':         {}, # TODO
108                        'maintainer':    {}, # TODO
109                        'fingerprint':   {}, # TODO
110                        'queue':         {}, # TODO
111                        'uid':           {}, # TODO
112                        'suite_version': Cache(lambda x: '%s_%s' % (x['source'], x['suite'])),
113                       }
114
115     def clear_caches(self):
116         self.__init_caches()
117
118     ## Functions to pass through to the database connector
119     def cursor(self):
120         return self.db_con.cursor()
121
122     def commit(self):
123         return self.db_con.commit()
124
125     ## Get functions
126     def __get_single_id(self, query, values, cachename=None):
127         # This is a bit of a hack but it's an internal function only
128         if cachename is not None:
129             res = self.caches[cachename].GetValue(values)
130             if res:
131                 return res
132
133         c = self.db_con.cursor()
134         c.execute(query, values)
135
136         if c.rowcount != 1:
137             return None
138
139         res = c.fetchone()[0]
140
141         if cachename is not None:
142             self.caches[cachename].SetValue(values, res)
143
144         return res
145
146     def __get_id(self, retfield, table, qfield, value):
147         query = "SELECT %s FROM %s WHERE %s = %%(value)s" % (retfield, table, qfield)
148         return self.__get_single_id(query, {'value': value}, cachename=table)
149
150     def get_suite_id(self, suite):
151         """
152         Returns database id for given C{suite}.
153         Results are kept in a cache during runtime to minimize database queries.
154
155         @type suite: string
156         @param suite: The name of the suite
157
158         @rtype: int
159         @return: the database id for the given suite
160
161         """
162         return self.__get_id('id', 'suite', 'suite_name', suite)
163
164     def get_section_id(self, section):
165         """
166         Returns database id for given C{section}.
167         Results are kept in a cache during runtime to minimize database queries.
168
169         @type section: string
170         @param section: The name of the section
171
172         @rtype: int
173         @return: the database id for the given section
174
175         """
176         return self.__get_id('id', 'section', 'section', section)
177
178     def get_priority_id(self, priority):
179         """
180         Returns database id for given C{priority}.
181         Results are kept in a cache during runtime to minimize database queries.
182
183         @type priority: string
184         @param priority: The name of the priority
185
186         @rtype: int
187         @return: the database id for the given priority
188
189         """
190         return self.__get_id('id', 'priority', 'priority', priority)
191
192     def get_override_type_id(self, override_type):
193         """
194         Returns database id for given override C{type}.
195         Results are kept in a cache during runtime to minimize database queries.
196
197         @type type: string
198         @param type: The name of the override type
199
200         @rtype: int
201         @return: the database id for the given override type
202
203         """
204         return self.__get_id('id', 'override_type', 'override_type', override_type)
205
206     def get_architecture_id(self, architecture):
207         """
208         Returns database id for given C{architecture}.
209         Results are kept in a cache during runtime to minimize database queries.
210
211         @type architecture: string
212         @param architecture: The name of the override type
213
214         @rtype: int
215         @return: the database id for the given architecture
216
217         """
218         return self.__get_id('id', 'architecture', 'arch_string', architecture)
219
220     def get_archive_id(self, archive):
221         """
222         returns database id for given c{archive}.
223         results are kept in a cache during runtime to minimize database queries.
224
225         @type archive: string
226         @param archive: the name of the override type
227
228         @rtype: int
229         @return: the database id for the given archive
230
231         """
232         return self.__get_id('id', 'archive', 'lower(name)', archive)
233
234     def get_component_id(self, component):
235         """
236         Returns database id for given C{component}.
237         Results are kept in a cache during runtime to minimize database queries.
238
239         @type component: string
240         @param component: The name of the override type
241
242         @rtype: int
243         @return: the database id for the given component
244
245         """
246         return self.__get_id('id', 'component', 'lower(name)', component)
247
248     def get_location_id(self, location, component, archive):
249         """
250         Returns database id for the location behind the given combination of
251           - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
252           - B{component} - the id of the component as returned by L{get_component_id}
253           - B{archive} - the id of the archive as returned by L{get_archive_id}
254         Results are kept in a cache during runtime to minimize database queries.
255
256         @type location: string
257         @param location: the path of the location
258
259         @type component: int
260         @param component: the id of the component
261
262         @type archive: int
263         @param archive: the id of the archive
264
265         @rtype: int
266         @return: the database id for the location
267
268         """
269
270         archive_id = self.get_archive_id(archive)
271
272         if not archive_id:
273             return None
274
275         res = None
276
277         if component:
278             component_id = self.get_component_id(component)
279             if component_id:
280                 res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)d AND archive=%(archive)d",
281                         {'location': location, 'archive': archive_id, 'component': component_id}, cachename='location')
282         else:
283             res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d",
284                     {'location': location, 'archive': archive_id, 'component': ''}, cachename='location')
285
286         return res
287
288     def get_source_id(self, source, version):
289         """
290         Returns database id for the combination of C{source} and C{version}
291           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
292           - B{version}
293         Results are kept in a cache during runtime to minimize database queries.
294
295         @type source: string
296         @param source: source package name
297
298         @type version: string
299         @param version: the source version
300
301         @rtype: int
302         @return: the database id for the source
303
304         """
305         return self.__get_single_id("SELECT id FROM source s WHERE s.source=%(source)s AND s.version=%(version)s",
306                                  {'source': source, 'version': version}, cachename='source')
307
308     def get_suite_version(self, source, suite):
309         """
310         Returns database id for a combination of C{source} and C{suite}.
311
312           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
313           - B{suite} - a suite name, eg. I{unstable}
314
315         Results are kept in a cache during runtime to minimize database queries.
316
317         @type source: string
318         @param source: source package name
319
320         @type suite: string
321         @param suite: the suite name
322
323         @rtype: string
324         @return: the version for I{source} in I{suite}
325
326         """
327         return self.__get_single_id("""
328         SELECT s.version FROM source s, suite su, src_associations sa
329         WHERE sa.source=s.id
330           AND sa.suite=su.id
331           AND su.suite_name=%(suite)s
332           AND s.source=%(source)""", {'suite': suite, 'source': source}, cachename='suite_version')
333
334
335     def get_or_set_contents_file_id(self, filename):
336         """
337         Returns database id for given filename.
338
339         Results are kept in a cache during runtime to minimize database queries.
340         If no matching file is found, a row is inserted.
341
342         @type filename: string
343         @param filename: The filename
344
345         @rtype: int
346         @return: the database id for the given component
347         """
348         values={'value': filename}
349         query = "SELECT id FROM content_file_names WHERE file = %(value)s"
350         id = self.__get_single_id(query, values, cachename='content_file_names')
351         if not id:
352             c = self.db_con.cursor()
353             c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id",
354                        values )
355
356             id = c.fetchone()[0]
357             self.caches['content_file_names'].SetValue(values, id)
358
359         return id
360
361     def get_or_set_contents_path_id(self, path):
362         """
363         Returns database id for given path.
364
365         Results are kept in a cache during runtime to minimize database queries.
366         If no matching file is found, a row is inserted.
367
368         @type path: string
369         @param path: The filename
370
371         @rtype: int
372         @return: the database id for the given component
373         """
374         values={'value': path}
375         query = "SELECT id FROM content_file_paths WHERE path = %(value)s"
376         id = self.__get_single_id(query, values, cachename='content_path_names')
377         if not id:
378             c = self.db_con.cursor()
379             c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id",
380                        values )
381
382             id = c.fetchone()[0]
383             self.caches['content_path_names'].SetValue(values, id)
384
385         return id
386
387     def insert_content_paths(self, bin_id, fullpaths):
388         """
389         Make sure given path is associated with given binary id
390
391         @type bin_id: int
392         @param bin_id: the id of the binary
393         @type fullpath: string
394         @param fullpath: the path of the file being associated with the binary
395         """
396
397         c = self.db_con.cursor()
398
399         for fullpath in fullpaths:
400             c.execute( "BEGIN WORK" )
401             (path, file) = os.path.split(fullpath)
402
403             # Get the necessary IDs ...
404             file_id = self.get_or_set_contents_file_id(file)
405             path_id = self.get_or_set_contents_path_id(path)
406
407             # Determine if we're inserting a duplicate row
408
409             c.execute("SELECT 1 FROM content_associations WHERE binary_pkg = '%d' AND filepath = '%d' AND filename = '%d'" % (int(bin_id), path_id, file_id))
410             if not c.fetchone():
411                 # no, we are not, do the insert
412
413                 c.execute("INSERT INTO content_associations VALUES (DEFAULT, '%d', '%d', '%d')" % (bin_id, path_id, file_id))
414         c.execute( "COMMIT" )