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