]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
22fa262cae20cd68f7a68a78b717d8752b6b5d8a
[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 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
607     """
608     Returns a list of DSCFiles which may be empty
609
610     @type dscfile_id: int (optional)
611     @param dscfile_id: the dscfile_id of the DSCFiles to find
612
613     @type source_id: int (optional)
614     @param source_id: the source id related to the DSCFiles to find
615
616     @type poolfile_id: int (optional)
617     @param poolfile_id: the poolfile id related to the DSCFiles to find
618
619     @rtype: list
620     @return: Possibly empty list of DSCFiles
621     """
622
623     if session is None:
624         session = DBConn().session()
625
626     q = session.query(DSCFile)
627
628     if dscfile_id is not None:
629         q = q.filter_by(dscfile_id=dscfile_id)
630
631     if source_id is not None:
632         q = q.filter_by(source_id=source_id)
633
634     if poolfile_id is not None:
635         q = q.filter_by(poolfile_id=poolfile_id)
636
637     return q.all()
638
639 __all__.append('get_dscfiles')
640
641 ################################################################################
642
643 class PoolFile(object):
644     def __init__(self, *args, **kwargs):
645         pass
646
647     def __repr__(self):
648         return '<PoolFile %s>' % self.filename
649
650 __all__.append('PoolFile')
651
652 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
653     """
654     Returns a tuple:
655      (ValidFileFound [boolean or None], PoolFile object or None)
656
657     @type filename: string
658     @param filename: the filename of the file to check against the DB
659
660     @type filesize: int
661     @param filesize: the size of the file to check against the DB
662
663     @type md5sum: string
664     @param md5sum: the md5sum of the file to check against the DB
665
666     @type location_id: int
667     @param location_id: the id of the location to look in
668
669     @rtype: tuple
670     @return: Tuple of length 2.
671              If more than one file found with that name:
672                     (None,  None)
673              If valid pool file found: (True, PoolFile object)
674              If valid pool file not found:
675                     (False, None) if no file found
676                     (False, PoolFile object) if file found with size/md5sum mismatch
677     """
678
679     if session is None:
680         session = DBConn().session()
681
682     q = session.query(PoolFile).filter_by(filename=filename)
683     q = q.join(Location).filter_by(location_id=location_id)
684
685     if q.count() > 1:
686         return (None, None)
687     if q.count() < 1:
688         return (False, None)
689
690     obj = q.one()
691     if obj.md5sum != md5sum or obj.filesize != filesize:
692         return (False, obj)
693
694     return (True, obj)
695
696 __all__.append('check_poolfile')
697
698 def get_poolfile_by_id(file_id, session=None):
699     """
700     Returns a PoolFile objects or None for the given id
701
702     @type file_id: int
703     @param file_id: the id of the file to look for
704
705     @rtype: PoolFile or None
706     @return: either the PoolFile object or None
707     """
708
709     if session is None:
710         session = DBConn().session()
711
712     q = session.query(PoolFile).filter_by(file_id=file_id)
713
714     if q.count() > 0:
715         return q.one()
716
717     return None
718
719 __all__.append('get_poolfile_by_id')
720
721
722 def get_poolfile_by_name(filename, location_id=None, session=None):
723     """
724     Returns an array of PoolFile objects for the given filename and
725     (optionally) location_id
726
727     @type filename: string
728     @param filename: the filename of the file to check against the DB
729
730     @type location_id: int
731     @param location_id: the id of the location to look in (optional)
732
733     @rtype: array
734     @return: array of PoolFile objects
735     """
736
737     if session is None:
738         session = DBConn().session()
739
740     q = session.query(PoolFile).filter_by(filename=filename)
741
742     if location_id is not None:
743         q = q.join(Location).filter_by(location_id=location_id)
744
745     return q.all()
746
747 __all__.append('get_poolfile_by_name')
748
749 def get_poolfile_like_name(filename, session=None):
750     """
751     Returns an array of PoolFile objects which are like the given name
752
753     @type filename: string
754     @param filename: the filename of the file to check against the DB
755
756     @rtype: array
757     @return: array of PoolFile objects
758     """
759
760     if session is None:
761         session = DBConn().session()
762
763     # TODO: There must be a way of properly using bind parameters with %FOO%
764     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
765
766     return q.all()
767
768 __all__.append('get_poolfile_like_name')
769
770 ################################################################################
771
772 class Fingerprint(object):
773     def __init__(self, *args, **kwargs):
774         pass
775
776     def __repr__(self):
777         return '<Fingerprint %s>' % self.fingerprint
778
779 __all__.append('Fingerprint')
780
781 def get_or_set_fingerprint(fpr, session=None):
782     """
783     Returns Fingerprint object for given fpr.
784
785     If no matching fpr is found, a row is inserted.
786
787     @type fpr: string
788     @param fpr: The fpr to find / add
789
790     @type session: SQLAlchemy
791     @param session: Optional SQL session object (a temporary one will be
792     generated if not supplied).  If not passed, a commit will be performed at
793     the end of the function, otherwise the caller is responsible for commiting.
794     A flush will be performed either way.
795
796     @rtype: Fingerprint
797     @return: the Fingerprint object for the given fpr
798     """
799     privatetrans = False
800     if session is None:
801         session = DBConn().session()
802         privatetrans = True
803
804     try:
805         q = session.query(Fingerprint).filter_by(fingerprint=fpr)
806         if q.count() < 1:
807             fingerprint = Fingerprint()
808             fingerprint.fingerprint = fpr
809             session.add(fingerprint)
810             if privatetrans:
811                 session.commit()
812             else:
813                 session.flush()
814             return fingerprint
815         else:
816             return q.one()
817
818     except:
819         traceback.print_exc()
820         raise
821
822 __all__.append('get_or_set_fingerprint')
823
824 ################################################################################
825
826 class Keyring(object):
827     def __init__(self, *args, **kwargs):
828         pass
829
830     def __repr__(self):
831         return '<Keyring %s>' % self.keyring_name
832
833 __all__.append('Keyring')
834
835 ################################################################################
836
837 class Location(object):
838     def __init__(self, *args, **kwargs):
839         pass
840
841     def __repr__(self):
842         return '<Location %s (%s)>' % (self.path, self.location_id)
843
844 __all__.append('Location')
845
846 def get_location(location, component=None, archive=None, session=None):
847     """
848     Returns Location object for the given combination of location, component
849     and archive
850
851     @type location: string
852     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
853
854     @type component: string
855     @param component: the component name (if None, no restriction applied)
856
857     @type archive: string
858     @param archive_id: the archive name (if None, no restriction applied)
859
860     @rtype: Location / None
861     @return: Either a Location object or None if one can't be found
862     """
863
864     if session is None:
865         session = DBConn().session()
866
867     q = session.query(Location).filter_by(path=location)
868
869     if archive is not None:
870         q = q.join(Archive).filter_by(archive_name=archive)
871
872     if component is not None:
873         q = q.join(Component).filter_by(component_name=component)
874
875     if q.count() < 1:
876         return None
877     else:
878         return q.one()
879
880 __all__.append('get_location')
881
882 ################################################################################
883
884 class Maintainer(object):
885     def __init__(self, *args, **kwargs):
886         pass
887
888     def __repr__(self):
889         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
890
891     def get_split_maintainer(self):
892         if not hasattr(self, 'name') or self.name is None:
893             return ('', '', '', '')
894
895         return fix_maintainer(self.name.strip())
896
897 __all__.append('Maintainer')
898
899 def get_or_set_maintainer(name, session=None):
900     """
901     Returns Maintainer object for given maintainer name.
902
903     If no matching maintainer name is found, a row is inserted.
904
905     @type name: string
906     @param name: The maintainer name to add
907
908     @type session: SQLAlchemy
909     @param session: Optional SQL session object (a temporary one will be
910     generated if not supplied).  If not passed, a commit will be performed at
911     the end of the function, otherwise the caller is responsible for commiting.
912     A flush will be performed either way.
913
914     @rtype: Maintainer
915     @return: the Maintainer object for the given maintainer
916     """
917     privatetrans = False
918     if session is None:
919         session = DBConn().session()
920         privatetrans = True
921
922     try:
923         q = session.query(Maintainer).filter_by(name=name)
924         if q.count() < 1:
925             maintainer = Maintainer()
926             maintainer.name = name
927             session.add(maintainer)
928             if privatetrans:
929                 session.commit()
930             else:
931                 session.flush()
932             return maintainer
933         else:
934             return q.one()
935
936     except:
937         traceback.print_exc()
938         raise
939
940 __all__.append('get_or_set_maintainer')
941
942 ################################################################################
943
944 class NewComment(object):
945     def __init__(self, *args, **kwargs):
946         pass
947
948     def __repr__(self):
949         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
950
951 __all__.append('NewComment')
952
953 def has_new_comment(package, version, session=None):
954     """
955     Returns true if the given combination of C{package}, C{version} has a comment.
956
957     @type package: string
958     @param package: name of the package
959
960     @type version: string
961     @param version: package version
962
963     @type session: Session
964     @param session: Optional SQLA session object (a temporary one will be
965     generated if not supplied)
966
967     @rtype: boolean
968     @return: true/false
969     """
970
971     if session is None:
972         session = DBConn().session()
973
974     q = session.query(NewComment)
975     q = q.filter_by(package=package)
976     q = q.filter_by(version=version)
977     return q.count() > 0
978
979 __all__.append('has_new_comment')
980
981 def get_new_comments(package=None, version=None, comment_id=None, session=None):
982     """
983     Returns (possibly empty) list of NewComment objects for the given
984     parameters
985
986     @type package: string (optional)
987     @param package: name of the package
988
989     @type version: string (optional)
990     @param version: package version
991
992     @type comment_id: int (optional)
993     @param comment_id: An id of a comment
994
995     @type session: Session
996     @param session: Optional SQLA session object (a temporary one will be
997     generated if not supplied)
998
999     @rtype: list
1000     @return: A (possibly empty) list of NewComment objects will be returned
1001
1002     """
1003
1004     if session is None:
1005         session = DBConn().session()
1006
1007     q = session.query(NewComment)
1008     if package is not None: q = q.filter_by(package=package)
1009     if version is not None: q = q.filter_by(version=version)
1010     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1011
1012     return q.all()
1013
1014 __all__.append('get_new_comments')
1015
1016 ################################################################################
1017
1018 class Override(object):
1019     def __init__(self, *args, **kwargs):
1020         pass
1021
1022     def __repr__(self):
1023         return '<Override %s (%s)>' % (self.package, self.suite_id)
1024
1025 __all__.append('Override')
1026
1027 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1028     """
1029     Returns Override object for the given parameters
1030
1031     @type package: string
1032     @param package: The name of the package
1033
1034     @type suite: string, list or None
1035     @param suite: The name of the suite (or suites if a list) to limit to.  If
1036                   None, don't limit.  Defaults to None.
1037
1038     @type component: string, list or None
1039     @param component: The name of the component (or components if a list) to
1040                       limit to.  If None, don't limit.  Defaults to None.
1041
1042     @type overridetype: string, list or None
1043     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1044                          limit to.  If None, don't limit.  Defaults to None.
1045
1046     @type session: Session
1047     @param session: Optional SQLA session object (a temporary one will be
1048     generated if not supplied)
1049
1050     @rtype: list
1051     @return: A (possibly empty) list of Override objects will be returned
1052
1053     """
1054     if session is None:
1055         session = DBConn().session()
1056
1057     q = session.query(Override)
1058     q = q.filter_by(package=package)
1059
1060     if suite is not None:
1061         if not isinstance(suite, list): suite = [suite]
1062         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1063
1064     if component is not None:
1065         if not isinstance(component, list): component = [component]
1066         q = q.join(Component).filter(Component.component_name.in_(component))
1067
1068     if overridetype is not None:
1069         if not isinstance(overridetype, list): overridetype = [overridetype]
1070         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1071
1072     return q.all()
1073
1074 __all__.append('get_override')
1075
1076
1077 ################################################################################
1078
1079 class OverrideType(object):
1080     def __init__(self, *args, **kwargs):
1081         pass
1082
1083     def __repr__(self):
1084         return '<OverrideType %s>' % self.overridetype
1085
1086 __all__.append('OverrideType')
1087
1088 def get_override_type(override_type, session=None):
1089     """
1090     Returns OverrideType object for given C{override type}.
1091
1092     @type override_type: string
1093     @param override_type: The name of the override type
1094
1095     @type session: Session
1096     @param session: Optional SQLA session object (a temporary one will be
1097     generated if not supplied)
1098
1099     @rtype: int
1100     @return: the database id for the given override type
1101
1102     """
1103     if session is None:
1104         session = DBConn().session()
1105     q = session.query(OverrideType).filter_by(overridetype=override_type)
1106     if q.count() == 0:
1107         return None
1108     return q.one()
1109
1110 __all__.append('get_override_type')
1111
1112 ################################################################################
1113
1114 class PendingContentAssociation(object):
1115     def __init__(self, *args, **kwargs):
1116         pass
1117
1118     def __repr__(self):
1119         return '<PendingContentAssociation %s>' % self.pca_id
1120
1121 __all__.append('PendingContentAssociation')
1122
1123 def insert_pending_content_paths(package, fullpaths, session=None):
1124     """
1125     Make sure given paths are temporarily associated with given
1126     package
1127
1128     @type package: dict
1129     @param package: the package to associate with should have been read in from the binary control file
1130     @type fullpaths: list
1131     @param fullpaths: the list of paths of the file being associated with the binary
1132     @type session: SQLAlchemy session
1133     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1134     is responsible for ensuring a transaction has begun and committing the
1135     results or rolling back based on the result code.  If not passed, a commit
1136     will be performed at the end of the function
1137
1138     @return: True upon success, False if there is a problem
1139     """
1140
1141     privatetrans = False
1142
1143     if session is None:
1144         session = DBConn().session()
1145         privatetrans = True
1146
1147     try:
1148         arch = get_architecture(package['Architecture'], session)
1149         arch_id = arch.arch_id
1150
1151         # Remove any already existing recorded files for this package
1152         q = session.query(PendingContentAssociation)
1153         q = q.filter_by(package=package['Package'])
1154         q = q.filter_by(version=package['Version'])
1155         q = q.filter_by(architecture=arch_id)
1156         q.delete()
1157
1158         # Insert paths
1159         for fullpath in fullpaths:
1160             (path, file) = os.path.split(fullpath)
1161
1162             if path.startswith( "./" ):
1163                 path = path[2:]
1164
1165             pca = PendingContentAssociation()
1166             pca.package = package['Package']
1167             pca.version = package['Version']
1168             pca.filename_id = get_or_set_contents_file_id(file, session)
1169             pca.filepath_id = get_or_set_contents_path_id(path, session)
1170             pca.architecture = arch_id
1171             session.add(pca)
1172
1173         # Only commit if we set up the session ourself
1174         if privatetrans:
1175             session.commit()
1176
1177         return True
1178     except:
1179         traceback.print_exc()
1180
1181         # Only rollback if we set up the session ourself
1182         if privatetrans:
1183             session.rollback()
1184
1185         return False
1186
1187 __all__.append('insert_pending_content_paths')
1188
1189 ################################################################################
1190
1191 class Priority(object):
1192     def __init__(self, *args, **kwargs):
1193         pass
1194
1195     def __eq__(self, val):
1196         if isinstance(val, str):
1197             return (self.priority == val)
1198         # This signals to use the normal comparison operator
1199         return NotImplemented
1200
1201     def __ne__(self, val):
1202         if isinstance(val, str):
1203             return (self.priority != val)
1204         # This signals to use the normal comparison operator
1205         return NotImplemented
1206
1207     def __repr__(self):
1208         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1209
1210 __all__.append('Priority')
1211
1212 def get_priority(priority, session=None):
1213     """
1214     Returns Priority object for given C{priority name}.
1215
1216     @type priority: string
1217     @param priority: The name of the priority
1218
1219     @type session: Session
1220     @param session: Optional SQLA session object (a temporary one will be
1221     generated if not supplied)
1222
1223     @rtype: Priority
1224     @return: Priority object for the given priority
1225
1226     """
1227     if session is None:
1228         session = DBConn().session()
1229     q = session.query(Priority).filter_by(priority=priority)
1230     if q.count() == 0:
1231         return None
1232     return q.one()
1233
1234 __all__.append('get_priority')
1235
1236 def get_priorities(session=None):
1237     """
1238     Returns dictionary of priority names -> id mappings
1239
1240     @type session: Session
1241     @param session: Optional SQL session object (a temporary one will be
1242     generated if not supplied)
1243
1244     @rtype: dictionary
1245     @return: dictionary of priority names -> id mappings
1246     """
1247     if session is None:
1248         session = DBConn().session()
1249
1250     ret = {}
1251     q = session.query(Priority)
1252     for x in q.all():
1253         ret[x.priority] = x.priority_id
1254
1255     return ret
1256
1257 __all__.append('get_priorities')
1258
1259 ################################################################################
1260
1261 class Queue(object):
1262     def __init__(self, *args, **kwargs):
1263         pass
1264
1265     def __repr__(self):
1266         return '<Queue %s>' % self.queue_name
1267
1268     def autobuild_upload(self, changes, srcpath, session=None):
1269         """
1270         Update queue_build database table used for incoming autobuild support.
1271
1272         @type changes: Changes
1273         @param changes: changes object for the upload to process
1274
1275         @type srcpath: string
1276         @param srcpath: path for the queue file entries/link destinations
1277
1278         @type session: SQLAlchemy session
1279         @param session: Optional SQLAlchemy session.  If this is passed, the
1280         caller is responsible for ensuring a transaction has begun and
1281         committing the results or rolling back based on the result code.  If
1282         not passed, a commit will be performed at the end of the function,
1283         otherwise the caller is responsible for commiting.
1284
1285         @rtype: NoneType or string
1286         @return: None if the operation failed, a string describing the error if not
1287         """
1288
1289         localcommit = False
1290         if session is None:
1291             session = DBConn().session()
1292             localcommit = True
1293
1294         # TODO: Remove by moving queue config into the database
1295         conf = Config()
1296
1297         for suitename in changes.changes["distribution"].keys():
1298             # TODO: Move into database as:
1299             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1300             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1301             #       This also gets rid of the SecurityQueueBuild hack below
1302             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1303                 continue
1304
1305             # Find suite object
1306             s = get_suite(suitename, session)
1307             if s is None:
1308                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1309
1310             # TODO: Get from database as above
1311             dest_dir = conf["Dir::QueueBuild"]
1312
1313             # TODO: Move into database as above
1314             if conf.FindB("Dinstall::SecurityQueueBuild"):
1315                 dest_dir = os.path.join(dest_dir, suitename)
1316
1317             for file_entry in changes.files.keys():
1318                 src = os.path.join(srcpath, file_entry)
1319                 dest = os.path.join(dest_dir, file_entry)
1320
1321                 # TODO: Move into database as above
1322                 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1323                     # Copy it since the original won't be readable by www-data
1324                     utils.copy(src, dest)
1325                 else:
1326                     # Create a symlink to it
1327                     os.symlink(src, dest)
1328
1329                 qb = QueueBuild()
1330                 qb.suite_id = s.suite_id
1331                 qb.queue_id = self.queue_id
1332                 qb.filename = dest
1333                 qb.in_queue = True
1334
1335                 session.add(qb)
1336
1337             # If the .orig.tar.gz is in the pool, create a symlink to
1338             # it (if one doesn't already exist)
1339             if changes.orig_tar_id:
1340                 # Determine the .orig.tar.gz file name
1341                 for dsc_file in changes.dsc_files.keys():
1342                     if dsc_file.endswith(".orig.tar.gz"):
1343                         filename = dsc_file
1344
1345                 dest = os.path.join(dest_dir, filename)
1346
1347                 # If it doesn't exist, create a symlink
1348                 if not os.path.exists(dest):
1349                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1350                                         {'id': changes.orig_tar_id})
1351                     res = q.fetchone()
1352                     if not res:
1353                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1354
1355                     src = os.path.join(res[0], res[1])
1356                     os.symlink(src, dest)
1357
1358                     # Add it to the list of packages for later processing by apt-ftparchive
1359                     qb = QueueBuild()
1360                     qb.suite_id = s.suite_id
1361                     qb.queue_id = self.queue_id
1362                     qb.filename = dest
1363                     qb.in_queue = True
1364                     session.add(qb)
1365
1366                 # If it does, update things to ensure it's not removed prematurely
1367                 else:
1368                     qb = get_queue_build(dest, suite_id, session)
1369                     if qb is None:
1370                         qb.in_queue = True
1371                         qb.last_used = None
1372                         session.add(qb)
1373
1374         if localcommit:
1375             session.commit()
1376
1377         return None
1378
1379 __all__.append('Queue')
1380
1381 def get_queue(queuename, session=None):
1382     """
1383     Returns Queue object for given C{queue name}.
1384
1385     @type queuename: string
1386     @param queuename: The name of the queue
1387
1388     @type session: Session
1389     @param session: Optional SQLA session object (a temporary one will be
1390     generated if not supplied)
1391
1392     @rtype: Queue
1393     @return: Queue object for the given queue
1394
1395     """
1396     if session is None:
1397         session = DBConn().session()
1398     q = session.query(Queue).filter_by(queue_name=queuename)
1399     if q.count() == 0:
1400         return None
1401     return q.one()
1402
1403 __all__.append('get_queue')
1404
1405 ################################################################################
1406
1407 class QueueBuild(object):
1408     def __init__(self, *args, **kwargs):
1409         pass
1410
1411     def __repr__(self):
1412         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1413
1414 __all__.append('QueueBuild')
1415
1416 def get_queue_build(filename, suite, session=None):
1417     """
1418     Returns QueueBuild object for given C{filename} and C{suite}.
1419
1420     @type filename: string
1421     @param filename: The name of the file
1422
1423     @type suiteid: int or str
1424     @param suiteid: Suite name or ID
1425
1426     @type session: Session
1427     @param session: Optional SQLA session object (a temporary one will be
1428     generated if not supplied)
1429
1430     @rtype: Queue
1431     @return: Queue object for the given queue
1432
1433     """
1434     if session is None:
1435         session = DBConn().session()
1436     if isinstance(suite, int):
1437         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1438     else:
1439         q = session.query(QueueBuild).filter_by(filename=filename)
1440         q = q.join(Suite).filter_by(suite_name=suite)
1441
1442     if q.count() == 0:
1443         return None
1444     return q.one()
1445
1446 __all__.append('get_queue_build')
1447
1448 ################################################################################
1449
1450 class Section(object):
1451     def __init__(self, *args, **kwargs):
1452         pass
1453
1454     def __eq__(self, val):
1455         if isinstance(val, str):
1456             return (self.section == val)
1457         # This signals to use the normal comparison operator
1458         return NotImplemented
1459
1460     def __ne__(self, val):
1461         if isinstance(val, str):
1462             return (self.section != val)
1463         # This signals to use the normal comparison operator
1464         return NotImplemented
1465
1466     def __repr__(self):
1467         return '<Section %s>' % self.section
1468
1469 __all__.append('Section')
1470
1471 def get_section(section, session=None):
1472     """
1473     Returns Section object for given C{section name}.
1474
1475     @type section: string
1476     @param section: The name of the section
1477
1478     @type session: Session
1479     @param session: Optional SQLA session object (a temporary one will be
1480     generated if not supplied)
1481
1482     @rtype: Section
1483     @return: Section object for the given section name
1484
1485     """
1486     if session is None:
1487         session = DBConn().session()
1488     q = session.query(Section).filter_by(section=section)
1489     if q.count() == 0:
1490         return None
1491     return q.one()
1492
1493 __all__.append('get_section')
1494
1495 def get_sections(session=None):
1496     """
1497     Returns dictionary of section names -> id mappings
1498
1499     @type session: Session
1500     @param session: Optional SQL session object (a temporary one will be
1501     generated if not supplied)
1502
1503     @rtype: dictionary
1504     @return: dictionary of section names -> id mappings
1505     """
1506     if session is None:
1507         session = DBConn().session()
1508
1509     ret = {}
1510     q = session.query(Section)
1511     for x in q.all():
1512         ret[x.section] = x.section_id
1513
1514     return ret
1515
1516 __all__.append('get_sections')
1517
1518 ################################################################################
1519
1520 class DBSource(object):
1521     def __init__(self, *args, **kwargs):
1522         pass
1523
1524     def __repr__(self):
1525         return '<DBSource %s (%s)>' % (self.source, self.version)
1526
1527 __all__.append('DBSource')
1528
1529 def source_exists(source, source_version, suites = ["any"], session=None):
1530     """
1531     Ensure that source exists somewhere in the archive for the binary
1532     upload being processed.
1533       1. exact match     => 1.0-3
1534       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1535
1536     @type package: string
1537     @param package: package source name
1538
1539     @type source_version: string
1540     @param source_version: expected source version
1541
1542     @type suites: list
1543     @param suites: list of suites to check in, default I{any}
1544
1545     @type session: Session
1546     @param session: Optional SQLA session object (a temporary one will be
1547     generated if not supplied)
1548
1549     @rtype: int
1550     @return: returns 1 if a source with expected version is found, otherwise 0
1551
1552     """
1553
1554     if session is None:
1555         session = DBConn().session()
1556
1557     cnf = Config()
1558
1559     for suite in suites:
1560         q = session.query(DBSource).filter_by(source=source)
1561         if suite != "any":
1562             # source must exist in suite X, or in some other suite that's
1563             # mapped to X, recursively... silent-maps are counted too,
1564             # unreleased-maps aren't.
1565             maps = cnf.ValueList("SuiteMappings")[:]
1566             maps.reverse()
1567             maps = [ m.split() for m in maps ]
1568             maps = [ (x[1], x[2]) for x in maps
1569                             if x[0] == "map" or x[0] == "silent-map" ]
1570             s = [suite]
1571             for x in maps:
1572                 if x[1] in s and x[0] not in s:
1573                     s.append(x[0])
1574
1575             q = q.join(SrcAssociation).join(Suite)
1576             q = q.filter(Suite.suite_name.in_(s))
1577
1578         # Reduce the query results to a list of version numbers
1579         ql = [ j.version for j in q.all() ]
1580
1581         # Try (1)
1582         if source_version in ql:
1583             continue
1584
1585         # Try (2)
1586         from daklib.regexes import re_bin_only_nmu
1587         orig_source_version = re_bin_only_nmu.sub('', source_version)
1588         if orig_source_version in ql:
1589             continue
1590
1591         # No source found so return not ok
1592         return 0
1593
1594     # We're good
1595     return 1
1596
1597 __all__.append('source_exists')
1598
1599 def get_suites_source_in(source, session=None):
1600     """
1601     Returns list of Suite objects which given C{source} name is in
1602
1603     @type source: str
1604     @param source: DBSource package name to search for
1605
1606     @rtype: list
1607     @return: list of Suite objects for the given source
1608     """
1609
1610     if session is None:
1611         session = DBConn().session()
1612
1613     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1614
1615 __all__.append('get_suites_source_in')
1616
1617 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1618     """
1619     Returns list of DBSource objects for given C{source} name and other parameters
1620
1621     @type source: str
1622     @param source: DBSource package name to search for
1623
1624     @type source: str or None
1625     @param source: DBSource version name to search for or None if not applicable
1626
1627     @type dm_upload_allowed: bool
1628     @param dm_upload_allowed: If None, no effect.  If True or False, only
1629     return packages with that dm_upload_allowed setting
1630
1631     @type session: Session
1632     @param session: Optional SQL session object (a temporary one will be
1633     generated if not supplied)
1634
1635     @rtype: list
1636     @return: list of DBSource objects for the given name (may be empty)
1637     """
1638     if session is None:
1639         session = DBConn().session()
1640
1641     q = session.query(DBSource).filter_by(source=source)
1642
1643     if version is not None:
1644         q = q.filter_by(version=version)
1645
1646     if dm_upload_allowed is not None:
1647         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1648
1649     return q.all()
1650
1651 __all__.append('get_sources_from_name')
1652
1653 def get_source_in_suite(source, suite, session=None):
1654     """
1655     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1656
1657       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1658       - B{suite} - a suite name, eg. I{unstable}
1659
1660     @type source: string
1661     @param source: source package name
1662
1663     @type suite: string
1664     @param suite: the suite name
1665
1666     @rtype: string
1667     @return: the version for I{source} in I{suite}
1668
1669     """
1670     if session is None:
1671         session = DBConn().session()
1672     q = session.query(SrcAssociation)
1673     q = q.join('source').filter_by(source=source)
1674     q = q.join('suite').filter_by(suite_name=suite)
1675     if q.count() == 0:
1676         return None
1677     # ???: Maybe we should just return the SrcAssociation object instead
1678     return q.one().source
1679
1680 __all__.append('get_source_in_suite')
1681
1682 ################################################################################
1683
1684 class SrcAssociation(object):
1685     def __init__(self, *args, **kwargs):
1686         pass
1687
1688     def __repr__(self):
1689         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1690
1691 __all__.append('SrcAssociation')
1692
1693 ################################################################################
1694
1695 class SrcUploader(object):
1696     def __init__(self, *args, **kwargs):
1697         pass
1698
1699     def __repr__(self):
1700         return '<SrcUploader %s>' % self.uploader_id
1701
1702 __all__.append('SrcUploader')
1703
1704 ################################################################################
1705
1706 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1707                  ('SuiteID', 'suite_id'),
1708                  ('Version', 'version'),
1709                  ('Origin', 'origin'),
1710                  ('Label', 'label'),
1711                  ('Description', 'description'),
1712                  ('Untouchable', 'untouchable'),
1713                  ('Announce', 'announce'),
1714                  ('Codename', 'codename'),
1715                  ('OverrideCodename', 'overridecodename'),
1716                  ('ValidTime', 'validtime'),
1717                  ('Priority', 'priority'),
1718                  ('NotAutomatic', 'notautomatic'),
1719                  ('CopyChanges', 'copychanges'),
1720                  ('CopyDotDak', 'copydotdak'),
1721                  ('CommentsDir', 'commentsdir'),
1722                  ('OverrideSuite', 'overridesuite'),
1723                  ('ChangelogBase', 'changelogbase')]
1724
1725
1726 class Suite(object):
1727     def __init__(self, *args, **kwargs):
1728         pass
1729
1730     def __repr__(self):
1731         return '<Suite %s>' % self.suite_name
1732
1733     def __eq__(self, val):
1734         if isinstance(val, str):
1735             return (self.suite_name == val)
1736         # This signals to use the normal comparison operator
1737         return NotImplemented
1738
1739     def __ne__(self, val):
1740         if isinstance(val, str):
1741             return (self.suite_name != val)
1742         # This signals to use the normal comparison operator
1743         return NotImplemented
1744
1745     def details(self):
1746         ret = []
1747         for disp, field in SUITE_FIELDS:
1748             val = getattr(self, field, None)
1749             if val is not None:
1750                 ret.append("%s: %s" % (disp, val))
1751
1752         return "\n".join(ret)
1753
1754 __all__.append('Suite')
1755
1756 def get_suite_architecture(suite, architecture, session=None):
1757     """
1758     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1759     doesn't exist
1760
1761     @type suite: str
1762     @param suite: Suite name to search for
1763
1764     @type architecture: str
1765     @param architecture: Architecture name to search for
1766
1767     @type session: Session
1768     @param session: Optional SQL session object (a temporary one will be
1769     generated if not supplied)
1770
1771     @rtype: SuiteArchitecture
1772     @return: the SuiteArchitecture object or None
1773     """
1774
1775     if session is None:
1776         session = DBConn().session()
1777
1778     q = session.query(SuiteArchitecture)
1779     q = q.join(Architecture).filter_by(arch_string=architecture)
1780     q = q.join(Suite).filter_by(suite_name=suite)
1781     if q.count() == 0:
1782         return None
1783     return q.one()
1784
1785 __all__.append('get_suite_architecture')
1786
1787 def get_suite(suite, session=None):
1788     """
1789     Returns Suite object for given C{suite name}.
1790
1791     @type suite: string
1792     @param suite: The name of the suite
1793
1794     @type session: Session
1795     @param session: Optional SQLA session object (a temporary one will be
1796     generated if not supplied)
1797
1798     @rtype: Suite
1799     @return: Suite object for the requested suite name (None if not presenT)
1800
1801     """
1802     if session is None:
1803         session = DBConn().session()
1804     q = session.query(Suite).filter_by(suite_name=suite)
1805     if q.count() == 0:
1806         return None
1807     return q.one()
1808
1809 __all__.append('get_suite')
1810
1811 ################################################################################
1812
1813 class SuiteArchitecture(object):
1814     def __init__(self, *args, **kwargs):
1815         pass
1816
1817     def __repr__(self):
1818         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1819
1820 __all__.append('SuiteArchitecture')
1821
1822 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1823     """
1824     Returns list of Architecture objects for given C{suite} name
1825
1826     @type source: str
1827     @param source: Suite name to search for
1828
1829     @type skipsrc: boolean
1830     @param skipsrc: Whether to skip returning the 'source' architecture entry
1831     (Default False)
1832
1833     @type skipall: boolean
1834     @param skipall: Whether to skip returning the 'all' architecture entry
1835     (Default False)
1836
1837     @type session: Session
1838     @param session: Optional SQL session object (a temporary one will be
1839     generated if not supplied)
1840
1841     @rtype: list
1842     @return: list of Architecture objects for the given name (may be empty)
1843     """
1844
1845     if session is None:
1846         session = DBConn().session()
1847
1848     q = session.query(Architecture)
1849     q = q.join(SuiteArchitecture)
1850     q = q.join(Suite).filter_by(suite_name=suite)
1851     if skipsrc:
1852         q = q.filter(Architecture.arch_string != 'source')
1853     if skipall:
1854         q = q.filter(Architecture.arch_string != 'all')
1855     q = q.order_by('arch_string')
1856     return q.all()
1857
1858 __all__.append('get_suite_architectures')
1859
1860 ################################################################################
1861
1862 class Uid(object):
1863     def __init__(self, *args, **kwargs):
1864         pass
1865
1866     def __eq__(self, val):
1867         if isinstance(val, str):
1868             return (self.uid == val)
1869         # This signals to use the normal comparison operator
1870         return NotImplemented
1871
1872     def __ne__(self, val):
1873         if isinstance(val, str):
1874             return (self.uid != val)
1875         # This signals to use the normal comparison operator
1876         return NotImplemented
1877
1878     def __repr__(self):
1879         return '<Uid %s (%s)>' % (self.uid, self.name)
1880
1881 __all__.append('Uid')
1882
1883 def add_database_user(uidname, session=None):
1884     """
1885     Adds a database user
1886
1887     @type uidname: string
1888     @param uidname: The uid of the user to add
1889
1890     @type session: SQLAlchemy
1891     @param session: Optional SQL session object (a temporary one will be
1892     generated if not supplied).  If not passed, a commit will be performed at
1893     the end of the function, otherwise the caller is responsible for commiting.
1894
1895     @rtype: Uid
1896     @return: the uid object for the given uidname
1897     """
1898     privatetrans = False
1899     if session is None:
1900         session = DBConn().session()
1901         privatetrans = True
1902
1903     try:
1904         session.execute("CREATE USER :uid", {'uid': uidname})
1905         if privatetrans:
1906             session.commit()
1907     except:
1908         traceback.print_exc()
1909         raise
1910
1911 __all__.append('add_database_user')
1912
1913 def get_or_set_uid(uidname, session=None):
1914     """
1915     Returns uid object for given uidname.
1916
1917     If no matching uidname is found, a row is inserted.
1918
1919     @type uidname: string
1920     @param uidname: The uid to add
1921
1922     @type session: SQLAlchemy
1923     @param session: Optional SQL session object (a temporary one will be
1924     generated if not supplied).  If not passed, a commit will be performed at
1925     the end of the function, otherwise the caller is responsible for commiting.
1926
1927     @rtype: Uid
1928     @return: the uid object for the given uidname
1929     """
1930     privatetrans = False
1931     if session is None:
1932         session = DBConn().session()
1933         privatetrans = True
1934
1935     try:
1936         q = session.query(Uid).filter_by(uid=uidname)
1937         if q.count() < 1:
1938             uid = Uid()
1939             uid.uid = uidname
1940             session.add(uid)
1941             if privatetrans:
1942                 session.commit()
1943             else:
1944                 session.flush()
1945             return uid
1946         else:
1947             return q.one()
1948
1949     except:
1950         traceback.print_exc()
1951         raise
1952
1953 __all__.append('get_or_set_uid')
1954
1955
1956 def get_uid_from_fingerprint(fpr, session=None):
1957     if session is None:
1958         session = DBConn().session()
1959
1960     q = session.query(Uid)
1961     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1962
1963     if q.count() != 1:
1964         return None
1965     else:
1966         return q.one()
1967
1968 __all__.append('get_uid_from_fingerprint')
1969
1970 ################################################################################
1971
1972 class DBConn(Singleton):
1973     """
1974     database module init.
1975     """
1976     def __init__(self, *args, **kwargs):
1977         super(DBConn, self).__init__(*args, **kwargs)
1978
1979     def _startup(self, *args, **kwargs):
1980         self.debug = False
1981         if kwargs.has_key('debug'):
1982             self.debug = True
1983         self.__createconn()
1984
1985     def __setuptables(self):
1986         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1987         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1988         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1989         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1990         self.tbl_component = Table('component', self.db_meta, autoload=True)
1991         self.tbl_config = Table('config', self.db_meta, autoload=True)
1992         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1993         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1994         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1995         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1996         self.tbl_files = Table('files', self.db_meta, autoload=True)
1997         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1998         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1999         self.tbl_location = Table('location', self.db_meta, autoload=True)
2000         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2001         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2002         self.tbl_override = Table('override', self.db_meta, autoload=True)
2003         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2004         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2005         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2006         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2007         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2008         self.tbl_section = Table('section', self.db_meta, autoload=True)
2009         self.tbl_source = Table('source', self.db_meta, autoload=True)
2010         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2011         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2012         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2013         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2014         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2015
2016     def __setupmappers(self):
2017         mapper(Architecture, self.tbl_architecture,
2018                properties = dict(arch_id = self.tbl_architecture.c.id))
2019
2020         mapper(Archive, self.tbl_archive,
2021                properties = dict(archive_id = self.tbl_archive.c.id,
2022                                  archive_name = self.tbl_archive.c.name))
2023
2024         mapper(BinAssociation, self.tbl_bin_associations,
2025                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2026                                  suite_id = self.tbl_bin_associations.c.suite,
2027                                  suite = relation(Suite),
2028                                  binary_id = self.tbl_bin_associations.c.bin,
2029                                  binary = relation(DBBinary)))
2030
2031         mapper(DBBinary, self.tbl_binaries,
2032                properties = dict(binary_id = self.tbl_binaries.c.id,
2033                                  package = self.tbl_binaries.c.package,
2034                                  version = self.tbl_binaries.c.version,
2035                                  maintainer_id = self.tbl_binaries.c.maintainer,
2036                                  maintainer = relation(Maintainer),
2037                                  source_id = self.tbl_binaries.c.source,
2038                                  source = relation(DBSource),
2039                                  arch_id = self.tbl_binaries.c.architecture,
2040                                  architecture = relation(Architecture),
2041                                  poolfile_id = self.tbl_binaries.c.file,
2042                                  poolfile = relation(PoolFile),
2043                                  binarytype = self.tbl_binaries.c.type,
2044                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2045                                  fingerprint = relation(Fingerprint),
2046                                  install_date = self.tbl_binaries.c.install_date,
2047                                  binassociations = relation(BinAssociation,
2048                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2049
2050         mapper(Component, self.tbl_component,
2051                properties = dict(component_id = self.tbl_component.c.id,
2052                                  component_name = self.tbl_component.c.name))
2053
2054         mapper(DBConfig, self.tbl_config,
2055                properties = dict(config_id = self.tbl_config.c.id))
2056
2057         mapper(ContentAssociation, self.tbl_content_associations,
2058                properties = dict(ca_id = self.tbl_content_associations.c.id,
2059                                  filename_id = self.tbl_content_associations.c.filename,
2060                                  filename    = relation(ContentFilename),
2061                                  filepath_id = self.tbl_content_associations.c.filepath,
2062                                  filepath    = relation(ContentFilepath),
2063                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2064                                  binary      = relation(DBBinary)))
2065
2066
2067         mapper(ContentFilename, self.tbl_content_file_names,
2068                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2069                                  filename = self.tbl_content_file_names.c.file))
2070
2071         mapper(ContentFilepath, self.tbl_content_file_paths,
2072                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2073                                  filepath = self.tbl_content_file_paths.c.path))
2074
2075         mapper(DSCFile, self.tbl_dsc_files,
2076                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2077                                  source_id = self.tbl_dsc_files.c.source,
2078                                  source = relation(DBSource),
2079                                  poolfile_id = self.tbl_dsc_files.c.file,
2080                                  poolfile = relation(PoolFile)))
2081
2082         mapper(PoolFile, self.tbl_files,
2083                properties = dict(file_id = self.tbl_files.c.id,
2084                                  filesize = self.tbl_files.c.size,
2085                                  location_id = self.tbl_files.c.location,
2086                                  location = relation(Location)))
2087
2088         mapper(Fingerprint, self.tbl_fingerprint,
2089                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2090                                  uid_id = self.tbl_fingerprint.c.uid,
2091                                  uid = relation(Uid),
2092                                  keyring_id = self.tbl_fingerprint.c.keyring,
2093                                  keyring = relation(Keyring)))
2094
2095         mapper(Keyring, self.tbl_keyrings,
2096                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2097                                  keyring_id = self.tbl_keyrings.c.id))
2098
2099         mapper(Location, self.tbl_location,
2100                properties = dict(location_id = self.tbl_location.c.id,
2101                                  component_id = self.tbl_location.c.component,
2102                                  component = relation(Component),
2103                                  archive_id = self.tbl_location.c.archive,
2104                                  archive = relation(Archive),
2105                                  archive_type = self.tbl_location.c.type))
2106
2107         mapper(Maintainer, self.tbl_maintainer,
2108                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2109
2110         mapper(NewComment, self.tbl_new_comments,
2111                properties = dict(comment_id = self.tbl_new_comments.c.id))
2112
2113         mapper(Override, self.tbl_override,
2114                properties = dict(suite_id = self.tbl_override.c.suite,
2115                                  suite = relation(Suite),
2116                                  component_id = self.tbl_override.c.component,
2117                                  component = relation(Component),
2118                                  priority_id = self.tbl_override.c.priority,
2119                                  priority = relation(Priority),
2120                                  section_id = self.tbl_override.c.section,
2121                                  section = relation(Section),
2122                                  overridetype_id = self.tbl_override.c.type,
2123                                  overridetype = relation(OverrideType)))
2124
2125         mapper(OverrideType, self.tbl_override_type,
2126                properties = dict(overridetype = self.tbl_override_type.c.type,
2127                                  overridetype_id = self.tbl_override_type.c.id))
2128
2129         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2130                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2131                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2132                                  filepath = relation(ContentFilepath),
2133                                  filename_id = self.tbl_pending_content_associations.c.filename,
2134                                  filename = relation(ContentFilename)))
2135
2136         mapper(Priority, self.tbl_priority,
2137                properties = dict(priority_id = self.tbl_priority.c.id))
2138
2139         mapper(Queue, self.tbl_queue,
2140                properties = dict(queue_id = self.tbl_queue.c.id))
2141
2142         mapper(QueueBuild, self.tbl_queue_build,
2143                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2144                                  queue_id = self.tbl_queue_build.c.queue,
2145                                  queue = relation(Queue, backref='queuebuild')))
2146
2147         mapper(Section, self.tbl_section,
2148                properties = dict(section_id = self.tbl_section.c.id))
2149
2150         mapper(DBSource, self.tbl_source,
2151                properties = dict(source_id = self.tbl_source.c.id,
2152                                  version = self.tbl_source.c.version,
2153                                  maintainer_id = self.tbl_source.c.maintainer,
2154                                  maintainer = relation(Maintainer,
2155                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2156                                  poolfile_id = self.tbl_source.c.file,
2157                                  poolfile = relation(PoolFile),
2158                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2159                                  fingerprint = relation(Fingerprint),
2160                                  changedby_id = self.tbl_source.c.changedby,
2161                                  changedby = relation(Maintainer,
2162                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2163                                  srcfiles = relation(DSCFile,
2164                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2165                                  srcassociations = relation(SrcAssociation,
2166                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2167
2168         mapper(SrcAssociation, self.tbl_src_associations,
2169                properties = dict(sa_id = self.tbl_src_associations.c.id,
2170                                  suite_id = self.tbl_src_associations.c.suite,
2171                                  suite = relation(Suite),
2172                                  source_id = self.tbl_src_associations.c.source,
2173                                  source = relation(DBSource)))
2174
2175         mapper(SrcUploader, self.tbl_src_uploaders,
2176                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2177                                  source_id = self.tbl_src_uploaders.c.source,
2178                                  source = relation(DBSource,
2179                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2180                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2181                                  maintainer = relation(Maintainer,
2182                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2183
2184         mapper(Suite, self.tbl_suite,
2185                properties = dict(suite_id = self.tbl_suite.c.id))
2186
2187         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2188                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2189                                  suite = relation(Suite, backref='suitearchitectures'),
2190                                  arch_id = self.tbl_suite_architectures.c.architecture,
2191                                  architecture = relation(Architecture)))
2192
2193         mapper(Uid, self.tbl_uid,
2194                properties = dict(uid_id = self.tbl_uid.c.id,
2195                                  fingerprint = relation(Fingerprint)))
2196
2197     ## Connection functions
2198     def __createconn(self):
2199         from config import Config
2200         cnf = Config()
2201         if cnf["DB::Host"]:
2202             # TCP/IP
2203             connstr = "postgres://%s" % cnf["DB::Host"]
2204             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2205                 connstr += ":%s" % cnf["DB::Port"]
2206             connstr += "/%s" % cnf["DB::Name"]
2207         else:
2208             # Unix Socket
2209             connstr = "postgres:///%s" % cnf["DB::Name"]
2210             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2211                 connstr += "?port=%s" % cnf["DB::Port"]
2212
2213         self.db_pg   = create_engine(connstr, echo=self.debug)
2214         self.db_meta = MetaData()
2215         self.db_meta.bind = self.db_pg
2216         self.db_smaker = sessionmaker(bind=self.db_pg,
2217                                       autoflush=True,
2218                                       autocommit=False)
2219
2220         self.__setuptables()
2221         self.__setupmappers()
2222
2223     def session(self):
2224         return self.db_smaker()
2225
2226 __all__.append('DBConn')
2227
2228