]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
merge from master
[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 re
38 import psycopg2
39 import traceback
40
41 from inspect import getargspec
42
43 from sqlalchemy import create_engine, Table, MetaData
44 from sqlalchemy.orm import sessionmaker, mapper, relation
45
46 # Don't remove this, we re-export the exceptions to scripts which import us
47 from sqlalchemy.exc import *
48 from sqlalchemy.orm.exc import NoResultFound
49
50 # Only import Config until Queue stuff is changed to store its config
51 # in the database
52 from config import Config
53 from singleton import Singleton
54 from textutils import fix_maintainer
55
56 ################################################################################
57
58 __all__ = ['IntegrityError', 'SQLAlchemyError']
59
60 ################################################################################
61
62 def session_wrapper(fn):
63     """
64     Wrapper around common ".., session=None):" handling. If the wrapped
65     function is called without passing 'session', we create a local one
66     and destroy it when the function ends.
67
68     Also attaches a commit_or_flush method to the session; if we created a
69     local session, this is a synonym for session.commit(), otherwise it is a
70     synonym for session.flush().
71     """
72
73     def wrapped(*args, **kwargs):
74         private_transaction = False
75
76         # Find the session object
77         session = kwargs.get('session')
78
79         if session is None:
80             if len(args) <= len(getargspec(fn)[0]) - 1:
81                 # No session specified as last argument or in kwargs
82                 private_transaction = True
83                 session = kwargs['session'] = DBConn().session()
84             else:
85                 # Session is last argument in args
86                 session = args[-1]
87                 if session is None:
88                     args = list(args)
89                     session = args[-1] = DBConn().session()
90                     private_transaction = True
91
92         if private_transaction:
93             session.commit_or_flush = session.commit
94         else:
95             session.commit_or_flush = session.flush
96
97         try:
98             return fn(*args, **kwargs)
99         finally:
100             if private_transaction:
101                 # We created a session; close it.
102                 session.close()
103
104     wrapped.__doc__ = fn.__doc__
105     wrapped.func_name = fn.func_name
106
107     return wrapped
108
109 ################################################################################
110
111 class Architecture(object):
112     def __init__(self, *args, **kwargs):
113         pass
114
115     def __eq__(self, val):
116         if isinstance(val, str):
117             return (self.arch_string== val)
118         # This signals to use the normal comparison operator
119         return NotImplemented
120
121     def __ne__(self, val):
122         if isinstance(val, str):
123             return (self.arch_string != val)
124         # This signals to use the normal comparison operator
125         return NotImplemented
126
127     def __repr__(self):
128         return '<Architecture %s>' % self.arch_string
129
130 __all__.append('Architecture')
131
132 @session_wrapper
133 def get_architecture(architecture, session=None):
134     """
135     Returns database id for given C{architecture}.
136
137     @type architecture: string
138     @param architecture: The name of the architecture
139
140     @type session: Session
141     @param session: Optional SQLA session object (a temporary one will be
142     generated if not supplied)
143
144     @rtype: Architecture
145     @return: Architecture object for the given arch (None if not present)
146     """
147
148     q = session.query(Architecture).filter_by(arch_string=architecture)
149
150     try:
151         return q.one()
152     except NoResultFound:
153         return None
154
155 __all__.append('get_architecture')
156
157 @session_wrapper
158 def get_architecture_suites(architecture, session=None):
159     """
160     Returns list of Suite objects for given C{architecture} name
161
162     @type source: str
163     @param source: Architecture name to search for
164
165     @type session: Session
166     @param session: Optional SQL session object (a temporary one will be
167     generated if not supplied)
168
169     @rtype: list
170     @return: list of Suite objects for the given name (may be empty)
171     """
172
173     q = session.query(Suite)
174     q = q.join(SuiteArchitecture)
175     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
176
177     ret = q.all()
178
179     return ret
180
181 __all__.append('get_architecture_suites')
182
183 ################################################################################
184
185 class Archive(object):
186     def __init__(self, *args, **kwargs):
187         pass
188
189     def __repr__(self):
190         return '<Archive %s>' % self.archive_name
191
192 __all__.append('Archive')
193
194 @session_wrapper
195 def get_archive(archive, session=None):
196     """
197     returns database id for given C{archive}.
198
199     @type archive: string
200     @param archive: the name of the arhive
201
202     @type session: Session
203     @param session: Optional SQLA session object (a temporary one will be
204     generated if not supplied)
205
206     @rtype: Archive
207     @return: Archive object for the given name (None if not present)
208
209     """
210     archive = archive.lower()
211
212     q = session.query(Archive).filter_by(archive_name=archive)
213
214     try:
215         return q.one()
216     except NoResultFound:
217         return None
218
219 __all__.append('get_archive')
220
221 ################################################################################
222
223 class BinAssociation(object):
224     def __init__(self, *args, **kwargs):
225         pass
226
227     def __repr__(self):
228         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
229
230 __all__.append('BinAssociation')
231
232 ################################################################################
233
234 class BinContents(object):
235     def __init__(self, *args, **kwargs):
236         pass
237
238     def __repr__(self):
239         return '<BinContents (%s, %s)>' % (self.binary, self.filename)
240
241 __all__.append('BinContents')
242
243 ################################################################################
244
245 class DBBinary(object):
246     def __init__(self, *args, **kwargs):
247         pass
248
249     def __repr__(self):
250         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
251
252 __all__.append('DBBinary')
253
254 @session_wrapper
255 def get_suites_binary_in(package, session=None):
256     """
257     Returns list of Suite objects which given C{package} name is in
258
259     @type source: str
260     @param source: DBBinary package name to search for
261
262     @rtype: list
263     @return: list of Suite objects for the given package
264     """
265
266     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
267
268 __all__.append('get_suites_binary_in')
269
270 @session_wrapper
271 def get_binary_from_id(id, session=None):
272     """
273     Returns DBBinary object for given C{id}
274
275     @type id: int
276     @param id: Id of the required binary
277
278     @type session: Session
279     @param session: Optional SQLA session object (a temporary one will be
280     generated if not supplied)
281
282     @rtype: DBBinary
283     @return: DBBinary object for the given binary (None if not present)
284     """
285
286     q = session.query(DBBinary).filter_by(binary_id=id)
287
288     try:
289         return q.one()
290     except NoResultFound:
291         return None
292
293 __all__.append('get_binary_from_id')
294
295 @session_wrapper
296 def get_binaries_from_name(package, version=None, architecture=None, session=None):
297     """
298     Returns list of DBBinary objects for given C{package} name
299
300     @type package: str
301     @param package: DBBinary package name to search for
302
303     @type version: str or None
304     @param version: Version to search for (or None)
305
306     @type package: str, list or None
307     @param package: Architectures to limit to (or None if no limit)
308
309     @type session: Session
310     @param session: Optional SQL session object (a temporary one will be
311     generated if not supplied)
312
313     @rtype: list
314     @return: list of DBBinary objects for the given name (may be empty)
315     """
316
317     q = session.query(DBBinary).filter_by(package=package)
318
319     if version is not None:
320         q = q.filter_by(version=version)
321
322     if architecture is not None:
323         if not isinstance(architecture, list):
324             architecture = [architecture]
325         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
326
327     ret = q.all()
328
329     return ret
330
331 __all__.append('get_binaries_from_name')
332
333 @session_wrapper
334 def get_binaries_from_source_id(source_id, session=None):
335     """
336     Returns list of DBBinary objects for given C{source_id}
337
338     @type source_id: int
339     @param source_id: source_id to search for
340
341     @type session: Session
342     @param session: Optional SQL session object (a temporary one will be
343     generated if not supplied)
344
345     @rtype: list
346     @return: list of DBBinary objects for the given name (may be empty)
347     """
348
349     return session.query(DBBinary).filter_by(source_id=source_id).all()
350
351 __all__.append('get_binaries_from_source_id')
352
353 @session_wrapper
354 def get_binary_from_name_suite(package, suitename, session=None):
355     ### For dak examine-package
356     ### XXX: Doesn't use object API yet
357
358     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
359              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
360              WHERE b.package=:package
361                AND b.file = fi.id
362                AND fi.location = l.id
363                AND l.component = c.id
364                AND ba.bin=b.id
365                AND ba.suite = su.id
366                AND su.suite_name=:suitename
367           ORDER BY b.version DESC"""
368
369     return session.execute(sql, {'package': package, 'suitename': suitename})
370
371 __all__.append('get_binary_from_name_suite')
372
373 @session_wrapper
374 def get_binary_components(package, suitename, arch, session=None):
375     # Check for packages that have moved from one component to another
376     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
377     WHERE b.package=:package AND s.suite_name=:suitename
378       AND (a.arch_string = :arch OR a.arch_string = 'all')
379       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
380       AND f.location = l.id
381       AND l.component = c.id
382       AND b.file = f.id"""
383
384     vals = {'package': package, 'suitename': suitename, 'arch': arch}
385
386     return session.execute(query, vals)
387
388 __all__.append('get_binary_components')
389
390 ################################################################################
391
392 class BinaryACL(object):
393     def __init__(self, *args, **kwargs):
394         pass
395
396     def __repr__(self):
397         return '<BinaryACL %s>' % self.binary_acl_id
398
399 __all__.append('BinaryACL')
400
401 ################################################################################
402
403 class BinaryACLMap(object):
404     def __init__(self, *args, **kwargs):
405         pass
406
407     def __repr__(self):
408         return '<BinaryACLMap %s>' % self.binary_acl_map_id
409
410 __all__.append('BinaryACLMap')
411
412 ################################################################################
413
414 class Component(object):
415     def __init__(self, *args, **kwargs):
416         pass
417
418     def __eq__(self, val):
419         if isinstance(val, str):
420             return (self.component_name == val)
421         # This signals to use the normal comparison operator
422         return NotImplemented
423
424     def __ne__(self, val):
425         if isinstance(val, str):
426             return (self.component_name != val)
427         # This signals to use the normal comparison operator
428         return NotImplemented
429
430     def __repr__(self):
431         return '<Component %s>' % self.component_name
432
433
434 __all__.append('Component')
435
436 @session_wrapper
437 def get_component(component, session=None):
438     """
439     Returns database id for given C{component}.
440
441     @type component: string
442     @param component: The name of the override type
443
444     @rtype: int
445     @return: the database id for the given component
446
447     """
448     component = component.lower()
449
450     q = session.query(Component).filter_by(component_name=component)
451
452     try:
453         return q.one()
454     except NoResultFound:
455         return None
456
457 __all__.append('get_component')
458
459 ################################################################################
460
461 class DBConfig(object):
462     def __init__(self, *args, **kwargs):
463         pass
464
465     def __repr__(self):
466         return '<DBConfig %s>' % self.name
467
468 __all__.append('DBConfig')
469
470 ################################################################################
471
472 @session_wrapper
473 def get_or_set_contents_file_id(filename, session=None):
474     """
475     Returns database id for given filename.
476
477     If no matching file is found, a row is inserted.
478
479     @type filename: string
480     @param filename: The filename
481     @type session: SQLAlchemy
482     @param session: Optional SQL session object (a temporary one will be
483     generated if not supplied).  If not passed, a commit will be performed at
484     the end of the function, otherwise the caller is responsible for commiting.
485
486     @rtype: int
487     @return: the database id for the given component
488     """
489
490     q = session.query(ContentFilename).filter_by(filename=filename)
491
492     try:
493         ret = q.one().cafilename_id
494     except NoResultFound:
495         cf = ContentFilename()
496         cf.filename = filename
497         session.add(cf)
498         session.commit_or_flush()
499         ret = cf.cafilename_id
500
501     return ret
502
503 __all__.append('get_or_set_contents_file_id')
504
505 @session_wrapper
506 def get_contents(suite, overridetype, section=None, session=None):
507     """
508     Returns contents for a suite / overridetype combination, limiting
509     to a section if not None.
510
511     @type suite: Suite
512     @param suite: Suite object
513
514     @type overridetype: OverrideType
515     @param overridetype: OverrideType object
516
517     @type section: Section
518     @param section: Optional section object to limit results to
519
520     @type session: SQLAlchemy
521     @param session: Optional SQL session object (a temporary one will be
522     generated if not supplied)
523
524     @rtype: ResultsProxy
525     @return: ResultsProxy object set up to return tuples of (filename, section,
526     package, arch_id)
527     """
528
529     # find me all of the contents for a given suite
530     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
531                             s.section,
532                             b.package,
533                             b.architecture
534                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
535                    JOIN content_file_names n ON (c.filename=n.id)
536                    JOIN binaries b ON (b.id=c.binary_pkg)
537                    JOIN override o ON (o.package=b.package)
538                    JOIN section s ON (s.id=o.section)
539                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
540                    AND b.type=:overridetypename"""
541
542     vals = {'suiteid': suite.suite_id,
543             'overridetypeid': overridetype.overridetype_id,
544             'overridetypename': overridetype.overridetype}
545
546     if section is not None:
547         contents_q += " AND s.id = :sectionid"
548         vals['sectionid'] = section.section_id
549
550     contents_q += " ORDER BY fn"
551
552     return session.execute(contents_q, vals)
553
554 __all__.append('get_contents')
555
556 ################################################################################
557
558 class ContentFilepath(object):
559     def __init__(self, *args, **kwargs):
560         pass
561
562     def __repr__(self):
563         return '<ContentFilepath %s>' % self.filepath
564
565 __all__.append('ContentFilepath')
566
567 @session_wrapper
568 def get_or_set_contents_path_id(filepath, session=None):
569     """
570     Returns database id for given path.
571
572     If no matching file is found, a row is inserted.
573
574     @type filename: string
575     @param filename: The filepath
576     @type session: SQLAlchemy
577     @param session: Optional SQL session object (a temporary one will be
578     generated if not supplied).  If not passed, a commit will be performed at
579     the end of the function, otherwise the caller is responsible for commiting.
580
581     @rtype: int
582     @return: the database id for the given path
583     """
584
585     q = session.query(ContentFilepath).filter_by(filepath=filepath)
586
587     try:
588         ret = q.one().cafilepath_id
589     except NoResultFound:
590         cf = ContentFilepath()
591         cf.filepath = filepath
592         session.add(cf)
593         session.commit_or_flush()
594         ret = cf.cafilepath_id
595
596     return ret
597
598 __all__.append('get_or_set_contents_path_id')
599
600 ################################################################################
601
602 class ContentAssociation(object):
603     def __init__(self, *args, **kwargs):
604         pass
605
606     def __repr__(self):
607         return '<ContentAssociation %s>' % self.ca_id
608
609 __all__.append('ContentAssociation')
610
611 def insert_content_paths(binary_id, fullpaths, session=None):
612     """
613     Make sure given path is associated with given binary id
614
615     @type binary_id: int
616     @param binary_id: the id of the binary
617     @type fullpaths: list
618     @param fullpaths: the list of paths of the file being associated with the binary
619     @type session: SQLAlchemy session
620     @param session: Optional SQLAlchemy session.  If this is passed, the caller
621     is responsible for ensuring a transaction has begun and committing the
622     results or rolling back based on the result code.  If not passed, a commit
623     will be performed at the end of the function, otherwise the caller is
624     responsible for commiting.
625
626     @return: True upon success
627     """
628
629     privatetrans = False
630     if session is None:
631         session = DBConn().session()
632         privatetrans = True
633
634     try:
635         # Insert paths
636         pathcache = {}
637         for fullpath in fullpaths:
638             if fullpath.startswith( './' ):
639                 fullpath = fullpath[2:]
640             
641             session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id}  )
642
643         session.commit()
644         if privatetrans:
645             session.close()
646         return True
647
648     except:
649         traceback.print_exc()
650
651         # Only rollback if we set up the session ourself
652         if privatetrans:
653             session.rollback()
654             session.close()
655
656         return False
657
658 __all__.append('insert_content_paths')
659
660 ################################################################################
661
662 class DSCFile(object):
663     def __init__(self, *args, **kwargs):
664         pass
665
666     def __repr__(self):
667         return '<DSCFile %s>' % self.dscfile_id
668
669 __all__.append('DSCFile')
670
671 @session_wrapper
672 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
673     """
674     Returns a list of DSCFiles which may be empty
675
676     @type dscfile_id: int (optional)
677     @param dscfile_id: the dscfile_id of the DSCFiles to find
678
679     @type source_id: int (optional)
680     @param source_id: the source id related to the DSCFiles to find
681
682     @type poolfile_id: int (optional)
683     @param poolfile_id: the poolfile id related to the DSCFiles to find
684
685     @rtype: list
686     @return: Possibly empty list of DSCFiles
687     """
688
689     q = session.query(DSCFile)
690
691     if dscfile_id is not None:
692         q = q.filter_by(dscfile_id=dscfile_id)
693
694     if source_id is not None:
695         q = q.filter_by(source_id=source_id)
696
697     if poolfile_id is not None:
698         q = q.filter_by(poolfile_id=poolfile_id)
699
700     return q.all()
701
702 __all__.append('get_dscfiles')
703
704 ################################################################################
705
706 class PoolFile(object):
707     def __init__(self, *args, **kwargs):
708         pass
709
710     def __repr__(self):
711         return '<PoolFile %s>' % self.filename
712
713 __all__.append('PoolFile')
714
715 @session_wrapper
716 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
717     """
718     Returns a tuple:
719      (ValidFileFound [boolean or None], PoolFile object or None)
720
721     @type filename: string
722     @param filename: the filename of the file to check against the DB
723
724     @type filesize: int
725     @param filesize: the size of the file to check against the DB
726
727     @type md5sum: string
728     @param md5sum: the md5sum of the file to check against the DB
729
730     @type location_id: int
731     @param location_id: the id of the location to look in
732
733     @rtype: tuple
734     @return: Tuple of length 2.
735              If more than one file found with that name:
736                     (None,  None)
737              If valid pool file found: (True, PoolFile object)
738              If valid pool file not found:
739                     (False, None) if no file found
740                     (False, PoolFile object) if file found with size/md5sum mismatch
741     """
742
743     q = session.query(PoolFile).filter_by(filename=filename)
744     q = q.join(Location).filter_by(location_id=location_id)
745
746     ret = None
747
748     if q.count() > 1:
749         ret = (None, None)
750     elif q.count() < 1:
751         ret = (False, None)
752     else:
753         obj = q.one()
754         if obj.md5sum != md5sum or obj.filesize != filesize:
755             ret = (False, obj)
756
757     if ret is None:
758         ret = (True, obj)
759
760     return ret
761
762 __all__.append('check_poolfile')
763
764 @session_wrapper
765 def get_poolfile_by_id(file_id, session=None):
766     """
767     Returns a PoolFile objects or None for the given id
768
769     @type file_id: int
770     @param file_id: the id of the file to look for
771
772     @rtype: PoolFile or None
773     @return: either the PoolFile object or None
774     """
775
776     q = session.query(PoolFile).filter_by(file_id=file_id)
777
778     try:
779         return q.one()
780     except NoResultFound:
781         return None
782
783 __all__.append('get_poolfile_by_id')
784
785
786 @session_wrapper
787 def get_poolfile_by_name(filename, location_id=None, session=None):
788     """
789     Returns an array of PoolFile objects for the given filename and
790     (optionally) location_id
791
792     @type filename: string
793     @param filename: the filename of the file to check against the DB
794
795     @type location_id: int
796     @param location_id: the id of the location to look in (optional)
797
798     @rtype: array
799     @return: array of PoolFile objects
800     """
801
802     q = session.query(PoolFile).filter_by(filename=filename)
803
804     if location_id is not None:
805         q = q.join(Location).filter_by(location_id=location_id)
806
807     return q.all()
808
809 __all__.append('get_poolfile_by_name')
810
811 @session_wrapper
812 def get_poolfile_like_name(filename, session=None):
813     """
814     Returns an array of PoolFile objects which are like the given name
815
816     @type filename: string
817     @param filename: the filename of the file to check against the DB
818
819     @rtype: array
820     @return: array of PoolFile objects
821     """
822
823     # TODO: There must be a way of properly using bind parameters with %FOO%
824     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
825
826     return q.all()
827
828 __all__.append('get_poolfile_like_name')
829
830 ################################################################################
831
832 class Fingerprint(object):
833     def __init__(self, *args, **kwargs):
834         pass
835
836     def __repr__(self):
837         return '<Fingerprint %s>' % self.fingerprint
838
839 __all__.append('Fingerprint')
840
841 @session_wrapper
842 def get_fingerprint(fpr, session=None):
843     """
844     Returns Fingerprint object for given fpr.
845
846     @type fpr: string
847     @param fpr: The fpr to find / add
848
849     @type session: SQLAlchemy
850     @param session: Optional SQL session object (a temporary one will be
851     generated if not supplied).
852
853     @rtype: Fingerprint
854     @return: the Fingerprint object for the given fpr or None
855     """
856
857     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
858
859     try:
860         ret = q.one()
861     except NoResultFound:
862         ret = None
863
864     return ret
865
866 __all__.append('get_fingerprint')
867
868 @session_wrapper
869 def get_or_set_fingerprint(fpr, session=None):
870     """
871     Returns Fingerprint object for given fpr.
872
873     If no matching fpr is found, a row is inserted.
874
875     @type fpr: string
876     @param fpr: The fpr to find / add
877
878     @type session: SQLAlchemy
879     @param session: Optional SQL session object (a temporary one will be
880     generated if not supplied).  If not passed, a commit will be performed at
881     the end of the function, otherwise the caller is responsible for commiting.
882     A flush will be performed either way.
883
884     @rtype: Fingerprint
885     @return: the Fingerprint object for the given fpr
886     """
887
888     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
889
890     try:
891         ret = q.one()
892     except NoResultFound:
893         fingerprint = Fingerprint()
894         fingerprint.fingerprint = fpr
895         session.add(fingerprint)
896         session.commit_or_flush()
897         ret = fingerprint
898
899     return ret
900
901 __all__.append('get_or_set_fingerprint')
902
903 ################################################################################
904
905 # Helper routine for Keyring class
906 def get_ldap_name(entry):
907     name = []
908     for k in ["cn", "mn", "sn"]:
909         ret = entry.get(k)
910         if ret and ret[0] != "" and ret[0] != "-":
911             name.append(ret[0])
912     return " ".join(name)
913
914 ################################################################################
915
916 class Keyring(object):
917     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
918                      " --with-colons --fingerprint --fingerprint"
919
920     keys = {}
921     fpr_lookup = {}
922
923     def __init__(self, *args, **kwargs):
924         pass
925
926     def __repr__(self):
927         return '<Keyring %s>' % self.keyring_name
928
929     def de_escape_gpg_str(self, str):
930         esclist = re.split(r'(\\x..)', str)
931         for x in range(1,len(esclist),2):
932             esclist[x] = "%c" % (int(esclist[x][2:],16))
933         return "".join(esclist)
934
935     def load_keys(self, keyring):
936         import email.Utils
937
938         if not self.keyring_id:
939             raise Exception('Must be initialized with database information')
940
941         k = os.popen(self.gpg_invocation % keyring, "r")
942         key = None
943         signingkey = False
944
945         for line in k.xreadlines():
946             field = line.split(":")
947             if field[0] == "pub":
948                 key = field[4]
949                 (name, addr) = email.Utils.parseaddr(field[9])
950                 name = re.sub(r"\s*[(].*[)]", "", name)
951                 if name == "" or addr == "" or "@" not in addr:
952                     name = field[9]
953                     addr = "invalid-uid"
954                 name = self.de_escape_gpg_str(name)
955                 self.keys[key] = {"email": addr}
956                 if name != "":
957                     self.keys[key]["name"] = name
958                 self.keys[key]["aliases"] = [name]
959                 self.keys[key]["fingerprints"] = []
960                 signingkey = True
961             elif key and field[0] == "sub" and len(field) >= 12:
962                 signingkey = ("s" in field[11])
963             elif key and field[0] == "uid":
964                 (name, addr) = email.Utils.parseaddr(field[9])
965                 if name and name not in self.keys[key]["aliases"]:
966                     self.keys[key]["aliases"].append(name)
967             elif signingkey and field[0] == "fpr":
968                 self.keys[key]["fingerprints"].append(field[9])
969                 self.fpr_lookup[field[9]] = key
970
971     def import_users_from_ldap(self, session):
972         import ldap
973         cnf = Config()
974
975         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
976         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
977
978         l = ldap.open(LDAPServer)
979         l.simple_bind_s("","")
980         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
981                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
982                ["uid", "keyfingerprint", "cn", "mn", "sn"])
983
984         ldap_fin_uid_id = {}
985
986         byuid = {}
987         byname = {}
988
989         for i in Attrs:
990             entry = i[1]
991             uid = entry["uid"][0]
992             name = get_ldap_name(entry)
993             fingerprints = entry["keyFingerPrint"]
994             keyid = None
995             for f in fingerprints:
996                 key = self.fpr_lookup.get(f, None)
997                 if key not in self.keys:
998                     continue
999                 self.keys[key]["uid"] = uid
1000
1001                 if keyid != None:
1002                     continue
1003                 keyid = get_or_set_uid(uid, session).uid_id
1004                 byuid[keyid] = (uid, name)
1005                 byname[uid] = (keyid, name)
1006
1007         return (byname, byuid)
1008
1009     def generate_users_from_keyring(self, format, session):
1010         byuid = {}
1011         byname = {}
1012         any_invalid = False
1013         for x in self.keys.keys():
1014             if self.keys[x]["email"] == "invalid-uid":
1015                 any_invalid = True
1016                 self.keys[x]["uid"] = format % "invalid-uid"
1017             else:
1018                 uid = format % self.keys[x]["email"]
1019                 keyid = get_or_set_uid(uid, session).uid_id
1020                 byuid[keyid] = (uid, self.keys[x]["name"])
1021                 byname[uid] = (keyid, self.keys[x]["name"])
1022                 self.keys[x]["uid"] = uid
1023
1024         if any_invalid:
1025             uid = format % "invalid-uid"
1026             keyid = get_or_set_uid(uid, session).uid_id
1027             byuid[keyid] = (uid, "ungeneratable user id")
1028             byname[uid] = (keyid, "ungeneratable user id")
1029
1030         return (byname, byuid)
1031
1032 __all__.append('Keyring')
1033
1034 @session_wrapper
1035 def get_keyring(keyring, session=None):
1036     """
1037     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1038     If C{keyring} already has an entry, simply return the existing Keyring
1039
1040     @type keyring: string
1041     @param keyring: the keyring name
1042
1043     @rtype: Keyring
1044     @return: the Keyring object for this keyring
1045     """
1046
1047     q = session.query(Keyring).filter_by(keyring_name=keyring)
1048
1049     try:
1050         return q.one()
1051     except NoResultFound:
1052         return None
1053
1054 __all__.append('get_keyring')
1055
1056 ################################################################################
1057
1058 class KeyringACLMap(object):
1059     def __init__(self, *args, **kwargs):
1060         pass
1061
1062     def __repr__(self):
1063         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1064
1065 __all__.append('KeyringACLMap')
1066
1067 ################################################################################
1068
1069 class Location(object):
1070     def __init__(self, *args, **kwargs):
1071         pass
1072
1073     def __repr__(self):
1074         return '<Location %s (%s)>' % (self.path, self.location_id)
1075
1076 __all__.append('Location')
1077
1078 @session_wrapper
1079 def get_location(location, component=None, archive=None, session=None):
1080     """
1081     Returns Location object for the given combination of location, component
1082     and archive
1083
1084     @type location: string
1085     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1086
1087     @type component: string
1088     @param component: the component name (if None, no restriction applied)
1089
1090     @type archive: string
1091     @param archive_id: the archive name (if None, no restriction applied)
1092
1093     @rtype: Location / None
1094     @return: Either a Location object or None if one can't be found
1095     """
1096
1097     q = session.query(Location).filter_by(path=location)
1098
1099     if archive is not None:
1100         q = q.join(Archive).filter_by(archive_name=archive)
1101
1102     if component is not None:
1103         q = q.join(Component).filter_by(component_name=component)
1104
1105     try:
1106         return q.one()
1107     except NoResultFound:
1108         return None
1109
1110 __all__.append('get_location')
1111
1112 ################################################################################
1113
1114 class Maintainer(object):
1115     def __init__(self, *args, **kwargs):
1116         pass
1117
1118     def __repr__(self):
1119         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1120
1121     def get_split_maintainer(self):
1122         if not hasattr(self, 'name') or self.name is None:
1123             return ('', '', '', '')
1124
1125         return fix_maintainer(self.name.strip())
1126
1127 __all__.append('Maintainer')
1128
1129 @session_wrapper
1130 def get_or_set_maintainer(name, session=None):
1131     """
1132     Returns Maintainer object for given maintainer name.
1133
1134     If no matching maintainer name is found, a row is inserted.
1135
1136     @type name: string
1137     @param name: The maintainer name to add
1138
1139     @type session: SQLAlchemy
1140     @param session: Optional SQL session object (a temporary one will be
1141     generated if not supplied).  If not passed, a commit will be performed at
1142     the end of the function, otherwise the caller is responsible for commiting.
1143     A flush will be performed either way.
1144
1145     @rtype: Maintainer
1146     @return: the Maintainer object for the given maintainer
1147     """
1148
1149     q = session.query(Maintainer).filter_by(name=name)
1150     try:
1151         ret = q.one()
1152     except NoResultFound:
1153         maintainer = Maintainer()
1154         maintainer.name = name
1155         session.add(maintainer)
1156         session.commit_or_flush()
1157         ret = maintainer
1158
1159     return ret
1160
1161 __all__.append('get_or_set_maintainer')
1162
1163 @session_wrapper
1164 def get_maintainer(maintainer_id, session=None):
1165     """
1166     Return the name of the maintainer behind C{maintainer_id} or None if that
1167     maintainer_id is invalid.
1168
1169     @type maintainer_id: int
1170     @param maintainer_id: the id of the maintainer
1171
1172     @rtype: Maintainer
1173     @return: the Maintainer with this C{maintainer_id}
1174     """
1175
1176     return session.query(Maintainer).get(maintainer_id)
1177
1178 __all__.append('get_maintainer')
1179
1180 ################################################################################
1181
1182 class NewComment(object):
1183     def __init__(self, *args, **kwargs):
1184         pass
1185
1186     def __repr__(self):
1187         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1188
1189 __all__.append('NewComment')
1190
1191 @session_wrapper
1192 def has_new_comment(package, version, session=None):
1193     """
1194     Returns true if the given combination of C{package}, C{version} has a comment.
1195
1196     @type package: string
1197     @param package: name of the package
1198
1199     @type version: string
1200     @param version: package version
1201
1202     @type session: Session
1203     @param session: Optional SQLA session object (a temporary one will be
1204     generated if not supplied)
1205
1206     @rtype: boolean
1207     @return: true/false
1208     """
1209
1210     q = session.query(NewComment)
1211     q = q.filter_by(package=package)
1212     q = q.filter_by(version=version)
1213
1214     return bool(q.count() > 0)
1215
1216 __all__.append('has_new_comment')
1217
1218 @session_wrapper
1219 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1220     """
1221     Returns (possibly empty) list of NewComment objects for the given
1222     parameters
1223
1224     @type package: string (optional)
1225     @param package: name of the package
1226
1227     @type version: string (optional)
1228     @param version: package version
1229
1230     @type comment_id: int (optional)
1231     @param comment_id: An id of a comment
1232
1233     @type session: Session
1234     @param session: Optional SQLA session object (a temporary one will be
1235     generated if not supplied)
1236
1237     @rtype: list
1238     @return: A (possibly empty) list of NewComment objects will be returned
1239     """
1240
1241     q = session.query(NewComment)
1242     if package is not None: q = q.filter_by(package=package)
1243     if version is not None: q = q.filter_by(version=version)
1244     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1245
1246     return q.all()
1247
1248 __all__.append('get_new_comments')
1249
1250 ################################################################################
1251
1252 class Override(object):
1253     def __init__(self, *args, **kwargs):
1254         pass
1255
1256     def __repr__(self):
1257         return '<Override %s (%s)>' % (self.package, self.suite_id)
1258
1259 __all__.append('Override')
1260
1261 @session_wrapper
1262 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1263     """
1264     Returns Override object for the given parameters
1265
1266     @type package: string
1267     @param package: The name of the package
1268
1269     @type suite: string, list or None
1270     @param suite: The name of the suite (or suites if a list) to limit to.  If
1271                   None, don't limit.  Defaults to None.
1272
1273     @type component: string, list or None
1274     @param component: The name of the component (or components if a list) to
1275                       limit to.  If None, don't limit.  Defaults to None.
1276
1277     @type overridetype: string, list or None
1278     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1279                          limit to.  If None, don't limit.  Defaults to None.
1280
1281     @type session: Session
1282     @param session: Optional SQLA session object (a temporary one will be
1283     generated if not supplied)
1284
1285     @rtype: list
1286     @return: A (possibly empty) list of Override objects will be returned
1287     """
1288
1289     q = session.query(Override)
1290     q = q.filter_by(package=package)
1291
1292     if suite is not None:
1293         if not isinstance(suite, list): suite = [suite]
1294         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1295
1296     if component is not None:
1297         if not isinstance(component, list): component = [component]
1298         q = q.join(Component).filter(Component.component_name.in_(component))
1299
1300     if overridetype is not None:
1301         if not isinstance(overridetype, list): overridetype = [overridetype]
1302         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1303
1304     return q.all()
1305
1306 __all__.append('get_override')
1307
1308
1309 ################################################################################
1310
1311 class OverrideType(object):
1312     def __init__(self, *args, **kwargs):
1313         pass
1314
1315     def __repr__(self):
1316         return '<OverrideType %s>' % self.overridetype
1317
1318 __all__.append('OverrideType')
1319
1320 @session_wrapper
1321 def get_override_type(override_type, session=None):
1322     """
1323     Returns OverrideType object for given C{override type}.
1324
1325     @type override_type: string
1326     @param override_type: The name of the override type
1327
1328     @type session: Session
1329     @param session: Optional SQLA session object (a temporary one will be
1330     generated if not supplied)
1331
1332     @rtype: int
1333     @return: the database id for the given override type
1334     """
1335
1336     q = session.query(OverrideType).filter_by(overridetype=override_type)
1337
1338     try:
1339         return q.one()
1340     except NoResultFound:
1341         return None
1342
1343 __all__.append('get_override_type')
1344
1345 ################################################################################
1346
1347 class PendingContentAssociation(object):
1348     def __init__(self, *args, **kwargs):
1349         pass
1350
1351     def __repr__(self):
1352         return '<PendingContentAssociation %s>' % self.pca_id
1353
1354 __all__.append('PendingContentAssociation')
1355
1356 def insert_pending_content_paths(package, fullpaths, session=None):
1357     """
1358     Make sure given paths are temporarily associated with given
1359     package
1360
1361     @type package: dict
1362     @param package: the package to associate with should have been read in from the binary control file
1363     @type fullpaths: list
1364     @param fullpaths: the list of paths of the file being associated with the binary
1365     @type session: SQLAlchemy session
1366     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1367     is responsible for ensuring a transaction has begun and committing the
1368     results or rolling back based on the result code.  If not passed, a commit
1369     will be performed at the end of the function
1370
1371     @return: True upon success, False if there is a problem
1372     """
1373
1374     privatetrans = False
1375
1376     if session is None:
1377         session = DBConn().session()
1378         privatetrans = True
1379
1380     try:
1381         arch = get_architecture(package['Architecture'], session)
1382         arch_id = arch.arch_id
1383
1384         # Remove any already existing recorded files for this package
1385         q = session.query(PendingContentAssociation)
1386         q = q.filter_by(package=package['Package'])
1387         q = q.filter_by(version=package['Version'])
1388         q = q.filter_by(architecture=arch_id)
1389         q.delete()
1390
1391         # Insert paths
1392         pathcache = {}
1393         for fullpath in fullpaths:
1394             (path, file) = os.path.split(fullpath)
1395
1396             if path.startswith( "./" ):
1397                 path = path[2:]
1398
1399             filepath_id = get_or_set_contents_path_id(path, session)
1400             filename_id = get_or_set_contents_file_id(file, session)
1401
1402             pathcache[fullpath] = (filepath_id, filename_id)
1403
1404         for fullpath, dat in pathcache.items():
1405             pca = PendingContentAssociation()
1406             pca.package = package['Package']
1407             pca.version = package['Version']
1408             pca.filepath_id = dat[0]
1409             pca.filename_id = dat[1]
1410             pca.architecture = arch_id
1411             session.add(pca)
1412
1413         # Only commit if we set up the session ourself
1414         if privatetrans:
1415             session.commit()
1416             session.close()
1417         else:
1418             session.flush()
1419
1420         return True
1421     except Exception, e:
1422         traceback.print_exc()
1423
1424         # Only rollback if we set up the session ourself
1425         if privatetrans:
1426             session.rollback()
1427             session.close()
1428
1429         return False
1430
1431 __all__.append('insert_pending_content_paths')
1432
1433 ################################################################################
1434
1435 class Priority(object):
1436     def __init__(self, *args, **kwargs):
1437         pass
1438
1439     def __eq__(self, val):
1440         if isinstance(val, str):
1441             return (self.priority == val)
1442         # This signals to use the normal comparison operator
1443         return NotImplemented
1444
1445     def __ne__(self, val):
1446         if isinstance(val, str):
1447             return (self.priority != val)
1448         # This signals to use the normal comparison operator
1449         return NotImplemented
1450
1451     def __repr__(self):
1452         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1453
1454 __all__.append('Priority')
1455
1456 @session_wrapper
1457 def get_priority(priority, session=None):
1458     """
1459     Returns Priority object for given C{priority name}.
1460
1461     @type priority: string
1462     @param priority: The name of the priority
1463
1464     @type session: Session
1465     @param session: Optional SQLA session object (a temporary one will be
1466     generated if not supplied)
1467
1468     @rtype: Priority
1469     @return: Priority object for the given priority
1470     """
1471
1472     q = session.query(Priority).filter_by(priority=priority)
1473
1474     try:
1475         return q.one()
1476     except NoResultFound:
1477         return None
1478
1479 __all__.append('get_priority')
1480
1481 @session_wrapper
1482 def get_priorities(session=None):
1483     """
1484     Returns dictionary of priority names -> id mappings
1485
1486     @type session: Session
1487     @param session: Optional SQL session object (a temporary one will be
1488     generated if not supplied)
1489
1490     @rtype: dictionary
1491     @return: dictionary of priority names -> id mappings
1492     """
1493
1494     ret = {}
1495     q = session.query(Priority)
1496     for x in q.all():
1497         ret[x.priority] = x.priority_id
1498
1499     return ret
1500
1501 __all__.append('get_priorities')
1502
1503 ################################################################################
1504
1505 class Queue(object):
1506     def __init__(self, *args, **kwargs):
1507         pass
1508
1509     def __repr__(self):
1510         return '<Queue %s>' % self.queue_name
1511
1512     def autobuild_upload(self, changes, srcpath, session=None):
1513         """
1514         Update queue_build database table used for incoming autobuild support.
1515
1516         @type changes: Changes
1517         @param changes: changes object for the upload to process
1518
1519         @type srcpath: string
1520         @param srcpath: path for the queue file entries/link destinations
1521
1522         @type session: SQLAlchemy session
1523         @param session: Optional SQLAlchemy session.  If this is passed, the
1524         caller is responsible for ensuring a transaction has begun and
1525         committing the results or rolling back based on the result code.  If
1526         not passed, a commit will be performed at the end of the function,
1527         otherwise the caller is responsible for commiting.
1528
1529         @rtype: NoneType or string
1530         @return: None if the operation failed, a string describing the error if not
1531         """
1532
1533         privatetrans = False
1534         if session is None:
1535             session = DBConn().session()
1536             privatetrans = True
1537
1538         # TODO: Remove by moving queue config into the database
1539         conf = Config()
1540
1541         for suitename in changes.changes["distribution"].keys():
1542             # TODO: Move into database as:
1543             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1544             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1545             #       This also gets rid of the SecurityQueueBuild hack below
1546             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1547                 continue
1548
1549             # Find suite object
1550             s = get_suite(suitename, session)
1551             if s is None:
1552                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1553
1554             # TODO: Get from database as above
1555             dest_dir = conf["Dir::QueueBuild"]
1556
1557             # TODO: Move into database as above
1558             if conf.FindB("Dinstall::SecurityQueueBuild"):
1559                 dest_dir = os.path.join(dest_dir, suitename)
1560
1561             for file_entry in changes.files.keys():
1562                 src = os.path.join(srcpath, file_entry)
1563                 dest = os.path.join(dest_dir, file_entry)
1564
1565                 # TODO: Move into database as above
1566                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1567                     # Copy it since the original won't be readable by www-data
1568                     import utils
1569                     utils.copy(src, dest)
1570                 else:
1571                     # Create a symlink to it
1572                     os.symlink(src, dest)
1573
1574                 qb = QueueBuild()
1575                 qb.suite_id = s.suite_id
1576                 qb.queue_id = self.queue_id
1577                 qb.filename = dest
1578                 qb.in_queue = True
1579
1580                 session.add(qb)
1581
1582             # If the .orig tarballs are in the pool, create a symlink to
1583             # them (if one doesn't already exist)
1584             for dsc_file in changes.dsc_files.keys():
1585                 # Skip all files except orig tarballs
1586                 from daklib.regexes import re_is_orig_source
1587                 if not re_is_orig_source.match(dsc_file):
1588                     continue
1589                 # Skip orig files not identified in the pool
1590                 if not (changes.orig_files.has_key(dsc_file) and
1591                         changes.orig_files[dsc_file].has_key("id")):
1592                     continue
1593                 orig_file_id = changes.orig_files[dsc_file]["id"]
1594                 dest = os.path.join(dest_dir, dsc_file)
1595
1596                 # If it doesn't exist, create a symlink
1597                 if not os.path.exists(dest):
1598                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1599                                         {'id': orig_file_id})
1600                     res = q.fetchone()
1601                     if not res:
1602                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1603
1604                     src = os.path.join(res[0], res[1])
1605                     os.symlink(src, dest)
1606
1607                     # Add it to the list of packages for later processing by apt-ftparchive
1608                     qb = QueueBuild()
1609                     qb.suite_id = s.suite_id
1610                     qb.queue_id = self.queue_id
1611                     qb.filename = dest
1612                     qb.in_queue = True
1613                     session.add(qb)
1614
1615                 # If it does, update things to ensure it's not removed prematurely
1616                 else:
1617                     qb = get_queue_build(dest, s.suite_id, session)
1618                     if qb is None:
1619                         qb.in_queue = True
1620                         qb.last_used = None
1621                         session.add(qb)
1622
1623         if privatetrans:
1624             session.commit()
1625             session.close()
1626
1627         return None
1628
1629 __all__.append('Queue')
1630
1631 @session_wrapper
1632 def get_or_set_queue(queuename, session=None):
1633     """
1634     Returns Queue object for given C{queue name}, creating it if it does not
1635     exist.
1636
1637     @type queuename: string
1638     @param queuename: The name of the queue
1639
1640     @type session: Session
1641     @param session: Optional SQLA session object (a temporary one will be
1642     generated if not supplied)
1643
1644     @rtype: Queue
1645     @return: Queue object for the given queue
1646     """
1647
1648     q = session.query(Queue).filter_by(queue_name=queuename)
1649
1650     try:
1651         ret = q.one()
1652     except NoResultFound:
1653         queue = Queue()
1654         queue.queue_name = queuename
1655         session.add(queue)
1656         session.commit_or_flush()
1657         ret = queue
1658
1659     return ret
1660
1661 __all__.append('get_or_set_queue')
1662
1663 ################################################################################
1664
1665 class QueueBuild(object):
1666     def __init__(self, *args, **kwargs):
1667         pass
1668
1669     def __repr__(self):
1670         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1671
1672 __all__.append('QueueBuild')
1673
1674 @session_wrapper
1675 def get_queue_build(filename, suite, session=None):
1676     """
1677     Returns QueueBuild object for given C{filename} and C{suite}.
1678
1679     @type filename: string
1680     @param filename: The name of the file
1681
1682     @type suiteid: int or str
1683     @param suiteid: Suite name or ID
1684
1685     @type session: Session
1686     @param session: Optional SQLA session object (a temporary one will be
1687     generated if not supplied)
1688
1689     @rtype: Queue
1690     @return: Queue object for the given queue
1691     """
1692
1693     if isinstance(suite, int):
1694         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1695     else:
1696         q = session.query(QueueBuild).filter_by(filename=filename)
1697         q = q.join(Suite).filter_by(suite_name=suite)
1698
1699     try:
1700         return q.one()
1701     except NoResultFound:
1702         return None
1703
1704 __all__.append('get_queue_build')
1705
1706 ################################################################################
1707
1708 class Section(object):
1709     def __init__(self, *args, **kwargs):
1710         pass
1711
1712     def __eq__(self, val):
1713         if isinstance(val, str):
1714             return (self.section == val)
1715         # This signals to use the normal comparison operator
1716         return NotImplemented
1717
1718     def __ne__(self, val):
1719         if isinstance(val, str):
1720             return (self.section != val)
1721         # This signals to use the normal comparison operator
1722         return NotImplemented
1723
1724     def __repr__(self):
1725         return '<Section %s>' % self.section
1726
1727 __all__.append('Section')
1728
1729 @session_wrapper
1730 def get_section(section, session=None):
1731     """
1732     Returns Section object for given C{section name}.
1733
1734     @type section: string
1735     @param section: The name of the section
1736
1737     @type session: Session
1738     @param session: Optional SQLA session object (a temporary one will be
1739     generated if not supplied)
1740
1741     @rtype: Section
1742     @return: Section object for the given section name
1743     """
1744
1745     q = session.query(Section).filter_by(section=section)
1746
1747     try:
1748         return q.one()
1749     except NoResultFound:
1750         return None
1751
1752 __all__.append('get_section')
1753
1754 @session_wrapper
1755 def get_sections(session=None):
1756     """
1757     Returns dictionary of section names -> id mappings
1758
1759     @type session: Session
1760     @param session: Optional SQL session object (a temporary one will be
1761     generated if not supplied)
1762
1763     @rtype: dictionary
1764     @return: dictionary of section names -> id mappings
1765     """
1766
1767     ret = {}
1768     q = session.query(Section)
1769     for x in q.all():
1770         ret[x.section] = x.section_id
1771
1772     return ret
1773
1774 __all__.append('get_sections')
1775
1776 ################################################################################
1777
1778 class DBSource(object):
1779     def __init__(self, *args, **kwargs):
1780         pass
1781
1782     def __repr__(self):
1783         return '<DBSource %s (%s)>' % (self.source, self.version)
1784
1785 __all__.append('DBSource')
1786
1787 @session_wrapper
1788 def source_exists(source, source_version, suites = ["any"], session=None):
1789     """
1790     Ensure that source exists somewhere in the archive for the binary
1791     upload being processed.
1792       1. exact match     => 1.0-3
1793       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1794
1795     @type package: string
1796     @param package: package source name
1797
1798     @type source_version: string
1799     @param source_version: expected source version
1800
1801     @type suites: list
1802     @param suites: list of suites to check in, default I{any}
1803
1804     @type session: Session
1805     @param session: Optional SQLA session object (a temporary one will be
1806     generated if not supplied)
1807
1808     @rtype: int
1809     @return: returns 1 if a source with expected version is found, otherwise 0
1810
1811     """
1812
1813     cnf = Config()
1814     ret = 1
1815
1816     for suite in suites:
1817         q = session.query(DBSource).filter_by(source=source)
1818         if suite != "any":
1819             # source must exist in suite X, or in some other suite that's
1820             # mapped to X, recursively... silent-maps are counted too,
1821             # unreleased-maps aren't.
1822             maps = cnf.ValueList("SuiteMappings")[:]
1823             maps.reverse()
1824             maps = [ m.split() for m in maps ]
1825             maps = [ (x[1], x[2]) for x in maps
1826                             if x[0] == "map" or x[0] == "silent-map" ]
1827             s = [suite]
1828             for x in maps:
1829                 if x[1] in s and x[0] not in s:
1830                     s.append(x[0])
1831
1832             q = q.join(SrcAssociation).join(Suite)
1833             q = q.filter(Suite.suite_name.in_(s))
1834
1835         # Reduce the query results to a list of version numbers
1836         ql = [ j.version for j in q.all() ]
1837
1838         # Try (1)
1839         if source_version in ql:
1840             continue
1841
1842         # Try (2)
1843         from daklib.regexes import re_bin_only_nmu
1844         orig_source_version = re_bin_only_nmu.sub('', source_version)
1845         if orig_source_version in ql:
1846             continue
1847
1848         # No source found so return not ok
1849         ret = 0
1850
1851     return ret
1852
1853 __all__.append('source_exists')
1854
1855 @session_wrapper
1856 def get_suites_source_in(source, session=None):
1857     """
1858     Returns list of Suite objects which given C{source} name is in
1859
1860     @type source: str
1861     @param source: DBSource package name to search for
1862
1863     @rtype: list
1864     @return: list of Suite objects for the given source
1865     """
1866
1867     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1868
1869 __all__.append('get_suites_source_in')
1870
1871 @session_wrapper
1872 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1873     """
1874     Returns list of DBSource objects for given C{source} name and other parameters
1875
1876     @type source: str
1877     @param source: DBSource package name to search for
1878
1879     @type source: str or None
1880     @param source: DBSource version name to search for or None if not applicable
1881
1882     @type dm_upload_allowed: bool
1883     @param dm_upload_allowed: If None, no effect.  If True or False, only
1884     return packages with that dm_upload_allowed setting
1885
1886     @type session: Session
1887     @param session: Optional SQL session object (a temporary one will be
1888     generated if not supplied)
1889
1890     @rtype: list
1891     @return: list of DBSource objects for the given name (may be empty)
1892     """
1893
1894     q = session.query(DBSource).filter_by(source=source)
1895
1896     if version is not None:
1897         q = q.filter_by(version=version)
1898
1899     if dm_upload_allowed is not None:
1900         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1901
1902     return q.all()
1903
1904 __all__.append('get_sources_from_name')
1905
1906 @session_wrapper
1907 def get_source_in_suite(source, suite, session=None):
1908     """
1909     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1910
1911       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1912       - B{suite} - a suite name, eg. I{unstable}
1913
1914     @type source: string
1915     @param source: source package name
1916
1917     @type suite: string
1918     @param suite: the suite name
1919
1920     @rtype: string
1921     @return: the version for I{source} in I{suite}
1922
1923     """
1924
1925     q = session.query(SrcAssociation)
1926     q = q.join('source').filter_by(source=source)
1927     q = q.join('suite').filter_by(suite_name=suite)
1928
1929     try:
1930         return q.one().source
1931     except NoResultFound:
1932         return None
1933
1934 __all__.append('get_source_in_suite')
1935
1936 ################################################################################
1937
1938 class SourceACL(object):
1939     def __init__(self, *args, **kwargs):
1940         pass
1941
1942     def __repr__(self):
1943         return '<SourceACL %s>' % self.source_acl_id
1944
1945 __all__.append('SourceACL')
1946
1947 ################################################################################
1948
1949 class SrcAssociation(object):
1950     def __init__(self, *args, **kwargs):
1951         pass
1952
1953     def __repr__(self):
1954         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1955
1956 __all__.append('SrcAssociation')
1957
1958 ################################################################################
1959
1960 class SrcFormat(object):
1961     def __init__(self, *args, **kwargs):
1962         pass
1963
1964     def __repr__(self):
1965         return '<SrcFormat %s>' % (self.format_name)
1966
1967 __all__.append('SrcFormat')
1968
1969 ################################################################################
1970
1971 class SrcUploader(object):
1972     def __init__(self, *args, **kwargs):
1973         pass
1974
1975     def __repr__(self):
1976         return '<SrcUploader %s>' % self.uploader_id
1977
1978 __all__.append('SrcUploader')
1979
1980 ################################################################################
1981
1982 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1983                  ('SuiteID', 'suite_id'),
1984                  ('Version', 'version'),
1985                  ('Origin', 'origin'),
1986                  ('Label', 'label'),
1987                  ('Description', 'description'),
1988                  ('Untouchable', 'untouchable'),
1989                  ('Announce', 'announce'),
1990                  ('Codename', 'codename'),
1991                  ('OverrideCodename', 'overridecodename'),
1992                  ('ValidTime', 'validtime'),
1993                  ('Priority', 'priority'),
1994                  ('NotAutomatic', 'notautomatic'),
1995                  ('CopyChanges', 'copychanges'),
1996                  ('CopyDotDak', 'copydotdak'),
1997                  ('CommentsDir', 'commentsdir'),
1998                  ('OverrideSuite', 'overridesuite'),
1999                  ('ChangelogBase', 'changelogbase')]
2000
2001
2002 class Suite(object):
2003     def __init__(self, *args, **kwargs):
2004         pass
2005
2006     def __repr__(self):
2007         return '<Suite %s>' % self.suite_name
2008
2009     def __eq__(self, val):
2010         if isinstance(val, str):
2011             return (self.suite_name == val)
2012         # This signals to use the normal comparison operator
2013         return NotImplemented
2014
2015     def __ne__(self, val):
2016         if isinstance(val, str):
2017             return (self.suite_name != val)
2018         # This signals to use the normal comparison operator
2019         return NotImplemented
2020
2021     def details(self):
2022         ret = []
2023         for disp, field in SUITE_FIELDS:
2024             val = getattr(self, field, None)
2025             if val is not None:
2026                 ret.append("%s: %s" % (disp, val))
2027
2028         return "\n".join(ret)
2029
2030 __all__.append('Suite')
2031
2032 @session_wrapper
2033 def get_suite_architecture(suite, architecture, session=None):
2034     """
2035     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2036     doesn't exist
2037
2038     @type suite: str
2039     @param suite: Suite name to search for
2040
2041     @type architecture: str
2042     @param architecture: Architecture name to search for
2043
2044     @type session: Session
2045     @param session: Optional SQL session object (a temporary one will be
2046     generated if not supplied)
2047
2048     @rtype: SuiteArchitecture
2049     @return: the SuiteArchitecture object or None
2050     """
2051
2052     q = session.query(SuiteArchitecture)
2053     q = q.join(Architecture).filter_by(arch_string=architecture)
2054     q = q.join(Suite).filter_by(suite_name=suite)
2055
2056     try:
2057         return q.one()
2058     except NoResultFound:
2059         return None
2060
2061 __all__.append('get_suite_architecture')
2062
2063 @session_wrapper
2064 def get_suite(suite, session=None):
2065     """
2066     Returns Suite object for given C{suite name}.
2067
2068     @type suite: string
2069     @param suite: The name of the suite
2070
2071     @type session: Session
2072     @param session: Optional SQLA session object (a temporary one will be
2073     generated if not supplied)
2074
2075     @rtype: Suite
2076     @return: Suite object for the requested suite name (None if not present)
2077     """
2078
2079     q = session.query(Suite).filter_by(suite_name=suite)
2080
2081     try:
2082         return q.one()
2083     except NoResultFound:
2084         return None
2085
2086 __all__.append('get_suite')
2087
2088 ################################################################################
2089
2090 class SuiteArchitecture(object):
2091     def __init__(self, *args, **kwargs):
2092         pass
2093
2094     def __repr__(self):
2095         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2096
2097 __all__.append('SuiteArchitecture')
2098
2099 @session_wrapper
2100 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2101     """
2102     Returns list of Architecture objects for given C{suite} name
2103
2104     @type source: str
2105     @param source: Suite name to search for
2106
2107     @type skipsrc: boolean
2108     @param skipsrc: Whether to skip returning the 'source' architecture entry
2109     (Default False)
2110
2111     @type skipall: boolean
2112     @param skipall: Whether to skip returning the 'all' architecture entry
2113     (Default False)
2114
2115     @type session: Session
2116     @param session: Optional SQL session object (a temporary one will be
2117     generated if not supplied)
2118
2119     @rtype: list
2120     @return: list of Architecture objects for the given name (may be empty)
2121     """
2122
2123     q = session.query(Architecture)
2124     q = q.join(SuiteArchitecture)
2125     q = q.join(Suite).filter_by(suite_name=suite)
2126
2127     if skipsrc:
2128         q = q.filter(Architecture.arch_string != 'source')
2129
2130     if skipall:
2131         q = q.filter(Architecture.arch_string != 'all')
2132
2133     q = q.order_by('arch_string')
2134
2135     return q.all()
2136
2137 __all__.append('get_suite_architectures')
2138
2139 ################################################################################
2140
2141 class SuiteSrcFormat(object):
2142     def __init__(self, *args, **kwargs):
2143         pass
2144
2145     def __repr__(self):
2146         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2147
2148 __all__.append('SuiteSrcFormat')
2149
2150 @session_wrapper
2151 def get_suite_src_formats(suite, session=None):
2152     """
2153     Returns list of allowed SrcFormat for C{suite}.
2154
2155     @type suite: str
2156     @param suite: Suite name to search for
2157
2158     @type session: Session
2159     @param session: Optional SQL session object (a temporary one will be
2160     generated if not supplied)
2161
2162     @rtype: list
2163     @return: the list of allowed source formats for I{suite}
2164     """
2165
2166     q = session.query(SrcFormat)
2167     q = q.join(SuiteSrcFormat)
2168     q = q.join(Suite).filter_by(suite_name=suite)
2169     q = q.order_by('format_name')
2170
2171     return q.all()
2172
2173 __all__.append('get_suite_src_formats')
2174
2175 ################################################################################
2176
2177 class Uid(object):
2178     def __init__(self, *args, **kwargs):
2179         pass
2180
2181     def __eq__(self, val):
2182         if isinstance(val, str):
2183             return (self.uid == val)
2184         # This signals to use the normal comparison operator
2185         return NotImplemented
2186
2187     def __ne__(self, val):
2188         if isinstance(val, str):
2189             return (self.uid != val)
2190         # This signals to use the normal comparison operator
2191         return NotImplemented
2192
2193     def __repr__(self):
2194         return '<Uid %s (%s)>' % (self.uid, self.name)
2195
2196 __all__.append('Uid')
2197
2198 @session_wrapper
2199 def add_database_user(uidname, session=None):
2200     """
2201     Adds a database user
2202
2203     @type uidname: string
2204     @param uidname: The uid of the user to add
2205
2206     @type session: SQLAlchemy
2207     @param session: Optional SQL session object (a temporary one will be
2208     generated if not supplied).  If not passed, a commit will be performed at
2209     the end of the function, otherwise the caller is responsible for commiting.
2210
2211     @rtype: Uid
2212     @return: the uid object for the given uidname
2213     """
2214
2215     session.execute("CREATE USER :uid", {'uid': uidname})
2216     session.commit_or_flush()
2217
2218 __all__.append('add_database_user')
2219
2220 @session_wrapper
2221 def get_or_set_uid(uidname, session=None):
2222     """
2223     Returns uid object for given uidname.
2224
2225     If no matching uidname is found, a row is inserted.
2226
2227     @type uidname: string
2228     @param uidname: The uid to add
2229
2230     @type session: SQLAlchemy
2231     @param session: Optional SQL session object (a temporary one will be
2232     generated if not supplied).  If not passed, a commit will be performed at
2233     the end of the function, otherwise the caller is responsible for commiting.
2234
2235     @rtype: Uid
2236     @return: the uid object for the given uidname
2237     """
2238
2239     q = session.query(Uid).filter_by(uid=uidname)
2240
2241     try:
2242         ret = q.one()
2243     except NoResultFound:
2244         uid = Uid()
2245         uid.uid = uidname
2246         session.add(uid)
2247         session.commit_or_flush()
2248         ret = uid
2249
2250     return ret
2251
2252 __all__.append('get_or_set_uid')
2253
2254 @session_wrapper
2255 def get_uid_from_fingerprint(fpr, session=None):
2256     q = session.query(Uid)
2257     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2258
2259     try:
2260         return q.one()
2261     except NoResultFound:
2262         return None
2263
2264 __all__.append('get_uid_from_fingerprint')
2265
2266 ################################################################################
2267
2268 class UploadBlock(object):
2269     def __init__(self, *args, **kwargs):
2270         pass
2271
2272     def __repr__(self):
2273         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2274
2275 __all__.append('UploadBlock')
2276
2277 ################################################################################
2278
2279 class DBConn(Singleton):
2280     """
2281     database module init.
2282     """
2283     def __init__(self, *args, **kwargs):
2284         super(DBConn, self).__init__(*args, **kwargs)
2285
2286     def _startup(self, *args, **kwargs):
2287         self.debug = False
2288         if kwargs.has_key('debug'):
2289             self.debug = True
2290         self.__createconn()
2291
2292     def __setuptables(self):
2293         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2294         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2295         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2296         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2297         self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2298         self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2299         self.tbl_component = Table('component', self.db_meta, autoload=True)
2300         self.tbl_config = Table('config', self.db_meta, autoload=True)
2301         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2302         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2303         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2304         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2305         self.tbl_files = Table('files', self.db_meta, autoload=True)
2306         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2307         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2308         self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2309         self.tbl_location = Table('location', self.db_meta, autoload=True)
2310         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2311         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2312         self.tbl_override = Table('override', self.db_meta, autoload=True)
2313         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2314         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2315         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2316         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2317         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2318         self.tbl_section = Table('section', self.db_meta, autoload=True)
2319         self.tbl_source = Table('source', self.db_meta, autoload=True)
2320         self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2321         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2322         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2323         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2324         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2325         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2326         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2327         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2328         self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2329
2330     def __setupmappers(self):
2331         mapper(Architecture, self.tbl_architecture,
2332                properties = dict(arch_id = self.tbl_architecture.c.id))
2333
2334         mapper(Archive, self.tbl_archive,
2335                properties = dict(archive_id = self.tbl_archive.c.id,
2336                                  archive_name = self.tbl_archive.c.name))
2337
2338         mapper(BinAssociation, self.tbl_bin_associations,
2339                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2340                                  suite_id = self.tbl_bin_associations.c.suite,
2341                                  suite = relation(Suite),
2342                                  binary_id = self.tbl_bin_associations.c.bin,
2343                                  binary = relation(DBBinary)))
2344
2345
2346         mapper(DBBinary, self.tbl_binaries,
2347                properties = dict(binary_id = self.tbl_binaries.c.id,
2348                                  package = self.tbl_binaries.c.package,
2349                                  version = self.tbl_binaries.c.version,
2350                                  maintainer_id = self.tbl_binaries.c.maintainer,
2351                                  maintainer = relation(Maintainer),
2352                                  source_id = self.tbl_binaries.c.source,
2353                                  source = relation(DBSource),
2354                                  arch_id = self.tbl_binaries.c.architecture,
2355                                  architecture = relation(Architecture),
2356                                  poolfile_id = self.tbl_binaries.c.file,
2357                                  poolfile = relation(PoolFile),
2358                                  binarytype = self.tbl_binaries.c.type,
2359                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2360                                  fingerprint = relation(Fingerprint),
2361                                  install_date = self.tbl_binaries.c.install_date,
2362                                  binassociations = relation(BinAssociation,
2363                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2364
2365         mapper(BinaryACL, self.tbl_binary_acl,
2366                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2367
2368         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2369                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2370                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2371                                  architecture = relation(Architecture)))
2372
2373         mapper(Component, self.tbl_component,
2374                properties = dict(component_id = self.tbl_component.c.id,
2375                                  component_name = self.tbl_component.c.name))
2376
2377         mapper(DBConfig, self.tbl_config,
2378                properties = dict(config_id = self.tbl_config.c.id))
2379
2380         mapper(DSCFile, self.tbl_dsc_files,
2381                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2382                                  source_id = self.tbl_dsc_files.c.source,
2383                                  source = relation(DBSource),
2384                                  poolfile_id = self.tbl_dsc_files.c.file,
2385                                  poolfile = relation(PoolFile)))
2386
2387         mapper(PoolFile, self.tbl_files,
2388                properties = dict(file_id = self.tbl_files.c.id,
2389                                  filesize = self.tbl_files.c.size,
2390                                  location_id = self.tbl_files.c.location,
2391                                  location = relation(Location)))
2392
2393         mapper(Fingerprint, self.tbl_fingerprint,
2394                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2395                                  uid_id = self.tbl_fingerprint.c.uid,
2396                                  uid = relation(Uid),
2397                                  keyring_id = self.tbl_fingerprint.c.keyring,
2398                                  keyring = relation(Keyring),
2399                                  source_acl = relation(SourceACL),
2400                                  binary_acl = relation(BinaryACL)))
2401
2402         mapper(Keyring, self.tbl_keyrings,
2403                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2404                                  keyring_id = self.tbl_keyrings.c.id))
2405
2406         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2407                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2408                                  keyring = relation(Keyring, backref="keyring_acl_map"),
2409                                  architecture = relation(Architecture)))
2410
2411         mapper(Location, self.tbl_location,
2412                properties = dict(location_id = self.tbl_location.c.id,
2413                                  component_id = self.tbl_location.c.component,
2414                                  component = relation(Component),
2415                                  archive_id = self.tbl_location.c.archive,
2416                                  archive = relation(Archive),
2417                                  archive_type = self.tbl_location.c.type))
2418
2419         mapper(Maintainer, self.tbl_maintainer,
2420                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2421
2422         mapper(NewComment, self.tbl_new_comments,
2423                properties = dict(comment_id = self.tbl_new_comments.c.id))
2424
2425         mapper(Override, self.tbl_override,
2426                properties = dict(suite_id = self.tbl_override.c.suite,
2427                                  suite = relation(Suite),
2428                                  component_id = self.tbl_override.c.component,
2429                                  component = relation(Component),
2430                                  priority_id = self.tbl_override.c.priority,
2431                                  priority = relation(Priority),
2432                                  section_id = self.tbl_override.c.section,
2433                                  section = relation(Section),
2434                                  overridetype_id = self.tbl_override.c.type,
2435                                  overridetype = relation(OverrideType)))
2436
2437         mapper(OverrideType, self.tbl_override_type,
2438                properties = dict(overridetype = self.tbl_override_type.c.type,
2439                                  overridetype_id = self.tbl_override_type.c.id))
2440
2441         mapper(Priority, self.tbl_priority,
2442                properties = dict(priority_id = self.tbl_priority.c.id))
2443
2444         mapper(Queue, self.tbl_queue,
2445                properties = dict(queue_id = self.tbl_queue.c.id))
2446
2447         mapper(QueueBuild, self.tbl_queue_build,
2448                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2449                                  queue_id = self.tbl_queue_build.c.queue,
2450                                  queue = relation(Queue, backref='queuebuild')))
2451
2452         mapper(Section, self.tbl_section,
2453                properties = dict(section_id = self.tbl_section.c.id))
2454
2455         mapper(DBSource, self.tbl_source,
2456                properties = dict(source_id = self.tbl_source.c.id,
2457                                  version = self.tbl_source.c.version,
2458                                  maintainer_id = self.tbl_source.c.maintainer,
2459                                  maintainer = relation(Maintainer,
2460                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2461                                  poolfile_id = self.tbl_source.c.file,
2462                                  poolfile = relation(PoolFile),
2463                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2464                                  fingerprint = relation(Fingerprint),
2465                                  changedby_id = self.tbl_source.c.changedby,
2466                                  changedby = relation(Maintainer,
2467                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2468                                  srcfiles = relation(DSCFile,
2469                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2470                                  srcassociations = relation(SrcAssociation,
2471                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2472                                  srcuploaders = relation(SrcUploader)))
2473
2474         mapper(SourceACL, self.tbl_source_acl,
2475                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2476
2477         mapper(SrcAssociation, self.tbl_src_associations,
2478                properties = dict(sa_id = self.tbl_src_associations.c.id,
2479                                  suite_id = self.tbl_src_associations.c.suite,
2480                                  suite = relation(Suite),
2481                                  source_id = self.tbl_src_associations.c.source,
2482                                  source = relation(DBSource)))
2483
2484         mapper(SrcFormat, self.tbl_src_format,
2485                properties = dict(src_format_id = self.tbl_src_format.c.id,
2486                                  format_name = self.tbl_src_format.c.format_name))
2487
2488         mapper(SrcUploader, self.tbl_src_uploaders,
2489                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2490                                  source_id = self.tbl_src_uploaders.c.source,
2491                                  source = relation(DBSource,
2492                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2493                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2494                                  maintainer = relation(Maintainer,
2495                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2496
2497         mapper(Suite, self.tbl_suite,
2498                properties = dict(suite_id = self.tbl_suite.c.id))
2499
2500         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2501                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2502                                  suite = relation(Suite, backref='suitearchitectures'),
2503                                  arch_id = self.tbl_suite_architectures.c.architecture,
2504                                  architecture = relation(Architecture)))
2505
2506         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2507                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2508                                  suite = relation(Suite, backref='suitesrcformats'),
2509                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2510                                  src_format = relation(SrcFormat)))
2511
2512         mapper(Uid, self.tbl_uid,
2513                properties = dict(uid_id = self.tbl_uid.c.id,
2514                                  fingerprint = relation(Fingerprint)))
2515
2516         mapper(UploadBlock, self.tbl_upload_blocks,
2517                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2518                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
2519                                  uid = relation(Uid, backref="uploadblocks")))
2520
2521     ## Connection functions
2522     def __createconn(self):
2523         from config import Config
2524         cnf = Config()
2525         if cnf["DB::Host"]:
2526             # TCP/IP
2527             connstr = "postgres://%s" % cnf["DB::Host"]
2528             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2529                 connstr += ":%s" % cnf["DB::Port"]
2530             connstr += "/%s" % cnf["DB::Name"]
2531         else:
2532             # Unix Socket
2533             connstr = "postgres:///%s" % cnf["DB::Name"]
2534             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2535                 connstr += "?port=%s" % cnf["DB::Port"]
2536
2537         self.db_pg   = create_engine(connstr, echo=self.debug)
2538         self.db_meta = MetaData()
2539         self.db_meta.bind = self.db_pg
2540         self.db_smaker = sessionmaker(bind=self.db_pg,
2541                                       autoflush=True,
2542                                       autocommit=False)
2543
2544         self.__setuptables()
2545         self.__setupmappers()
2546
2547     def session(self):
2548         return self.db_smaker()
2549
2550 __all__.append('DBConn')
2551
2552