]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
more conversions to sqla
[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 ContentAssociations(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(suite, session=None):
471     """
472     Returns Suite object for given C{suite name}.
473
474     @type suite: string
475     @param suite: The name of the suite
476
477     @type session: Session
478     @param session: Optional SQLA session object (a temporary one will be
479     generated if not supplied)
480
481     @rtype: Suite
482     @return: Suite object for the requested suite name (None if not presenT)
483
484     """
485     if session is None:
486         session = DBConn().session()
487     q = session.query(Suite).filter_by(suite_name=suite)
488     if q.count() == 0:
489         return None
490     return q.one()
491
492 class SuiteArchitecture(object):
493     def __init__(self, *args, **kwargs):
494         pass
495
496     def __repr__(self):
497         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
498
499 def get_suite_architectures(suite, session=None):
500     """
501     Returns list of Architecture objects for given C{suite} name
502
503     @type source: str
504     @param source: Suite name to search for
505
506     @type session: Session
507     @param session: Optional SQL session object (a temporary one will be
508     generated if not supplied)
509
510     @rtype: list
511     @return: list of Architecture objects for the given name (may be empty)
512     """
513
514     if session is None:
515         session = DBConn().session()
516
517     q = session.query(Architecture)
518     q = q.join(SuiteArchitecture)
519     q = q.join(Suite).filter_by(suite_name=suite).order_by('arch_string')
520     return q.all()
521
522
523 class Uid(object):
524     def __init__(self, *args, **kwargs):
525         pass
526
527     def __repr__(self):
528         return '<Uid %s (%s)>' % (self.uid, self.name)
529
530 ################################################################################
531
532 class DBConn(Singleton):
533     """
534     database module init.
535     """
536     def __init__(self, *args, **kwargs):
537         super(DBConn, self).__init__(*args, **kwargs)
538
539     def _startup(self, *args, **kwargs):
540         self.debug = False
541         if kwargs.has_key('debug'):
542             self.debug = True
543         self.__createconn()
544
545     def __setuptables(self):
546         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
547         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
548         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
549         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
550         self.tbl_component = Table('component', self.db_meta, autoload=True)
551         self.tbl_config = Table('config', self.db_meta, autoload=True)
552         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
553         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
554         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
555         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
556         self.tbl_files = Table('files', self.db_meta, autoload=True)
557         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
558         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
559         self.tbl_location = Table('location', self.db_meta, autoload=True)
560         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
561         self.tbl_override = Table('override', self.db_meta, autoload=True)
562         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
563         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
564         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
565         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
566         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
567         self.tbl_section = Table('section', self.db_meta, autoload=True)
568         self.tbl_source = Table('source', self.db_meta, autoload=True)
569         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
570         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
571         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
572         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
573         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
574
575     def __setupmappers(self):
576         mapper(Architecture, self.tbl_architecture,
577                properties = dict(arch_id = self.tbl_architecture.c.id))
578
579         mapper(Archive, self.tbl_archive,
580                properties = dict(archive_id = self.tbl_archive.c.id,
581                                  archive_name = self.tbl_archive.c.name))
582
583         mapper(BinAssociation, self.tbl_bin_associations,
584                properties = dict(ba_id = self.tbl_bin_associations.c.id,
585                                  suite_id = self.tbl_bin_associations.c.suite,
586                                  suite = relation(Suite),
587                                  binary_id = self.tbl_bin_associations.c.bin,
588                                  binary = relation(Binary)))
589
590         mapper(Binary, self.tbl_binaries,
591                properties = dict(binary_id = self.tbl_binaries.c.id,
592                                  package = self.tbl_binaries.c.package,
593                                  version = self.tbl_binaries.c.version,
594                                  maintainer_id = self.tbl_binaries.c.maintainer,
595                                  maintainer = relation(Maintainer),
596                                  source_id = self.tbl_binaries.c.source,
597                                  source = relation(Source),
598                                  arch_id = self.tbl_binaries.c.architecture,
599                                  architecture = relation(Architecture),
600                                  poolfile_id = self.tbl_binaries.c.file,
601                                  poolfile = relation(PoolFile),
602                                  binarytype = self.tbl_binaries.c.type,
603                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
604                                  fingerprint = relation(Fingerprint),
605                                  install_date = self.tbl_binaries.c.install_date,
606                                  binassociations = relation(BinAssociation,
607                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
608
609         mapper(Component, self.tbl_component,
610                properties = dict(component_id = self.tbl_component.c.id,
611                                  component_name = self.tbl_component.c.name))
612
613         mapper(DBConfig, self.tbl_config,
614                properties = dict(config_id = self.tbl_config.c.id))
615
616         mapper(ContentAssociations, self.tbl_content_associations,
617                properties = dict(ca_id = self.tbl_content_associations.c.id,
618                                  filename_id = self.tbl_content_associations.c.filename,
619                                  filename    = relation(ContentFilename),
620                                  filepath_id = self.tbl_content_associations.c.filepath,
621                                  filepath    = relation(ContentFilepath),
622                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
623                                  binary      = relation(Binary)))
624
625
626         mapper(ContentFilename, self.tbl_content_file_names,
627                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
628                                  filename = self.tbl_content_file_names.c.file))
629
630         mapper(ContentFilepath, self.tbl_content_file_paths,
631                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
632                                  filepath = self.tbl_content_file_paths.c.path))
633
634         mapper(DSCFile, self.tbl_dsc_files,
635                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
636                                  source_id = self.tbl_dsc_files.c.source,
637                                  source = relation(Source),
638                                  poolfile_id = self.tbl_dsc_files.c.file,
639                                  poolfile = relation(PoolFile)))
640
641         mapper(PoolFile, self.tbl_files,
642                properties = dict(file_id = self.tbl_files.c.id,
643                                  filesize = self.tbl_files.c.size,
644                                  location_id = self.tbl_files.c.location,
645                                  location = relation(Location)))
646
647         mapper(Fingerprint, self.tbl_fingerprint,
648                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
649                                  uid_id = self.tbl_fingerprint.c.uid,
650                                  uid = relation(Uid),
651                                  keyring_id = self.tbl_fingerprint.c.keyring,
652                                  keyring = relation(Keyring)))
653
654         mapper(Keyring, self.tbl_keyrings,
655                properties = dict(keyring_name = self.tbl_keyrings.c.name,
656                                  keyring_id = self.tbl_keyrings.c.id))
657
658         mapper(Location, self.tbl_location,
659                properties = dict(location_id = self.tbl_location.c.id,
660                                  component_id = self.tbl_location.c.component,
661                                  component = relation(Component),
662                                  archive_id = self.tbl_location.c.archive,
663                                  archive = relation(Archive),
664                                  archive_type = self.tbl_location.c.type))
665
666         mapper(Maintainer, self.tbl_maintainer,
667                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
668
669         mapper(Override, self.tbl_override,
670                properties = dict(suite_id = self.tbl_override.c.suite,
671                                  suite = relation(Suite),
672                                  component_id = self.tbl_override.c.component,
673                                  component = relation(Component),
674                                  priority_id = self.tbl_override.c.priority,
675                                  priority = relation(Priority),
676                                  section_id = self.tbl_override.c.section,
677                                  section = relation(Section),
678                                  overridetype_id = self.tbl_override.c.type,
679                                  overridetype = relation(OverrideType)))
680
681         mapper(OverrideType, self.tbl_override_type,
682                properties = dict(overridetype = self.tbl_override_type.c.type,
683                                  overridetype_id = self.tbl_override_type.c.id))
684
685         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
686                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
687                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
688                                  filepath = relation(ContentFilepath),
689                                  filename_id = self.tbl_pending_content_associations.c.filename,
690                                  filename = relation(ContentFilename)))
691
692         mapper(Priority, self.tbl_priority,
693                properties = dict(priority_id = self.tbl_priority.c.id))
694
695         mapper(Queue, self.tbl_queue,
696                properties = dict(queue_id = self.tbl_queue.c.id))
697
698         mapper(QueueBuild, self.tbl_queue_build,
699                properties = dict(suite_id = self.tbl_queue_build.c.suite,
700                                  queue_id = self.tbl_queue_build.c.queue,
701                                  queue = relation(Queue)))
702
703         mapper(Section, self.tbl_section,
704                properties = dict(section_id = self.tbl_section.c.id))
705
706         mapper(Source, self.tbl_source,
707                properties = dict(source_id = self.tbl_source.c.id,
708                                  version = self.tbl_source.c.version,
709                                  maintainer_id = self.tbl_source.c.maintainer,
710                                  maintainer = relation(Maintainer,
711                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
712                                  poolfile_id = self.tbl_source.c.file,
713                                  poolfile = relation(PoolFile),
714                                  fingerprint_id = self.tbl_source.c.sig_fpr,
715                                  fingerprint = relation(Fingerprint),
716                                  changedby_id = self.tbl_source.c.changedby,
717                                  changedby = relation(Maintainer,
718                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
719                                  srcfiles = relation(DSCFile,
720                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
721                                  srcassociations = relation(SrcAssociation,
722                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
723
724         mapper(SrcAssociation, self.tbl_src_associations,
725                properties = dict(sa_id = self.tbl_src_associations.c.id,
726                                  suite_id = self.tbl_src_associations.c.suite,
727                                  suite = relation(Suite),
728                                  source_id = self.tbl_src_associations.c.source,
729                                  source = relation(Source)))
730
731         mapper(SrcUploader, self.tbl_src_uploaders,
732                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
733                                  source_id = self.tbl_src_uploaders.c.source,
734                                  source = relation(Source,
735                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
736                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
737                                  maintainer = relation(Maintainer,
738                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
739
740         mapper(Suite, self.tbl_suite,
741                properties = dict(suite_id = self.tbl_suite.c.id))
742
743         mapper(SuiteArchitecture, self.tbl_suite_architectures,
744                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
745                                  suite = relation(Suite),
746                                  arch_id = self.tbl_suite_architectures.c.architecture,
747                                  architecture = relation(Architecture)))
748
749         mapper(Uid, self.tbl_uid,
750                properties = dict(uid_id = self.tbl_uid.c.id))
751
752     ## Connection functions
753     def __createconn(self):
754         from config import Config
755         cnf = Config()
756         if cnf["DB::Host"]:
757             # TCP/IP
758             connstr = "postgres://%s" % cnf["DB::Host"]
759             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
760                 connstr += ":%s" % cnf["DB::Port"]
761             connstr += "/%s" % cnf["DB::Name"]
762         else:
763             # Unix Socket
764             connstr = "postgres:///%s" % cnf["DB::Name"]
765             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
766                 connstr += "?port=%s" % cnf["DB::Port"]
767
768         self.db_pg   = create_engine(connstr, echo=self.debug)
769         self.db_meta = MetaData()
770         self.db_meta.bind = self.db_pg
771         self.db_smaker = sessionmaker(bind=self.db_pg,
772                                       autoflush=True,
773                                       autocommit=True)
774
775         self.__setuptables()
776         self.__setupmappers()
777
778     def session(self):
779         return self.db_smaker()
780
781     def prepare(self,name,statement):
782         if not self.prepared_statements.has_key(name):
783             pgc.execute(statement)
784             self.prepared_statements[name] = statement
785
786
787     def get_location_id(self, location, component, archive):
788         """
789         Returns database id for the location behind the given combination of
790           - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
791           - B{component} - the id of the component as returned by L{get_component_id}
792           - B{archive} - the id of the archive as returned by L{get_archive_id}
793         Results are kept in a cache during runtime to minimize database queries.
794
795         @type location: string
796         @param location: the path of the location
797
798         @type component: int
799         @param component: the id of the component
800
801         @type archive: int
802         @param archive: the id of the archive
803
804         @rtype: int
805         @return: the database id for the location
806
807         """
808
809         archive_id = self.get_archive_id(archive)
810
811         if not archive_id:
812             return None
813
814         res = None
815
816         if component:
817             component_id = self.get_component_id(component)
818             if component_id:
819                 res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)s AND archive=%(archive)s",
820                         {'location': location,
821                          'archive': int(archive_id),
822                          'component': component_id}, cachename='location')
823         else:
824             res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d",
825                     {'location': location, 'archive': archive_id, 'component': ''}, cachename='location')
826
827         return res
828
829
830
831 def get_files_id (self, filename, size, md5sum, location_id):
832     """
833     Returns -1, -2 or the file_id for filename, if its C{size} and C{md5sum} match an
834     existing copy.
835
836     The database is queried using the C{filename} and C{location_id}. If a file does exist
837     at that location, the existing size and md5sum are checked against the provided
838     parameters. A size or checksum mismatch returns -2. If more than one entry is
839     found within the database, a -1 is returned, no result returns None, otherwise
840     the file id.
841
842     @type filename: string
843     @param filename: the filename of the file to check against the DB
844
845     @type size: int
846     @param size: the size of the file to check against the DB
847
848     @type md5sum: string
849     @param md5sum: the md5sum of the file to check against the DB
850
851     @type location_id: int
852     @param location_id: the id of the location as returned by L{get_location_id}
853
854     @rtype: int / None
855     @return: Various return values are possible:
856                - -2: size/checksum error
857                - -1: more than one file found in database
858                - None: no file found in database
859                - int: file id
860
861     """
862     values = {'filename' : filename,
863               'location' : location_id}
864
865     if not res:
866         query = """SELECT id, size, md5sum
867                    FROM files
868                    WHERE filename = %(filename)s AND location = %(location)s"""
869
870         cursor = self.db_con.cursor()
871         cursor.execute( query, values )
872
873         if cursor.rowcount == 0:
874             res = None
875
876         elif cursor.rowcount != 1:
877             res = -1
878
879         else:
880             row = cursor.fetchone()
881
882             if row[1] != int(size) or row[2] != md5sum:
883                 res =  -2
884
885             else:
886                 res = row[0]
887
888     return res
889
890
891 def get_or_set_contents_file_id(self, filename):
892     """
893     Returns database id for given filename.
894
895     If no matching file is found, a row is inserted.
896
897     @type filename: string
898     @param filename: The filename
899
900     @rtype: int
901     @return: the database id for the given component
902     """
903     try:
904         values={'value': filename}
905         query = "SELECT id FROM content_file_names WHERE file = %(value)s"
906         if not id:
907             c = self.db_con.cursor()
908             c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id",
909                        values )
910
911             id = c.fetchone()[0]
912
913         return id
914     except:
915         traceback.print_exc()
916         raise
917
918 def get_or_set_contents_path_id(self, path):
919     """
920     Returns database id for given path.
921
922     If no matching file is found, a row is inserted.
923
924     @type path: string
925     @param path: The filename
926
927     @rtype: int
928     @return: the database id for the given component
929     """
930     try:
931         values={'value': path}
932         query = "SELECT id FROM content_file_paths WHERE path = %(value)s"
933         if not id:
934             c = self.db_con.cursor()
935             c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id",
936                        values )
937
938             id = c.fetchone()[0]
939
940         return id
941     except:
942         traceback.print_exc()
943         raise
944
945
946 def insert_content_paths(self, bin_id, fullpaths):
947     """
948     Make sure given path is associated with given binary id
949
950     @type bin_id: int
951     @param bin_id: the id of the binary
952     @type fullpaths: list
953     @param fullpaths: the list of paths of the file being associated with the binary
954
955     @return: True upon success
956     """
957
958     c = self.db_con.cursor()
959
960     c.execute("BEGIN WORK")
961     try:
962
963         for fullpath in fullpaths:
964             (path, file) = os.path.split(fullpath)
965
966             # Get the necessary IDs ...
967             file_id = self.get_or_set_contents_file_id(file)
968             path_id = self.get_or_set_contents_path_id(path)
969
970             c.execute("""INSERT INTO content_associations
971                            (binary_pkg, filepath, filename)
972                        VALUES ( '%d', '%d', '%d')""" % (bin_id, path_id, file_id) )
973
974         c.execute("COMMIT")
975         return True
976     except:
977         traceback.print_exc()
978         c.execute("ROLLBACK")
979         return False
980
981 def insert_pending_content_paths(self, package, fullpaths):
982     """
983     Make sure given paths are temporarily associated with given
984     package
985
986     @type package: dict
987     @param package: the package to associate with should have been read in from the binary control file
988     @type fullpaths: list
989     @param fullpaths: the list of paths of the file being associated with the binary
990
991     @return: True upon success
992     """
993
994     c = self.db_con.cursor()
995
996     c.execute("BEGIN WORK")
997     try:
998         arch_id = self.get_architecture_id(package['Architecture'])
999
1000         # Remove any already existing recorded files for this package
1001         c.execute("""DELETE FROM pending_content_associations
1002                      WHERE package=%(Package)s
1003                      AND version=%(Version)s
1004                      AND architecture=%(ArchID)s""", {'Package': package['Package'],
1005                                                       'Version': package['Version'],
1006                                                       'ArchID':  arch_id})
1007
1008         for fullpath in fullpaths:
1009             (path, file) = os.path.split(fullpath)
1010
1011             if path.startswith( "./" ):
1012                 path = path[2:]
1013             # Get the necessary IDs ...
1014             file_id = self.get_or_set_contents_file_id(file)
1015             path_id = self.get_or_set_contents_path_id(path)
1016
1017             c.execute("""INSERT INTO pending_content_associations
1018                            (package, version, architecture, filepath, filename)
1019                         VALUES (%%(Package)s, %%(Version)s, '%d', '%d', '%d')"""
1020                 % (arch_id, path_id, file_id), package )
1021
1022         c.execute("COMMIT")
1023         return True
1024     except:
1025         traceback.print_exc()
1026         c.execute("ROLLBACK")
1027         return False