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
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.
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.
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
27 ################################################################################
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"
34 ################################################################################
39 from Singleton import Singleton
40 from Config import Config
42 ################################################################################
45 def __init__(self, hashfunc=None):
47 self.hashfunc = hashfunc
49 self.hashfunc = lambda x: x['value']
53 def SetValue(self, keys, value):
54 self.data[self.hashfunc(keys)] = value
56 def GetValue(self, keys):
57 return self.data.get(self.hashfunc(keys))
59 ################################################################################
61 class DBConn(Singleton):
65 def __init__(self, *args, **kwargs):
66 super(DBConn, self).__init__(*args, **kwargs)
68 def _startup(self, *args, **kwargs):
72 ## Connection functions
73 def __createconn(self):
75 connstr = "dbname=%s" % cnf["DB::Name"]
77 connstr += " host=%s" % cnf["DB::Host"]
78 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
79 connstr += " port=%s" % cnf["DB::Port"]
81 self.db_con = psycopg2.connect(connstr)
86 except psycopg2.InterfaceError:
93 def __init_caches(self):
94 self.caches = {'suite': Cache(),
97 'override_type': Cache(),
98 'architecture': 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'])),
108 'maintainer': {}, # TODO
109 'fingerprint': {}, # TODO
112 'suite_version': Cache(lambda x: '%s_%s' % (x['source'], x['suite'])),
115 def clear_caches(self):
118 ## Functions to pass through to the database connector
120 return self.db_con.cursor()
123 return self.db_con.commit()
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)
133 c = self.db_con.cursor()
134 c.execute(query, values)
139 res = c.fetchone()[0]
141 if cachename is not None:
142 self.caches[cachename].SetValue(values, res)
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)
150 def get_suite_id(self, suite):
152 Returns database id for given C{suite}.
153 Results are kept in a cache during runtime to minimize database queries.
156 @param suite: The name of the suite
159 @return: the database id for the given suite
162 return self.__get_id('id', 'suite', 'suite_name', suite)
164 def get_section_id(self, section):
166 Returns database id for given C{section}.
167 Results are kept in a cache during runtime to minimize database queries.
169 @type section: string
170 @param section: The name of the section
173 @return: the database id for the given section
176 return self.__get_id('id', 'section', 'section', section)
178 def get_priority_id(self, priority):
180 Returns database id for given C{priority}.
181 Results are kept in a cache during runtime to minimize database queries.
183 @type priority: string
184 @param priority: The name of the priority
187 @return: the database id for the given priority
190 return self.__get_id('id', 'priority', 'priority', priority)
192 def get_override_type_id(self, override_type):
194 Returns database id for given override C{type}.
195 Results are kept in a cache during runtime to minimize database queries.
198 @param type: The name of the override type
201 @return: the database id for the given override type
204 return self.__get_id('id', 'override_type', 'override_type', override_type)
206 def get_architecture_id(self, architecture):
208 Returns database id for given C{architecture}.
209 Results are kept in a cache during runtime to minimize database queries.
211 @type architecture: string
212 @param architecture: The name of the override type
215 @return: the database id for the given architecture
218 return self.__get_id('id', 'architecture', 'arch_string', architecture)
220 def get_archive_id(self, archive):
222 returns database id for given c{archive}.
223 results are kept in a cache during runtime to minimize database queries.
225 @type archive: string
226 @param archive: the name of the override type
229 @return: the database id for the given archive
232 return self.__get_id('id', 'archive', 'lower(name)', archive)
234 def get_component_id(self, component):
236 Returns database id for given C{component}.
237 Results are kept in a cache during runtime to minimize database queries.
239 @type component: string
240 @param component: The name of the override type
243 @return: the database id for the given component
246 return self.__get_id('id', 'component', 'lower(name)', component)
248 def get_location_id(self, location, component, archive):
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.
256 @type location: string
257 @param location: the path of the location
260 @param component: the id of the component
263 @param archive: the id of the archive
266 @return: the database id for the location
270 archive_id = self.get_archive_id(archive)
278 component_id = self.get_component_id(component)
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')
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')
288 def get_source_id(self, source, version):
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}
293 Results are kept in a cache during runtime to minimize database queries.
296 @param source: source package name
298 @type version: string
299 @param version: the source version
302 @return: the database id for the source
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')
308 def get_suite_version(self, source, suite):
310 Returns database id for a combination of C{source} and C{suite}.
312 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
313 - B{suite} - a suite name, eg. I{unstable}
315 Results are kept in a cache during runtime to minimize database queries.
318 @param source: source package name
321 @param suite: the suite name
324 @return: the version for I{source} in I{suite}
327 return self.__get_single_id("""
328 SELECT s.version FROM source s, suite su, src_associations sa
331 AND su.suite_name=%(suite)s
332 AND s.source=%(source)""", {'suite': suite, 'source': source}, cachename='suite_version')
335 def get_or_set_contents_file_id(self, filename):
337 Returns database id for given filename.
339 Results are kept in a cache during runtime to minimize database queries.
340 If no matching file is found, a row is inserted.
342 @type filename: string
343 @param filename: The filename
346 @return: the database id for the given component
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')
352 c = self.db_con.cursor()
353 c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id",
357 self.caches['content_file_names'].SetValue(values, id)
361 def get_or_set_contents_path_id(self, path):
363 Returns database id for given path.
365 Results are kept in a cache during runtime to minimize database queries.
366 If no matching file is found, a row is inserted.
369 @param path: The filename
372 @return: the database id for the given component
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')
378 c = self.db_con.cursor()
379 c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id",
383 self.caches['content_path_names'].SetValue(values, id)
387 def insert_content_paths(self, bin_id, fullpaths):
389 Make sure given path is associated with given binary id
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
397 c = self.db_con.cursor()
399 for fullpath in fullpaths:
400 c.execute( "BEGIN WORK" )
401 (path, file) = os.path.split(fullpath)
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)
407 # Determine if we're inserting a duplicate row
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))
411 # no, we are not, do the insert
413 c.execute("INSERT INTO content_associations VALUES (DEFAULT, '%d', '%d', '%d')" % (bin_id, path_id, file_id))
414 c.execute( "COMMIT" )