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