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