]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
ContentAssociations -> ContentAssociation
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/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 import traceback
39
40 from sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
42
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
45
46 from singleton import Singleton
47
48 ################################################################################
49
50 class Architecture(object):
51     def __init__(self, *args, **kwargs):
52         pass
53
54     def __repr__(self):
55         return '<Architecture %s>' % self.arch_string
56
57 def get_architecture(architecture, session=None):
58     """
59     Returns database id for given C{architecture}.
60
61     @type architecture: string
62     @param architecture: The name of the architecture
63
64     @type session: Session
65     @param session: Optional SQLA session object (a temporary one will be
66     generated if not supplied)
67
68     @rtype: Architecture
69     @return: Architecture object for the given arch (None if not present)
70
71     """
72     if session is None:
73         session = DBConn().session()
74     q = session.query(Architecture).filter_by(arch_string=architecture)
75     if q.count() == 0:
76         return None
77     return q.one()
78
79 def get_architecture_suites(architecture, session=None):
80     """
81     Returns list of Suite objects for given C{architecture} name
82
83     @type source: str
84     @param source: Architecture name to search for
85
86     @type session: Session
87     @param session: Optional SQL session object (a temporary one will be
88     generated if not supplied)
89
90     @rtype: list
91     @return: list of Suite objects for the given name (may be empty)
92     """
93
94     if session is None:
95         session = DBConn().session()
96
97     q = session.query(Suite)
98     q = q.join(SuiteArchitecture)
99     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
100     return q.all()
101
102 class Archive(object):
103     def __init__(self, *args, **kwargs):
104         pass
105
106     def __repr__(self):
107         return '<Archive %s>' % self.name
108
109 def get_archive(archive, session=None):
110     """
111     returns database id for given c{archive}.
112
113     @type archive: string
114     @param archive: the name of the arhive
115
116     @type session: Session
117     @param session: Optional SQLA session object (a temporary one will be
118     generated if not supplied)
119
120     @rtype: Archive
121     @return: Archive object for the given name (None if not present)
122
123     """
124     archive = archive.lower()
125     if session is None:
126         session = DBConn().session()
127     q = session.query(Archive).filter_by(archive_name=archive)
128     if q.count() == 0:
129         return None
130     return q.one()
131
132
133 class BinAssociation(object):
134     def __init__(self, *args, **kwargs):
135         pass
136
137     def __repr__(self):
138         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
139
140 class Binary(object):
141     def __init__(self, *args, **kwargs):
142         pass
143
144     def __repr__(self):
145         return '<Binary %s (%s, %s)>' % (self.package, self.version, self.architecture)
146
147 def get_binary_from_id(id, session=None):
148     """
149     Returns Binary object for given C{id}
150
151     @type id: int
152     @param id: Id of the required binary
153
154     @type session: Session
155     @param session: Optional SQLA session object (a temporary one will be
156     generated if not supplied)
157
158     @rtype: Binary
159     @return: Binary object for the given binary (None if not present)
160     """
161     if session is None:
162         session = DBConn().session()
163     q = session.query(Binary).filter_by(binary_id=id)
164     if q.count() == 0:
165         return None
166     return q.one()
167
168 def get_binaries_from_name(package, session=None):
169     """
170     Returns list of Binary objects for given C{package} name
171
172     @type package: str
173     @param package: Binary package name to search for
174
175     @type session: Session
176     @param session: Optional SQL session object (a temporary one will be
177     generated if not supplied)
178
179     @rtype: list
180     @return: list of Binary objects for the given name (may be empty)
181     """
182     if session is None:
183         session = DBConn().session()
184     return session.query(Binary).filter_by(package=package).all()
185
186 class Component(object):
187     def __init__(self, *args, **kwargs):
188         pass
189
190     def __repr__(self):
191         return '<Component %s>' % self.component_name
192
193 def get_component(component, session=None):
194     """
195     Returns database id for given C{component}.
196
197     @type component: string
198     @param component: The name of the override type
199
200     @rtype: int
201     @return: the database id for the given component
202
203     """
204     component = component.lower()
205     if session is None:
206         session = DBConn().session()
207     q = session.query(Component).filter_by(component_name=component)
208     if q.count() == 0:
209         return None
210     return q.one()
211
212 class DBConfig(object):
213     def __init__(self, *args, **kwargs):
214         pass
215
216     def __repr__(self):
217         return '<DBConfig %s>' % self.name
218
219 class ContentFilename(object):
220     def __init__(self, *args, **kwargs):
221         pass
222
223     def __repr__(self):
224         return '<ContentFilename %s>' % self.filename
225
226 class ContentFilepath(object):
227     def __init__(self, *args, **kwargs):
228         pass
229
230     def __repr__(self):
231         return '<ContentFilepath %s>' % self.filepath
232
233 class ContentAssociation(object):
234     def __init__(self, *args, **kwargs):
235         pass
236
237     def __repr__(self):
238         return '<ContentAssociation %s>' % self.ca_id
239
240 class DSCFile(object):
241     def __init__(self, *args, **kwargs):
242         pass
243
244     def __repr__(self):
245         return '<DSCFile %s>' % self.dscfile_id
246
247 class PoolFile(object):
248     def __init__(self, *args, **kwargs):
249         pass
250
251     def __repr__(self):
252         return '<PoolFile %s>' % self.filename
253
254 class Fingerprint(object):
255     def __init__(self, *args, **kwargs):
256         pass
257
258     def __repr__(self):
259         return '<Fingerprint %s>' % self.fingerprint
260
261 class Keyring(object):
262     def __init__(self, *args, **kwargs):
263         pass
264
265     def __repr__(self):
266         return '<Keyring %s>' % self.keyring_name
267
268 class Location(object):
269     def __init__(self, *args, **kwargs):
270         pass
271
272     def __repr__(self):
273         return '<Location %s (%s)>' % (self.path, self.location_id)
274
275 class Maintainer(object):
276     def __init__(self, *args, **kwargs):
277         pass
278
279     def __repr__(self):
280         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
281
282 class Override(object):
283     def __init__(self, *args, **kwargs):
284         pass
285
286     def __repr__(self):
287         return '<Override %s (%s)>' % (self.package, self.suite_id)
288
289 class OverrideType(object):
290     def __init__(self, *args, **kwargs):
291         pass
292
293     def __repr__(self):
294         return '<OverrideType %s>' % self.overridetype
295
296 def get_override_type(override_type, session=None):
297     """
298     Returns OverrideType object for given C{override type}.
299
300     @type override_type: string
301     @param override_type: The name of the override type
302
303     @type session: Session
304     @param session: Optional SQLA session object (a temporary one will be
305     generated if not supplied)
306
307     @rtype: int
308     @return: the database id for the given override type
309
310     """
311     if session is None:
312         session = DBConn().session()
313     q = session.query(Priority).filter_by(priority=priority)
314     if q.count() == 0:
315         return None
316     return q.one()
317
318 class PendingContentAssociation(object):
319     def __init__(self, *args, **kwargs):
320         pass
321
322     def __repr__(self):
323         return '<PendingContentAssociation %s>' % self.pca_id
324
325 class Priority(object):
326     def __init__(self, *args, **kwargs):
327         pass
328
329     def __repr__(self):
330         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
331
332 def get_priority(priority, session=None):
333     """
334     Returns Priority object for given C{priority name}.
335
336     @type priority: string
337     @param priority: The name of the priority
338
339     @type session: Session
340     @param session: Optional SQLA session object (a temporary one will be
341     generated if not supplied)
342
343     @rtype: Priority
344     @return: Priority object for the given priority
345
346     """
347     if session is None:
348         session = DBConn().session()
349     q = session.query(Priority).filter_by(priority=priority)
350     if q.count() == 0:
351         return None
352     return q.one()
353
354 class Queue(object):
355     def __init__(self, *args, **kwargs):
356         pass
357
358     def __repr__(self):
359         return '<Queue %s>' % self.queue_name
360
361 class QueueBuild(object):
362     def __init__(self, *args, **kwargs):
363         pass
364
365     def __repr__(self):
366         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
367
368 class Section(object):
369     def __init__(self, *args, **kwargs):
370         pass
371
372     def __repr__(self):
373         return '<Section %s>' % self.section
374
375 def get_section(section, session=None):
376     """
377     Returns Section object for given C{section name}.
378
379     @type section: string
380     @param section: The name of the section
381
382     @type session: Session
383     @param session: Optional SQLA session object (a temporary one will be
384     generated if not supplied)
385
386     @rtype: Section
387     @return: Section object for the given section name
388
389     """
390     if session is None:
391         session = DBConn().session()
392     q = session.query(Section).filter_by(section=section)
393     if q.count() == 0:
394         return None
395     return q.one()
396
397 class Source(object):
398     def __init__(self, *args, **kwargs):
399         pass
400
401     def __repr__(self):
402         return '<Source %s (%s)>' % (self.source, self.version)
403
404 def get_sources_from_name(source, session=None):
405     """
406     Returns list of Source objects for given C{source} name
407
408     @type source: str
409     @param source: Source package name to search for
410
411     @type session: Session
412     @param session: Optional SQL session object (a temporary one will be
413     generated if not supplied)
414
415     @rtype: list
416     @return: list of Source objects for the given name (may be empty)
417     """
418     if session is None:
419         session = DBConn().session()
420     return session.query(Source).filter_by(source=source).all()
421
422 def get_source_in_suite(source, suite, session=None):
423     """
424     Returns list of Source objects for a combination of C{source} and C{suite}.
425
426       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
427       - B{suite} - a suite name, eg. I{unstable}
428
429     @type source: string
430     @param source: source package name
431
432     @type suite: string
433     @param suite: the suite name
434
435     @rtype: string
436     @return: the version for I{source} in I{suite}
437
438     """
439     if session is None:
440         session = DBConn().session()
441     q = session.query(SrcAssociation)
442     q = q.join('source').filter_by(source=source)
443     q = q.join('suite').filter_by(suite_name=suite)
444     if q.count() == 0:
445         return None
446     # ???: Maybe we should just return the SrcAssociation object instead
447     return q.one().source
448
449 class SrcAssociation(object):
450     def __init__(self, *args, **kwargs):
451         pass
452
453     def __repr__(self):
454         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
455
456 class SrcUploader(object):
457     def __init__(self, *args, **kwargs):
458         pass
459
460     def __repr__(self):
461         return '<SrcUploader %s>' % self.uploader_id
462
463 class Suite(object):
464     def __init__(self, *args, **kwargs):
465         pass
466
467     def __repr__(self):
468         return '<Suite %s>' % self.suite_name
469
470 def get_suite_architecture(suite, architecture, session=None):
471     """
472     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
473     doesn't exist
474
475     @type suite: str
476     @param suite: Suite name to search for
477
478     @type architecture: str
479     @param architecture: Architecture name to search for
480
481     @type session: Session
482     @param session: Optional SQL session object (a temporary one will be
483     generated if not supplied)
484
485     @rtype: SuiteArchitecture
486     @return: the SuiteArchitecture object or None
487     """
488
489     if session is None:
490         session = DBConn().session()
491
492     q = session.query(SuiteArchitecture)
493     q = q.join(Architecture).filter_by(arch_string=architecture)
494     q = q.join(Suite).filter_by(suite_name=suite)
495     if q.count() == 0:
496         return None
497     return q.one()
498
499
500 def get_suite(suite, session=None):
501     """
502     Returns Suite object for given C{suite name}.
503
504     @type suite: string
505     @param suite: The name of the suite
506
507     @type session: Session
508     @param session: Optional SQLA session object (a temporary one will be
509     generated if not supplied)
510
511     @rtype: Suite
512     @return: Suite object for the requested suite name (None if not presenT)
513
514     """
515     if session is None:
516         session = DBConn().session()
517     q = session.query(Suite).filter_by(suite_name=suite)
518     if q.count() == 0:
519         return None
520     return q.one()
521
522 class SuiteArchitecture(object):
523     def __init__(self, *args, **kwargs):
524         pass
525
526     def __repr__(self):
527         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
528
529 def get_suite_architectures(suite, session=None):
530     """
531     Returns list of Architecture objects for given C{suite} name
532
533     @type source: str
534     @param source: Suite name to search for
535
536     @type session: Session
537     @param session: Optional SQL session object (a temporary one will be
538     generated if not supplied)
539
540     @rtype: list
541     @return: list of Architecture objects for the given name (may be empty)
542     """
543
544     if session is None:
545         session = DBConn().session()
546
547     q = session.query(Architecture)
548     q = q.join(SuiteArchitecture)
549     q = q.join(Suite).filter_by(suite_name=suite).order_by('arch_string')
550     return q.all()
551
552
553 class Uid(object):
554     def __init__(self, *args, **kwargs):
555         pass
556
557     def __repr__(self):
558         return '<Uid %s (%s)>' % (self.uid, self.name)
559
560 ################################################################################
561
562 class DBConn(Singleton):
563     """
564     database module init.
565     """
566     def __init__(self, *args, **kwargs):
567         super(DBConn, self).__init__(*args, **kwargs)
568
569     def _startup(self, *args, **kwargs):
570         self.debug = False
571         if kwargs.has_key('debug'):
572             self.debug = True
573         self.__createconn()
574
575     def __setuptables(self):
576         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
577         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
578         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
579         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
580         self.tbl_component = Table('component', self.db_meta, autoload=True)
581         self.tbl_config = Table('config', self.db_meta, autoload=True)
582         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
583         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
584         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
585         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
586         self.tbl_files = Table('files', self.db_meta, autoload=True)
587         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
588         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
589         self.tbl_location = Table('location', self.db_meta, autoload=True)
590         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
591         self.tbl_override = Table('override', self.db_meta, autoload=True)
592         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
593         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
594         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
595         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
596         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
597         self.tbl_section = Table('section', self.db_meta, autoload=True)
598         self.tbl_source = Table('source', self.db_meta, autoload=True)
599         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
600         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
601         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
602         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
603         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
604
605     def __setupmappers(self):
606         mapper(Architecture, self.tbl_architecture,
607                properties = dict(arch_id = self.tbl_architecture.c.id))
608
609         mapper(Archive, self.tbl_archive,
610                properties = dict(archive_id = self.tbl_archive.c.id,
611                                  archive_name = self.tbl_archive.c.name))
612
613         mapper(BinAssociation, self.tbl_bin_associations,
614                properties = dict(ba_id = self.tbl_bin_associations.c.id,
615                                  suite_id = self.tbl_bin_associations.c.suite,
616                                  suite = relation(Suite),
617                                  binary_id = self.tbl_bin_associations.c.bin,
618                                  binary = relation(Binary)))
619
620         mapper(Binary, self.tbl_binaries,
621                properties = dict(binary_id = self.tbl_binaries.c.id,
622                                  package = self.tbl_binaries.c.package,
623                                  version = self.tbl_binaries.c.version,
624                                  maintainer_id = self.tbl_binaries.c.maintainer,
625                                  maintainer = relation(Maintainer),
626                                  source_id = self.tbl_binaries.c.source,
627                                  source = relation(Source),
628                                  arch_id = self.tbl_binaries.c.architecture,
629                                  architecture = relation(Architecture),
630                                  poolfile_id = self.tbl_binaries.c.file,
631                                  poolfile = relation(PoolFile),
632                                  binarytype = self.tbl_binaries.c.type,
633                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
634                                  fingerprint = relation(Fingerprint),
635                                  install_date = self.tbl_binaries.c.install_date,
636                                  binassociations = relation(BinAssociation,
637                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
638
639         mapper(Component, self.tbl_component,
640                properties = dict(component_id = self.tbl_component.c.id,
641                                  component_name = self.tbl_component.c.name))
642
643         mapper(DBConfig, self.tbl_config,
644                properties = dict(config_id = self.tbl_config.c.id))
645
646         mapper(ContentAssociation, self.tbl_content_associations,
647                properties = dict(ca_id = self.tbl_content_associations.c.id,
648                                  filename_id = self.tbl_content_associations.c.filename,
649                                  filename    = relation(ContentFilename),
650                                  filepath_id = self.tbl_content_associations.c.filepath,
651                                  filepath    = relation(ContentFilepath),
652                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
653                                  binary      = relation(Binary)))
654
655
656         mapper(ContentFilename, self.tbl_content_file_names,
657                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
658                                  filename = self.tbl_content_file_names.c.file))
659
660         mapper(ContentFilepath, self.tbl_content_file_paths,
661                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
662                                  filepath = self.tbl_content_file_paths.c.path))
663
664         mapper(DSCFile, self.tbl_dsc_files,
665                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
666                                  source_id = self.tbl_dsc_files.c.source,
667                                  source = relation(Source),
668                                  poolfile_id = self.tbl_dsc_files.c.file,
669                                  poolfile = relation(PoolFile)))
670
671         mapper(PoolFile, self.tbl_files,
672                properties = dict(file_id = self.tbl_files.c.id,
673                                  filesize = self.tbl_files.c.size,
674                                  location_id = self.tbl_files.c.location,
675                                  location = relation(Location)))
676
677         mapper(Fingerprint, self.tbl_fingerprint,
678                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
679                                  uid_id = self.tbl_fingerprint.c.uid,
680                                  uid = relation(Uid),
681                                  keyring_id = self.tbl_fingerprint.c.keyring,
682                                  keyring = relation(Keyring)))
683
684         mapper(Keyring, self.tbl_keyrings,
685                properties = dict(keyring_name = self.tbl_keyrings.c.name,
686                                  keyring_id = self.tbl_keyrings.c.id))
687
688         mapper(Location, self.tbl_location,
689                properties = dict(location_id = self.tbl_location.c.id,
690                                  component_id = self.tbl_location.c.component,
691                                  component = relation(Component),
692                                  archive_id = self.tbl_location.c.archive,
693                                  archive = relation(Archive),
694                                  archive_type = self.tbl_location.c.type))
695
696         mapper(Maintainer, self.tbl_maintainer,
697                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
698
699         mapper(Override, self.tbl_override,
700                properties = dict(suite_id = self.tbl_override.c.suite,
701                                  suite = relation(Suite),
702                                  component_id = self.tbl_override.c.component,
703                                  component = relation(Component),
704                                  priority_id = self.tbl_override.c.priority,
705                                  priority = relation(Priority),
706                                  section_id = self.tbl_override.c.section,
707                                  section = relation(Section),
708                                  overridetype_id = self.tbl_override.c.type,
709                                  overridetype = relation(OverrideType)))
710
711         mapper(OverrideType, self.tbl_override_type,
712                properties = dict(overridetype = self.tbl_override_type.c.type,
713                                  overridetype_id = self.tbl_override_type.c.id))
714
715         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
716                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
717                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
718                                  filepath = relation(ContentFilepath),
719                                  filename_id = self.tbl_pending_content_associations.c.filename,
720                                  filename = relation(ContentFilename)))
721
722         mapper(Priority, self.tbl_priority,
723                properties = dict(priority_id = self.tbl_priority.c.id))
724
725         mapper(Queue, self.tbl_queue,
726                properties = dict(queue_id = self.tbl_queue.c.id))
727
728         mapper(QueueBuild, self.tbl_queue_build,
729                properties = dict(suite_id = self.tbl_queue_build.c.suite,
730                                  queue_id = self.tbl_queue_build.c.queue,
731                                  queue = relation(Queue)))
732
733         mapper(Section, self.tbl_section,
734                properties = dict(section_id = self.tbl_section.c.id))
735
736         mapper(Source, self.tbl_source,
737                properties = dict(source_id = self.tbl_source.c.id,
738                                  version = self.tbl_source.c.version,
739                                  maintainer_id = self.tbl_source.c.maintainer,
740                                  maintainer = relation(Maintainer,
741                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
742                                  poolfile_id = self.tbl_source.c.file,
743                                  poolfile = relation(PoolFile),
744                                  fingerprint_id = self.tbl_source.c.sig_fpr,
745                                  fingerprint = relation(Fingerprint),
746                                  changedby_id = self.tbl_source.c.changedby,
747                                  changedby = relation(Maintainer,
748                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
749                                  srcfiles = relation(DSCFile,
750                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
751                                  srcassociations = relation(SrcAssociation,
752                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
753
754         mapper(SrcAssociation, self.tbl_src_associations,
755                properties = dict(sa_id = self.tbl_src_associations.c.id,
756                                  suite_id = self.tbl_src_associations.c.suite,
757                                  suite = relation(Suite),
758                                  source_id = self.tbl_src_associations.c.source,
759                                  source = relation(Source)))
760
761         mapper(SrcUploader, self.tbl_src_uploaders,
762                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
763                                  source_id = self.tbl_src_uploaders.c.source,
764                                  source = relation(Source,
765                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
766                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
767                                  maintainer = relation(Maintainer,
768                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
769
770         mapper(Suite, self.tbl_suite,
771                properties = dict(suite_id = self.tbl_suite.c.id))
772
773         mapper(SuiteArchitecture, self.tbl_suite_architectures,
774                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
775                                  suite = relation(Suite),
776                                  arch_id = self.tbl_suite_architectures.c.architecture,
777                                  architecture = relation(Architecture)))
778
779         mapper(Uid, self.tbl_uid,
780                properties = dict(uid_id = self.tbl_uid.c.id))
781
782     ## Connection functions
783     def __createconn(self):
784         from config import Config
785         cnf = Config()
786         if cnf["DB::Host"]:
787             # TCP/IP
788             connstr = "postgres://%s" % cnf["DB::Host"]
789             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
790                 connstr += ":%s" % cnf["DB::Port"]
791             connstr += "/%s" % cnf["DB::Name"]
792         else:
793             # Unix Socket
794             connstr = "postgres:///%s" % cnf["DB::Name"]
795             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
796                 connstr += "?port=%s" % cnf["DB::Port"]
797
798         self.db_pg   = create_engine(connstr, echo=self.debug)
799         self.db_meta = MetaData()
800         self.db_meta.bind = self.db_pg
801         self.db_smaker = sessionmaker(bind=self.db_pg,
802                                       autoflush=True,
803                                       autocommit=True)
804
805         self.__setuptables()
806         self.__setupmappers()
807
808     def session(self):
809         return self.db_smaker()
810
811     def prepare(self,name,statement):
812         if not self.prepared_statements.has_key(name):
813             pgc.execute(statement)
814             self.prepared_statements[name] = statement
815
816
817     def get_location_id(self, location, component, archive):
818         """
819         Returns database id for the location behind the given combination of
820           - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
821           - B{component} - the id of the component as returned by L{get_component_id}
822           - B{archive} - the id of the archive as returned by L{get_archive_id}
823         Results are kept in a cache during runtime to minimize database queries.
824
825         @type location: string
826         @param location: the path of the location
827
828         @type component: int
829         @param component: the id of the component
830
831         @type archive: int
832         @param archive: the id of the archive
833
834         @rtype: int
835         @return: the database id for the location
836
837         """
838
839         archive_id = self.get_archive_id(archive)
840
841         if not archive_id:
842             return None
843
844         res = None
845
846         if component:
847             component_id = self.get_component_id(component)
848             if component_id:
849                 res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)s AND archive=%(archive)s",
850                         {'location': location,
851                          'archive': int(archive_id),
852                          'component': component_id}, cachename='location')
853         else:
854             res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d",
855                     {'location': location, 'archive': archive_id, 'component': ''}, cachename='location')
856
857         return res
858
859
860
861 def get_files_id (self, filename, size, md5sum, location_id):
862     """
863     Returns -1, -2 or the file_id for filename, if its C{size} and C{md5sum} match an
864     existing copy.
865
866     The database is queried using the C{filename} and C{location_id}. If a file does exist
867     at that location, the existing size and md5sum are checked against the provided
868     parameters. A size or checksum mismatch returns -2. If more than one entry is
869     found within the database, a -1 is returned, no result returns None, otherwise
870     the file id.
871
872     @type filename: string
873     @param filename: the filename of the file to check against the DB
874
875     @type size: int
876     @param size: the size of the file to check against the DB
877
878     @type md5sum: string
879     @param md5sum: the md5sum of the file to check against the DB
880
881     @type location_id: int
882     @param location_id: the id of the location as returned by L{get_location_id}
883
884     @rtype: int / None
885     @return: Various return values are possible:
886                - -2: size/checksum error
887                - -1: more than one file found in database
888                - None: no file found in database
889                - int: file id
890
891     """
892     values = {'filename' : filename,
893               'location' : location_id}
894
895     if not res:
896         query = """SELECT id, size, md5sum
897                    FROM files
898                    WHERE filename = %(filename)s AND location = %(location)s"""
899
900         cursor = self.db_con.cursor()
901         cursor.execute( query, values )
902
903         if cursor.rowcount == 0:
904             res = None
905
906         elif cursor.rowcount != 1:
907             res = -1
908
909         else:
910             row = cursor.fetchone()
911
912             if row[1] != int(size) or row[2] != md5sum:
913                 res =  -2
914
915             else:
916                 res = row[0]
917
918     return res
919
920
921 def get_or_set_contents_file_id(self, filename):
922     """
923     Returns database id for given filename.
924
925     If no matching file is found, a row is inserted.
926
927     @type filename: string
928     @param filename: The filename
929
930     @rtype: int
931     @return: the database id for the given component
932     """
933     try:
934         values={'value': filename}
935         query = "SELECT id FROM content_file_names WHERE file = %(value)s"
936         if not id:
937             c = self.db_con.cursor()
938             c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id",
939                        values )
940
941             id = c.fetchone()[0]
942
943         return id
944     except:
945         traceback.print_exc()
946         raise
947
948 def get_or_set_contents_path_id(self, path):
949     """
950     Returns database id for given path.
951
952     If no matching file is found, a row is inserted.
953
954     @type path: string
955     @param path: The filename
956
957     @rtype: int
958     @return: the database id for the given component
959     """
960     try:
961         values={'value': path}
962         query = "SELECT id FROM content_file_paths WHERE path = %(value)s"
963         if not id:
964             c = self.db_con.cursor()
965             c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id",
966                        values )
967
968             id = c.fetchone()[0]
969
970         return id
971     except:
972         traceback.print_exc()
973         raise
974
975
976 def insert_content_paths(self, bin_id, fullpaths):
977     """
978     Make sure given path is associated with given binary id
979
980     @type bin_id: int
981     @param bin_id: the id of the binary
982     @type fullpaths: list
983     @param fullpaths: the list of paths of the file being associated with the binary
984
985     @return: True upon success
986     """
987
988     c = self.db_con.cursor()
989
990     c.execute("BEGIN WORK")
991     try:
992
993         for fullpath in fullpaths:
994             (path, file) = os.path.split(fullpath)
995
996             # Get the necessary IDs ...
997             file_id = self.get_or_set_contents_file_id(file)
998             path_id = self.get_or_set_contents_path_id(path)
999
1000             c.execute("""INSERT INTO content_associations
1001                            (binary_pkg, filepath, filename)
1002                        VALUES ( '%d', '%d', '%d')""" % (bin_id, path_id, file_id) )
1003
1004         c.execute("COMMIT")
1005         return True
1006     except:
1007         traceback.print_exc()
1008         c.execute("ROLLBACK")
1009         return False
1010
1011 def insert_pending_content_paths(self, package, fullpaths):
1012     """
1013     Make sure given paths are temporarily associated with given
1014     package
1015
1016     @type package: dict
1017     @param package: the package to associate with should have been read in from the binary control file
1018     @type fullpaths: list
1019     @param fullpaths: the list of paths of the file being associated with the binary
1020
1021     @return: True upon success
1022     """
1023
1024     c = self.db_con.cursor()
1025
1026     c.execute("BEGIN WORK")
1027     try:
1028         arch_id = self.get_architecture_id(package['Architecture'])
1029
1030         # Remove any already existing recorded files for this package
1031         c.execute("""DELETE FROM pending_content_associations
1032                      WHERE package=%(Package)s
1033                      AND version=%(Version)s
1034                      AND architecture=%(ArchID)s""", {'Package': package['Package'],
1035                                                       'Version': package['Version'],
1036                                                       'ArchID':  arch_id})
1037
1038         for fullpath in fullpaths:
1039             (path, file) = os.path.split(fullpath)
1040
1041             if path.startswith( "./" ):
1042                 path = path[2:]
1043             # Get the necessary IDs ...
1044             file_id = self.get_or_set_contents_file_id(file)
1045             path_id = self.get_or_set_contents_path_id(path)
1046
1047             c.execute("""INSERT INTO pending_content_associations
1048                            (package, version, architecture, filepath, filename)
1049                         VALUES (%%(Package)s, %%(Version)s, '%d', '%d', '%d')"""
1050                 % (arch_id, path_id, file_id), package )
1051
1052         c.execute("COMMIT")
1053         return True
1054     except:
1055         traceback.print_exc()
1056         c.execute("ROLLBACK")
1057         return False