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