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