]> git.decadent.org.uk Git - dak.git/blob - daklib/DBConn.py
Merge commit 'ftpmaster/master' into psycopg2
[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 @license: GNU General Public License version 2 or later
10 """
11
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
16
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 ################################################################################
27
28 # < mhy> I need a funny comment
29 # < sgran> two peanuts were walking down a dark street
30 # < sgran> one was a-salted
31 #  * mhy looks up the definition of "funny"
32
33 ################################################################################
34
35 import psycopg2
36 from psycopg2.extras import DictCursor
37
38 from Singleton import Singleton
39 from Config import Config
40
41 ################################################################################
42
43 class Cache(object):
44     def __init__(self, hashfunc=None):
45         if hashfunc:
46             self.hashfunc = hashfunc
47         else:
48             self.hashfunc = lambda x: x['value']
49
50         self.data = {}
51
52     def SetValue(self, keys, value):
53         self.data[self.hashfunc(keys)] = value
54
55     def GetValue(self, keys):
56         return self.data.get(self.hashfunc(keys))
57
58 ################################################################################
59
60 class DBConn(Singleton):
61     """
62     database module init.
63     """
64     def __init__(self, *args, **kwargs):
65         super(DBConn, self).__init__(*args, **kwargs)
66
67     def _startup(self, *args, **kwargs):
68         self.__createconn()
69         self.__init_caches()
70
71     ## Connection functions
72     def __createconn(self):
73         connstr = Config().GetDBConnString()
74         self.db_con = psycopg2.connect(connstr)
75
76     def reconnect(self):
77         try:
78             self.db_con.close()
79         except psycopg2.InterfaceError:
80             pass
81
82         self.db_con = None
83         self.__createconn()
84
85     ## Cache functions
86     def __init_caches(self):
87         self.caches = {'suite':         Cache(),
88                        'section':       Cache(),
89                        'priority':      Cache(),
90                        'override_type': Cache(),
91                        'architecture':  Cache(),
92                        'archive':       Cache(),
93                        'component':     Cache(),
94                        'location':      Cache(lambda x: '%s_%s_%s' % (x['location'], x['component'], x['location'])),
95                        'maintainer':    {}, # TODO
96                        'keyring':       {}, # TODO
97                        'source':        Cache(lambda x: '%s_%s_' % (x['source'], x['version'])),
98                        'files':         {}, # TODO
99                        'maintainer':    {}, # TODO
100                        'fingerprint':   {}, # TODO
101                        'queue':         {}, # TODO
102                        'uid':           {}, # TODO
103                        'suite_version': Cache(lambda x: '%s_%s' % (x['source'], x['suite'])),
104                       }
105
106     def clear_caches(self):
107         self.__init_caches()
108
109     ## Functions to pass through to the database connector
110     def cursor(self):
111         return self.db_con.cursor()
112
113     def commit(self):
114         return self.db_con.commit()
115
116     ## Get functions
117     def __get_single_id(self, query, values, cachename=None):
118         # This is a bit of a hack but it's an internal function only
119         if cachename is not None:
120             res = self.caches[cachename].GetValue(values)
121             if res:
122                 return res
123
124         c = self.db_con.cursor()
125         c.execute(query, values)
126
127         if c.rowcount != 1:
128             return None
129
130         res = c.fetchone()[0]
131
132         if cachename is not None:
133             self.caches[cachename].SetValue(values, res)
134
135         return res
136
137     def __get_id(self, retfield, table, qfield, value):
138         query = "SELECT %s FROM %s WHERE %s = %%(value)s" % (retfield, table, qfield)
139         return self.__get_single_id(query, {'value': value}, cachename=table)
140
141     def get_suite_id(self, suite):
142         """
143         Returns database id for given C{suite}.
144         Results are kept in a cache during runtime to minimize database queries.
145
146         @type suite: string
147         @param suite: The name of the suite
148
149         @rtype: int
150         @return: the database id for the given suite
151
152         """
153         return self.__get_id('id', 'suite', 'suite_name', suite)
154
155     def get_section_id(self, section):
156         """
157         Returns database id for given C{section}.
158         Results are kept in a cache during runtime to minimize database queries.
159
160         @type section: string
161         @param section: The name of the section
162
163         @rtype: int
164         @return: the database id for the given section
165
166         """
167         return self.__get_id('id', 'section', 'section', section)
168
169     def get_priority_id(self, priority):
170         """
171         Returns database id for given C{priority}.
172         Results are kept in a cache during runtime to minimize database queries.
173
174         @type priority: string
175         @param priority: The name of the priority
176
177         @rtype: int
178         @return: the database id for the given priority
179
180         """
181         return self.__get_id('id', 'priority', 'priority', priority)
182
183     def get_override_type_id(self, override_type):
184         """
185         Returns database id for given override C{type}.
186         Results are kept in a cache during runtime to minimize database queries.
187
188         @type type: string
189         @param type: The name of the override type
190
191         @rtype: int
192         @return: the database id for the given override type
193
194         """
195         return self.__get_id('id', 'override_type', 'override_type', override_type)
196
197     def get_architecture_id(self, architecture):
198         """
199         Returns database id for given C{architecture}.
200         Results are kept in a cache during runtime to minimize database queries.
201
202         @type architecture: string
203         @param architecture: The name of the override type
204
205         @rtype: int
206         @return: the database id for the given architecture
207
208         """
209         return self.__get_id('id', 'architecture', 'arch_string', architecture)
210
211     def get_archive_id(self, archive):
212         """
213         returns database id for given c{archive}.
214         results are kept in a cache during runtime to minimize database queries.
215
216         @type archive: string
217         @param archive: the name of the override type
218
219         @rtype: int
220         @return: the database id for the given archive
221
222         """
223         return self.__get_id('id', 'archive', 'lower(name)', archive)
224
225     def get_component_id(self, component):
226         """
227         Returns database id for given C{component}.
228         Results are kept in a cache during runtime to minimize database queries.
229
230         @type component: string
231         @param component: The name of the override type
232
233         @rtype: int
234         @return: the database id for the given component
235
236         """
237         return self.__get_id('id', 'component', 'lower(name)', component)
238
239     def get_location_id(self, location, component, archive):
240         """
241         Returns database id for the location behind the given combination of
242           - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
243           - B{component} - the id of the component as returned by L{get_component_id}
244           - B{archive} - the id of the archive as returned by L{get_archive_id}
245         Results are kept in a cache during runtime to minimize database queries.
246
247         @type location: string
248         @param location: the path of the location
249
250         @type component: int
251         @param component: the id of the component
252
253         @type archive: int
254         @param archive: the id of the archive
255
256         @rtype: int
257         @return: the database id for the location
258
259         """
260
261         archive_id = self.get_archive_id(archive)
262
263         if not archive_id:
264             return None
265
266         res = None
267
268         if component:
269             component_id = self.get_component_id(component)
270             if component_id:
271                 res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)d AND archive=%(archive)d",
272                         {'location': location, 'archive': archive_id, 'component': component_id}, cachename='location')
273         else:
274             res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d",
275                     {'location': location, 'archive': archive_id, 'component': ''}, cachename='location')
276
277         return res
278
279     def get_source_id(self, source, version):
280         """
281         Returns database id for the combination of C{source} and C{version}
282           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
283           - B{version}
284         Results are kept in a cache during runtime to minimize database queries.
285
286         @type source: string
287         @param source: source package name
288
289         @type version: string
290         @param version: the source version
291
292         @rtype: int
293         @return: the database id for the source
294
295         """
296         return self.__get_single_id("SELECT id FROM source s WHERE s.source=%(source)s AND s.version=%(version)s",
297                                  {'source': source, 'version': version}, cachename='source')
298
299     def get_suite_version(self, source, suite):
300         """
301         Returns database id for a combination of C{source} and C{suite}.
302
303           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
304           - B{suite} - a suite name, eg. I{unstable}
305
306         Results are kept in a cache during runtime to minimize database queries.
307
308         @type source: string
309         @param source: source package name
310
311         @type suite: string
312         @param suite: the suite name
313
314         @rtype: string
315         @return: the version for I{source} in I{suite}
316
317         """
318         return self.__get_single_id("""
319         SELECT s.version FROM source s, suite su, src_associations sa
320         WHERE sa.source=s.id
321           AND sa.suite=su.id
322           AND su.suite_name=%(suite)s
323           AND s.source=%(source)""", {'suite': suite, 'source': source}, cachename='suite_version')
324