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
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.
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.
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
27 ################################################################################
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"
34 ################################################################################
40 from sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
46 # Only import Config until Queue stuff is changed to store its config
48 from config import Config
49 from singleton import Singleton
50 from textutils import fix_maintainer
52 ################################################################################
54 __all__ = ['IntegrityError', 'SQLAlchemyError']
56 ################################################################################
58 class Architecture(object):
59 def __init__(self, *args, **kwargs):
63 return '<Architecture %s>' % self.arch_string
65 __all__.append('Architecture')
67 def get_architecture(architecture, session=None):
69 Returns database id for given C{architecture}.
71 @type architecture: string
72 @param architecture: The name of the architecture
74 @type session: Session
75 @param session: Optional SQLA session object (a temporary one will be
76 generated if not supplied)
79 @return: Architecture object for the given arch (None if not present)
83 session = DBConn().session()
84 q = session.query(Architecture).filter_by(arch_string=architecture)
89 __all__.append('get_architecture')
91 def get_architecture_suites(architecture, session=None):
93 Returns list of Suite objects for given C{architecture} name
96 @param source: Architecture name to search for
98 @type session: Session
99 @param session: Optional SQL session object (a temporary one will be
100 generated if not supplied)
103 @return: list of Suite objects for the given name (may be empty)
107 session = DBConn().session()
109 q = session.query(Suite)
110 q = q.join(SuiteArchitecture)
111 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
114 __all__.append('get_architecture_suites')
116 ################################################################################
118 class Archive(object):
119 def __init__(self, *args, **kwargs):
123 return '<Archive %s>' % self.name
125 __all__.append('Archive')
127 def get_archive(archive, session=None):
129 returns database id for given c{archive}.
131 @type archive: string
132 @param archive: the name of the arhive
134 @type session: Session
135 @param session: Optional SQLA session object (a temporary one will be
136 generated if not supplied)
139 @return: Archive object for the given name (None if not present)
142 archive = archive.lower()
144 session = DBConn().session()
145 q = session.query(Archive).filter_by(archive_name=archive)
150 __all__.append('get_archive')
152 ################################################################################
154 class BinAssociation(object):
155 def __init__(self, *args, **kwargs):
159 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
161 __all__.append('BinAssociation')
163 ################################################################################
165 class DBBinary(object):
166 def __init__(self, *args, **kwargs):
170 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
172 __all__.append('DBBinary')
174 def get_binary_from_id(id, session=None):
176 Returns DBBinary object for given C{id}
179 @param id: Id of the required binary
181 @type session: Session
182 @param session: Optional SQLA session object (a temporary one will be
183 generated if not supplied)
186 @return: DBBinary object for the given binary (None if not present)
189 session = DBConn().session()
190 q = session.query(DBBinary).filter_by(binary_id=id)
195 __all__.append('get_binary_from_id')
197 def get_binaries_from_name(package, version=None, architecture=None, session=None):
199 Returns list of DBBinary objects for given C{package} name
202 @param package: DBBinary package name to search for
204 @type version: str or None
205 @param version: Version to search for (or None)
207 @type package: str, list or None
208 @param package: Architectures to limit to (or None if no limit)
210 @type session: Session
211 @param session: Optional SQL session object (a temporary one will be
212 generated if not supplied)
215 @return: list of DBBinary objects for the given name (may be empty)
218 session = DBConn().session()
220 q = session.query(DBBinary).filter_by(package=package)
222 if version is not None:
223 q = q.filter_by(version=version)
225 if architecture is not None:
226 if not isinstance(architecture, list):
227 architecture = [architecture]
228 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
232 __all__.append('get_binaries_from_name')
234 def get_binaries_from_source_id(source_id, session=None):
236 Returns list of DBBinary objects for given C{source_id}
239 @param source_id: source_id to search for
241 @type session: Session
242 @param session: Optional SQL session object (a temporary one will be
243 generated if not supplied)
246 @return: list of DBBinary objects for the given name (may be empty)
249 session = DBConn().session()
250 return session.query(DBBinary).filter_by(source_id=source_id).all()
252 __all__.append('get_binaries_from_source_id')
255 def get_binary_from_name_suite(package, suitename, session=None):
256 ### For dak examine-package
257 ### XXX: Doesn't use object API yet
259 session = DBConn().session()
261 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
262 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
263 WHERE b.package=:package
265 AND fi.location = l.id
266 AND l.component = c.id
269 AND su.suite_name=:suitename
270 ORDER BY b.version DESC"""
272 return session.execute(sql, {'package': package, 'suitename': suitename})
274 __all__.append('get_binary_from_name_suite')
276 def get_binary_components(package, suitename, arch, session=None):
277 # Check for packages that have moved from one component to another
278 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
279 WHERE b.package=:package AND s.suite_name=:suitename
280 AND (a.arch_string = :arch OR a.arch_string = 'all')
281 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
282 AND f.location = l.id
283 AND l.component = c.id
286 vals = {'package': package, 'suitename': suitename, 'arch': arch}
289 session = DBConn().session()
290 return session.execute(query, vals)
292 __all__.append('get_binary_components')
294 ################################################################################
296 class Component(object):
297 def __init__(self, *args, **kwargs):
301 return '<Component %s>' % self.component_name
304 __all__.append('Component')
306 def get_component(component, session=None):
308 Returns database id for given C{component}.
310 @type component: string
311 @param component: The name of the override type
314 @return: the database id for the given component
317 component = component.lower()
319 session = DBConn().session()
320 q = session.query(Component).filter_by(component_name=component)
325 __all__.append('get_component')
327 ################################################################################
329 class DBConfig(object):
330 def __init__(self, *args, **kwargs):
334 return '<DBConfig %s>' % self.name
336 __all__.append('DBConfig')
338 ################################################################################
340 class ContentFilename(object):
341 def __init__(self, *args, **kwargs):
345 return '<ContentFilename %s>' % self.filename
347 __all__.append('ContentFilename')
349 def get_or_set_contents_file_id(filename, session=None):
351 Returns database id for given filename.
353 If no matching file is found, a row is inserted.
355 @type filename: string
356 @param filename: The filename
357 @type session: SQLAlchemy
358 @param session: Optional SQL session object (a temporary one will be
359 generated if not supplied). If not passed, a commit will be performed at
360 the end of the function, otherwise the caller is responsible for commiting.
363 @return: the database id for the given component
367 session = DBConn().session()
371 q = session.query(ContentFilename).filter_by(filename=filename)
373 cf = ContentFilename()
374 cf.filename = filename
378 return cf.cafilename_id
380 return q.one().cafilename_id
383 traceback.print_exc()
386 __all__.append('get_or_set_contents_file_id')
388 def get_contents(suite, overridetype, section=None, session=None):
390 Returns contents for a suite / overridetype combination, limiting
391 to a section if not None.
394 @param suite: Suite object
396 @type overridetype: OverrideType
397 @param overridetype: OverrideType object
399 @type section: Section
400 @param section: Optional section object to limit results to
402 @type session: SQLAlchemy
403 @param session: Optional SQL session object (a temporary one will be
404 generated if not supplied)
407 @return: ResultsProxy object set up to return tuples of (filename, section,
412 session = DBConn().session()
414 # find me all of the contents for a given suite
415 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
419 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
420 JOIN content_file_names n ON (c.filename=n.id)
421 JOIN binaries b ON (b.id=c.binary_pkg)
422 JOIN override o ON (o.package=b.package)
423 JOIN section s ON (s.id=o.section)
424 WHERE o.suite = :suiteid AND o.type = :overridetypeid
425 AND b.type=:overridetypename"""
427 vals = {'suiteid': suite.suite_id,
428 'overridetypeid': overridetype.overridetype_id,
429 'overridetypename': overridetype.overridetype}
431 if section is not None:
432 contents_q += " AND s.id = :sectionid"
433 vals['sectionid'] = section.section_id
435 contents_q += " ORDER BY fn"
437 return session.execute(contents_q, vals)
439 __all__.append('get_contents')
441 ################################################################################
443 class ContentFilepath(object):
444 def __init__(self, *args, **kwargs):
448 return '<ContentFilepath %s>' % self.filepath
450 __all__.append('ContentFilepath')
452 def get_or_set_contents_path_id(filepath, session):
454 Returns database id for given path.
456 If no matching file is found, a row is inserted.
458 @type filename: string
459 @param filename: The filepath
460 @type session: SQLAlchemy
461 @param session: Optional SQL session object (a temporary one will be
462 generated if not supplied). If not passed, a commit will be performed at
463 the end of the function, otherwise the caller is responsible for commiting.
466 @return: the database id for the given path
470 session = DBConn().session()
474 q = session.query(ContentFilepath).filter_by(filepath=filepath)
476 cf = ContentFilepath()
477 cf.filepath = filepath
481 return cf.cafilepath_id
483 return q.one().cafilepath_id
486 traceback.print_exc()
489 __all__.append('get_or_set_contents_path_id')
491 ################################################################################
493 class ContentAssociation(object):
494 def __init__(self, *args, **kwargs):
498 return '<ContentAssociation %s>' % self.ca_id
500 __all__.append('ContentAssociation')
502 def insert_content_paths(binary_id, fullpaths, session=None):
504 Make sure given path is associated with given binary id
507 @param binary_id: the id of the binary
508 @type fullpaths: list
509 @param fullpaths: the list of paths of the file being associated with the binary
510 @type session: SQLAlchemy session
511 @param session: Optional SQLAlchemy session. If this is passed, the caller
512 is responsible for ensuring a transaction has begun and committing the
513 results or rolling back based on the result code. If not passed, a commit
514 will be performed at the end of the function, otherwise the caller is
515 responsible for commiting.
517 @return: True upon success
523 session = DBConn().session()
527 for fullpath in fullpaths:
528 (path, file) = os.path.split(fullpath)
530 # Get the necessary IDs ...
531 ca = ContentAssociation()
532 ca.binary_id = binary_id
533 ca.filename_id = get_or_set_contents_file_id(file)
534 ca.filepath_id = get_or_set_contents_path_id(path)
537 # Only commit if we set up the session ourself
543 traceback.print_exc()
545 # Only rollback if we set up the session ourself
551 __all__.append('insert_content_paths')
553 ################################################################################
555 class DSCFile(object):
556 def __init__(self, *args, **kwargs):
560 return '<DSCFile %s>' % self.dscfile_id
562 __all__.append('DSCFile')
564 ################################################################################
566 class PoolFile(object):
567 def __init__(self, *args, **kwargs):
571 return '<PoolFile %s>' % self.filename
573 __all__.append('PoolFile')
575 def get_poolfile_by_name(filename, location_id=None, session=None):
577 Returns an array of PoolFile objects for the given filename and
578 (optionally) location_id
580 @type filename: string
581 @param filename: the filename of the file to check against the DB
583 @type location_id: int
584 @param location_id: the id of the location to look in (optional)
587 @return: array of PoolFile objects
590 if session is not None:
591 session = DBConn().session()
593 q = session.query(PoolFile).filter_by(filename=filename)
595 if location_id is not None:
596 q = q.join(Location).filter_by(location_id=location_id)
600 __all__.append('get_poolfile_by_name')
602 def get_poolfile_like_name(filename, session=None):
604 Returns an array of PoolFile objects which are like the given name
606 @type filename: string
607 @param filename: the filename of the file to check against the DB
610 @return: array of PoolFile objects
613 if session is not None:
614 session = DBConn().session()
616 # TODO: There must be a way of properly using bind parameters with %FOO%
617 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
621 __all__.append('get_poolfile_like_name')
623 ################################################################################
625 class Fingerprint(object):
626 def __init__(self, *args, **kwargs):
630 return '<Fingerprint %s>' % self.fingerprint
632 __all__.append('Fingerprint')
634 ################################################################################
636 class Keyring(object):
637 def __init__(self, *args, **kwargs):
641 return '<Keyring %s>' % self.keyring_name
643 __all__.append('Keyring')
645 ################################################################################
647 class Location(object):
648 def __init__(self, *args, **kwargs):
652 return '<Location %s (%s)>' % (self.path, self.location_id)
654 __all__.append('Location')
656 def get_location(location, component=None, archive=None, session=None):
658 Returns Location object for the given combination of location, component
661 @type location: string
662 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
664 @type component: string
665 @param component: the component name (if None, no restriction applied)
667 @type archive: string
668 @param archive_id: the archive name (if None, no restriction applied)
670 @rtype: Location / None
671 @return: Either a Location object or None if one can't be found
675 session = DBConn().session()
677 q = session.query(Location).filter_by(path=location)
679 if archive is not None:
680 q = q.join(Archive).filter_by(archive_name=archive)
682 if component is not None:
683 q = q.join(Component).filter_by(component_name=component)
690 __all__.append('get_location')
692 ################################################################################
694 class Maintainer(object):
695 def __init__(self, *args, **kwargs):
699 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
701 def get_split_maintainer(self):
702 if not hasattr(self, 'name') or self.name is None:
703 return ('', '', '', '')
705 return fix_maintainer(self.name.strip())
707 __all__.append('Maintainer')
709 ################################################################################
711 class NewComment(object):
712 def __init__(self, *args, **kwargs):
716 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
718 __all__.append('NewComment')
720 def has_new_comment(package, version, session=None):
722 Returns true if the given combination of C{package}, C{version} has a comment.
724 @type package: string
725 @param package: name of the package
727 @type version: string
728 @param version: package version
730 @type session: Session
731 @param session: Optional SQLA session object (a temporary one will be
732 generated if not supplied)
739 session = DBConn().session()
741 q = session.query(NewComment)
742 q = q.filter_by(package=package)
743 q = q.filter_by(version=version)
746 __all__.append('has_new_comment')
748 def get_new_comments(package=None, version=None, comment_id=None, session=None):
750 Returns (possibly empty) list of NewComment objects for the given
753 @type package: string (optional)
754 @param package: name of the package
756 @type version: string (optional)
757 @param version: package version
759 @type comment_id: int (optional)
760 @param comment_id: An id of a comment
762 @type session: Session
763 @param session: Optional SQLA session object (a temporary one will be
764 generated if not supplied)
767 @return: A (possibly empty) list of NewComment objects will be returned
772 session = DBConn().session()
774 q = session.query(NewComment)
775 if package is not None: q = q.filter_by(package=package)
776 if version is not None: q = q.filter_by(version=version)
777 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
781 __all__.append('get_new_comments')
783 ################################################################################
785 class Override(object):
786 def __init__(self, *args, **kwargs):
790 return '<Override %s (%s)>' % (self.package, self.suite_id)
792 __all__.append('Override')
794 def get_override(package, suite=None, component=None, overridetype=None, session=None):
796 Returns Override object for the given parameters
798 @type package: string
799 @param package: The name of the package
801 @type suite: string, list or None
802 @param suite: The name of the suite (or suites if a list) to limit to. If
803 None, don't limit. Defaults to None.
805 @type component: string, list or None
806 @param component: The name of the component (or components if a list) to
807 limit to. If None, don't limit. Defaults to None.
809 @type overridetype: string, list or None
810 @param overridetype: The name of the overridetype (or overridetypes if a list) to
811 limit to. If None, don't limit. Defaults to None.
813 @type session: Session
814 @param session: Optional SQLA session object (a temporary one will be
815 generated if not supplied)
818 @return: A (possibly empty) list of Override objects will be returned
822 session = DBConn().session()
824 q = session.query(Override)
825 q = q.filter_by(package=package)
827 if suite is not None:
828 if not isinstance(suite, list): suite = [suite]
829 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
831 if component is not None:
832 if not isinstance(component, list): component = [component]
833 q = q.join(Component).filter(Component.component_name.in_(component))
835 if overridetype is not None:
836 if not isinstance(overridetype, list): overridetype = [overridetype]
837 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
841 __all__.append('get_override')
844 ################################################################################
846 class OverrideType(object):
847 def __init__(self, *args, **kwargs):
851 return '<OverrideType %s>' % self.overridetype
853 __all__.append('OverrideType')
855 def get_override_type(override_type, session=None):
857 Returns OverrideType object for given C{override type}.
859 @type override_type: string
860 @param override_type: The name of the override type
862 @type session: Session
863 @param session: Optional SQLA session object (a temporary one will be
864 generated if not supplied)
867 @return: the database id for the given override type
871 session = DBConn().session()
872 q = session.query(OverrideType).filter_by(overridetype=override_type)
877 __all__.append('get_override_type')
879 ################################################################################
881 class PendingContentAssociation(object):
882 def __init__(self, *args, **kwargs):
886 return '<PendingContentAssociation %s>' % self.pca_id
888 __all__.append('PendingContentAssociation')
890 def insert_pending_content_paths(package, fullpaths, session=None):
892 Make sure given paths are temporarily associated with given
896 @param package: the package to associate with should have been read in from the binary control file
897 @type fullpaths: list
898 @param fullpaths: the list of paths of the file being associated with the binary
899 @type session: SQLAlchemy session
900 @param session: Optional SQLAlchemy session. If this is passed, the caller
901 is responsible for ensuring a transaction has begun and committing the
902 results or rolling back based on the result code. If not passed, a commit
903 will be performed at the end of the function
905 @return: True upon success, False if there is a problem
911 session = DBConn().session()
915 arch = get_architecture(package['Architecture'], session)
916 arch_id = arch.arch_id
918 # Remove any already existing recorded files for this package
919 q = session.query(PendingContentAssociation)
920 q = q.filter_by(package=package['Package'])
921 q = q.filter_by(version=package['Version'])
922 q = q.filter_by(architecture=arch_id)
926 for fullpath in fullpaths:
927 (path, file) = os.path.split(fullpath)
929 if path.startswith( "./" ):
932 pca = PendingContentAssociation()
933 pca.package = package['Package']
934 pca.version = package['Version']
935 pca.filename_id = get_or_set_contents_file_id(file, session)
936 pca.filepath_id = get_or_set_contents_path_id(path, session)
937 pca.architecture = arch_id
940 # Only commit if we set up the session ourself
946 traceback.print_exc()
948 # Only rollback if we set up the session ourself
954 __all__.append('insert_pending_content_paths')
956 ################################################################################
958 class Priority(object):
959 def __init__(self, *args, **kwargs):
963 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
965 __all__.append('Priority')
967 def get_priority(priority, session=None):
969 Returns Priority object for given C{priority name}.
971 @type priority: string
972 @param priority: The name of the priority
974 @type session: Session
975 @param session: Optional SQLA session object (a temporary one will be
976 generated if not supplied)
979 @return: Priority object for the given priority
983 session = DBConn().session()
984 q = session.query(Priority).filter_by(priority=priority)
989 __all__.append('get_priority')
991 ################################################################################
994 def __init__(self, *args, **kwargs):
998 return '<Queue %s>' % self.queue_name
1000 def autobuild_upload(self, changes, srcpath, session=None):
1002 Update queue_build database table used for incoming autobuild support.
1004 @type changes: Changes
1005 @param changes: changes object for the upload to process
1007 @type srcpath: string
1008 @param srcpath: path for the queue file entries/link destinations
1010 @type session: SQLAlchemy session
1011 @param session: Optional SQLAlchemy session. If this is passed, the
1012 caller is responsible for ensuring a transaction has begun and
1013 committing the results or rolling back based on the result code. If
1014 not passed, a commit will be performed at the end of the function,
1015 otherwise the caller is responsible for commiting.
1017 @rtype: NoneType or string
1018 @return: None if the operation failed, a string describing the error if not
1023 session = DBConn().session()
1026 # TODO: Remove by moving queue config into the database
1029 for suitename in changes.changes["distribution"].keys():
1030 # TODO: Move into database as:
1031 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1032 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1033 # This also gets rid of the SecurityQueueBuild hack below
1034 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1038 s = get_suite(suitename, session)
1040 return "INTERNAL ERROR: Could not find suite %s" % suitename
1042 # TODO: Get from database as above
1043 dest_dir = conf["Dir::QueueBuild"]
1045 # TODO: Move into database as above
1046 if conf.FindB("Dinstall::SecurityQueueBuild"):
1047 dest_dir = os.path.join(dest_dir, suitename)
1049 for file_entry in changes.files.keys():
1050 src = os.path.join(srcpath, file_entry)
1051 dest = os.path.join(dest_dir, file_entry)
1053 # TODO: Move into database as above
1054 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1055 # Copy it since the original won't be readable by www-data
1056 utils.copy(src, dest)
1058 # Create a symlink to it
1059 os.symlink(src, dest)
1062 qb.suite_id = s.suite_id
1063 qb.queue_id = self.queue_id
1069 # If the .orig.tar.gz is in the pool, create a symlink to
1070 # it (if one doesn't already exist)
1071 if changes.orig_tar_id:
1072 # Determine the .orig.tar.gz file name
1073 for dsc_file in changes.dsc_files.keys():
1074 if dsc_file.endswith(".orig.tar.gz"):
1077 dest = os.path.join(dest_dir, filename)
1079 # If it doesn't exist, create a symlink
1080 if not os.path.exists(dest):
1081 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1082 {'id': changes.orig_tar_id})
1085 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1087 src = os.path.join(res[0], res[1])
1088 os.symlink(src, dest)
1090 # Add it to the list of packages for later processing by apt-ftparchive
1092 qb.suite_id = s.suite_id
1093 qb.queue_id = self.queue_id
1098 # If it does, update things to ensure it's not removed prematurely
1100 qb = get_queue_build(dest, suite_id, session)
1111 __all__.append('Queue')
1113 def get_queue(queuename, session=None):
1115 Returns Queue object for given C{queue name}.
1117 @type queuename: string
1118 @param queuename: The name of the queue
1120 @type session: Session
1121 @param session: Optional SQLA session object (a temporary one will be
1122 generated if not supplied)
1125 @return: Queue object for the given queue
1129 session = DBConn().session()
1130 q = session.query(Queue).filter_by(queue_name=queuename)
1135 __all__.append('get_queue')
1137 ################################################################################
1139 class QueueBuild(object):
1140 def __init__(self, *args, **kwargs):
1144 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1146 __all__.append('QueueBuild')
1148 def get_queue_build(filename, suite_id, session=None):
1150 Returns QueueBuild object for given C{filename} and C{suite id}.
1152 @type filename: string
1153 @param filename: The name of the file
1156 @param suiteid: Suite ID
1158 @type session: Session
1159 @param session: Optional SQLA session object (a temporary one will be
1160 generated if not supplied)
1163 @return: Queue object for the given queue
1167 session = DBConn().session()
1168 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1173 __all__.append('get_queue_build')
1175 ################################################################################
1177 class Section(object):
1178 def __init__(self, *args, **kwargs):
1182 return '<Section %s>' % self.section
1184 __all__.append('Section')
1186 def get_section(section, session=None):
1188 Returns Section object for given C{section name}.
1190 @type section: string
1191 @param section: The name of the section
1193 @type session: Session
1194 @param session: Optional SQLA session object (a temporary one will be
1195 generated if not supplied)
1198 @return: Section object for the given section name
1202 session = DBConn().session()
1203 q = session.query(Section).filter_by(section=section)
1208 __all__.append('get_section')
1210 ################################################################################
1212 class DBSource(object):
1213 def __init__(self, *args, **kwargs):
1217 return '<DBSource %s (%s)>' % (self.source, self.version)
1219 __all__.append('DBSource')
1221 def source_exists(source, source_version, suites = ["any"], session=None):
1223 Ensure that source exists somewhere in the archive for the binary
1224 upload being processed.
1225 1. exact match => 1.0-3
1226 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1228 @type package: string
1229 @param package: package source name
1231 @type source_version: string
1232 @param source_version: expected source version
1235 @param suites: list of suites to check in, default I{any}
1237 @type session: Session
1238 @param session: Optional SQLA session object (a temporary one will be
1239 generated if not supplied)
1242 @return: returns 1 if a source with expected version is found, otherwise 0
1247 session = DBConn().session()
1251 for suite in suites:
1252 q = session.query(DBSource).filter_by(source=source)
1254 # source must exist in suite X, or in some other suite that's
1255 # mapped to X, recursively... silent-maps are counted too,
1256 # unreleased-maps aren't.
1257 maps = cnf.ValueList("SuiteMappings")[:]
1259 maps = [ m.split() for m in maps ]
1260 maps = [ (x[1], x[2]) for x in maps
1261 if x[0] == "map" or x[0] == "silent-map" ]
1264 if x[1] in s and x[0] not in s:
1267 q = q.join(SrcAssociation).join(Suite)
1268 q = q.filter(Suite.suite_name.in_(s))
1270 # Reduce the query results to a list of version numbers
1271 ql = [ j.version for j in q.all() ]
1274 if source_version in ql:
1278 from daklib.regexes import re_bin_only_nmu
1279 orig_source_version = re_bin_only_nmu.sub('', source_version)
1280 if orig_source_version in ql:
1283 # No source found so return not ok
1289 __all__.append('source_exists')
1291 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1293 Returns list of DBSource objects for given C{source} name and other parameters
1296 @param source: DBSource package name to search for
1298 @type source: str or None
1299 @param source: DBSource version name to search for or None if not applicable
1301 @type dm_upload_allowed: bool
1302 @param dm_upload_allowed: If None, no effect. If True or False, only
1303 return packages with that dm_upload_allowed setting
1305 @type session: Session
1306 @param session: Optional SQL session object (a temporary one will be
1307 generated if not supplied)
1310 @return: list of DBSource objects for the given name (may be empty)
1313 session = DBConn().session()
1315 q = session.query(DBSource).filter_by(source=source)
1317 if version is not None:
1318 q = q.filter_by(version=version)
1320 if dm_upload_allowed is not None:
1321 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1325 __all__.append('get_sources_from_name')
1327 def get_source_in_suite(source, suite, session=None):
1329 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1331 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1332 - B{suite} - a suite name, eg. I{unstable}
1334 @type source: string
1335 @param source: source package name
1338 @param suite: the suite name
1341 @return: the version for I{source} in I{suite}
1345 session = DBConn().session()
1346 q = session.query(SrcAssociation)
1347 q = q.join('source').filter_by(source=source)
1348 q = q.join('suite').filter_by(suite_name=suite)
1351 # ???: Maybe we should just return the SrcAssociation object instead
1352 return q.one().source
1354 __all__.append('get_source_in_suite')
1356 ################################################################################
1358 class SrcAssociation(object):
1359 def __init__(self, *args, **kwargs):
1363 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1365 __all__.append('SrcAssociation')
1367 ################################################################################
1369 class SrcUploader(object):
1370 def __init__(self, *args, **kwargs):
1374 return '<SrcUploader %s>' % self.uploader_id
1376 __all__.append('SrcUploader')
1378 ################################################################################
1380 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1381 ('SuiteID', 'suite_id'),
1382 ('Version', 'version'),
1383 ('Origin', 'origin'),
1385 ('Description', 'description'),
1386 ('Untouchable', 'untouchable'),
1387 ('Announce', 'announce'),
1388 ('Codename', 'codename'),
1389 ('OverrideCodename', 'overridecodename'),
1390 ('ValidTime', 'validtime'),
1391 ('Priority', 'priority'),
1392 ('NotAutomatic', 'notautomatic'),
1393 ('CopyChanges', 'copychanges'),
1394 ('CopyDotDak', 'copydotdak'),
1395 ('CommentsDir', 'commentsdir'),
1396 ('OverrideSuite', 'overridesuite'),
1397 ('ChangelogBase', 'changelogbase')]
1400 class Suite(object):
1401 def __init__(self, *args, **kwargs):
1405 return '<Suite %s>' % self.suite_name
1409 for disp, field in SUITE_FIELDS:
1410 val = getattr(self, field, None)
1412 ret.append("%s: %s" % (disp, val))
1414 return "\n".join(ret)
1416 __all__.append('Suite')
1418 def get_suite_architecture(suite, architecture, session=None):
1420 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1424 @param suite: Suite name to search for
1426 @type architecture: str
1427 @param architecture: Architecture name to search for
1429 @type session: Session
1430 @param session: Optional SQL session object (a temporary one will be
1431 generated if not supplied)
1433 @rtype: SuiteArchitecture
1434 @return: the SuiteArchitecture object or None
1438 session = DBConn().session()
1440 q = session.query(SuiteArchitecture)
1441 q = q.join(Architecture).filter_by(arch_string=architecture)
1442 q = q.join(Suite).filter_by(suite_name=suite)
1447 __all__.append('get_suite_architecture')
1449 def get_suite(suite, session=None):
1451 Returns Suite object for given C{suite name}.
1454 @param suite: The name of the suite
1456 @type session: Session
1457 @param session: Optional SQLA session object (a temporary one will be
1458 generated if not supplied)
1461 @return: Suite object for the requested suite name (None if not presenT)
1465 session = DBConn().session()
1466 q = session.query(Suite).filter_by(suite_name=suite)
1471 __all__.append('get_suite')
1473 ################################################################################
1475 class SuiteArchitecture(object):
1476 def __init__(self, *args, **kwargs):
1480 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1482 __all__.append('SuiteArchitecture')
1484 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1486 Returns list of Architecture objects for given C{suite} name
1489 @param source: Suite name to search for
1491 @type skipsrc: boolean
1492 @param skipsrc: Whether to skip returning the 'source' architecture entry
1495 @type skipall: boolean
1496 @param skipall: Whether to skip returning the 'all' architecture entry
1499 @type session: Session
1500 @param session: Optional SQL session object (a temporary one will be
1501 generated if not supplied)
1504 @return: list of Architecture objects for the given name (may be empty)
1508 session = DBConn().session()
1510 q = session.query(Architecture)
1511 q = q.join(SuiteArchitecture)
1512 q = q.join(Suite).filter_by(suite_name=suite)
1514 q = q.filter(Architecture.arch_string != 'source')
1516 q = q.filter(Architecture.arch_string != 'all')
1517 q = q.order_by('arch_string')
1520 __all__.append('get_suite_architectures')
1522 ################################################################################
1525 def __init__(self, *args, **kwargs):
1529 return '<Uid %s (%s)>' % (self.uid, self.name)
1531 __all__.append('Uid')
1533 def add_database_user(uidname, session=None):
1535 Adds a database user
1537 @type uidname: string
1538 @param uidname: The uid of the user to add
1540 @type session: SQLAlchemy
1541 @param session: Optional SQL session object (a temporary one will be
1542 generated if not supplied). If not passed, a commit will be performed at
1543 the end of the function, otherwise the caller is responsible for commiting.
1546 @return: the uid object for the given uidname
1548 privatetrans = False
1550 session = DBConn().session()
1554 session.execute("CREATE USER :uid", {'uid': uidname})
1558 traceback.print_exc()
1561 __all__.append('add_database_user')
1563 def get_or_set_uid(uidname, session=None):
1565 Returns uid object for given uidname.
1567 If no matching uidname is found, a row is inserted.
1569 @type uidname: string
1570 @param uidname: The uid to add
1572 @type session: SQLAlchemy
1573 @param session: Optional SQL session object (a temporary one will be
1574 generated if not supplied). If not passed, a commit will be performed at
1575 the end of the function, otherwise the caller is responsible for commiting.
1578 @return: the uid object for the given uidname
1580 privatetrans = False
1582 session = DBConn().session()
1586 q = session.query(Uid).filter_by(uid=uidname)
1598 traceback.print_exc()
1601 __all__.append('get_or_set_uid')
1604 def get_uid_from_fingerprint(fpr, session=None):
1606 session = DBConn().session()
1608 q = session.query(Uid)
1609 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1616 __all__.append('get_uid_from_fingerprint')
1618 ################################################################################
1620 class DBConn(Singleton):
1622 database module init.
1624 def __init__(self, *args, **kwargs):
1625 super(DBConn, self).__init__(*args, **kwargs)
1627 def _startup(self, *args, **kwargs):
1629 if kwargs.has_key('debug'):
1633 def __setuptables(self):
1634 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1635 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1636 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1637 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1638 self.tbl_component = Table('component', self.db_meta, autoload=True)
1639 self.tbl_config = Table('config', self.db_meta, autoload=True)
1640 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1641 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1642 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1643 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1644 self.tbl_files = Table('files', self.db_meta, autoload=True)
1645 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1646 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1647 self.tbl_location = Table('location', self.db_meta, autoload=True)
1648 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1649 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
1650 self.tbl_override = Table('override', self.db_meta, autoload=True)
1651 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1652 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1653 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1654 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1655 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1656 self.tbl_section = Table('section', self.db_meta, autoload=True)
1657 self.tbl_source = Table('source', self.db_meta, autoload=True)
1658 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1659 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1660 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1661 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1662 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1664 def __setupmappers(self):
1665 mapper(Architecture, self.tbl_architecture,
1666 properties = dict(arch_id = self.tbl_architecture.c.id))
1668 mapper(Archive, self.tbl_archive,
1669 properties = dict(archive_id = self.tbl_archive.c.id,
1670 archive_name = self.tbl_archive.c.name))
1672 mapper(BinAssociation, self.tbl_bin_associations,
1673 properties = dict(ba_id = self.tbl_bin_associations.c.id,
1674 suite_id = self.tbl_bin_associations.c.suite,
1675 suite = relation(Suite),
1676 binary_id = self.tbl_bin_associations.c.bin,
1677 binary = relation(DBBinary)))
1679 mapper(DBBinary, self.tbl_binaries,
1680 properties = dict(binary_id = self.tbl_binaries.c.id,
1681 package = self.tbl_binaries.c.package,
1682 version = self.tbl_binaries.c.version,
1683 maintainer_id = self.tbl_binaries.c.maintainer,
1684 maintainer = relation(Maintainer),
1685 source_id = self.tbl_binaries.c.source,
1686 source = relation(DBSource),
1687 arch_id = self.tbl_binaries.c.architecture,
1688 architecture = relation(Architecture),
1689 poolfile_id = self.tbl_binaries.c.file,
1690 poolfile = relation(PoolFile),
1691 binarytype = self.tbl_binaries.c.type,
1692 fingerprint_id = self.tbl_binaries.c.sig_fpr,
1693 fingerprint = relation(Fingerprint),
1694 install_date = self.tbl_binaries.c.install_date,
1695 binassociations = relation(BinAssociation,
1696 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1698 mapper(Component, self.tbl_component,
1699 properties = dict(component_id = self.tbl_component.c.id,
1700 component_name = self.tbl_component.c.name))
1702 mapper(DBConfig, self.tbl_config,
1703 properties = dict(config_id = self.tbl_config.c.id))
1705 mapper(ContentAssociation, self.tbl_content_associations,
1706 properties = dict(ca_id = self.tbl_content_associations.c.id,
1707 filename_id = self.tbl_content_associations.c.filename,
1708 filename = relation(ContentFilename),
1709 filepath_id = self.tbl_content_associations.c.filepath,
1710 filepath = relation(ContentFilepath),
1711 binary_id = self.tbl_content_associations.c.binary_pkg,
1712 binary = relation(DBBinary)))
1715 mapper(ContentFilename, self.tbl_content_file_names,
1716 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1717 filename = self.tbl_content_file_names.c.file))
1719 mapper(ContentFilepath, self.tbl_content_file_paths,
1720 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1721 filepath = self.tbl_content_file_paths.c.path))
1723 mapper(DSCFile, self.tbl_dsc_files,
1724 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1725 source_id = self.tbl_dsc_files.c.source,
1726 source = relation(DBSource),
1727 poolfile_id = self.tbl_dsc_files.c.file,
1728 poolfile = relation(PoolFile)))
1730 mapper(PoolFile, self.tbl_files,
1731 properties = dict(file_id = self.tbl_files.c.id,
1732 filesize = self.tbl_files.c.size,
1733 location_id = self.tbl_files.c.location,
1734 location = relation(Location)))
1736 mapper(Fingerprint, self.tbl_fingerprint,
1737 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1738 uid_id = self.tbl_fingerprint.c.uid,
1739 uid = relation(Uid),
1740 keyring_id = self.tbl_fingerprint.c.keyring,
1741 keyring = relation(Keyring)))
1743 mapper(Keyring, self.tbl_keyrings,
1744 properties = dict(keyring_name = self.tbl_keyrings.c.name,
1745 keyring_id = self.tbl_keyrings.c.id))
1747 mapper(Location, self.tbl_location,
1748 properties = dict(location_id = self.tbl_location.c.id,
1749 component_id = self.tbl_location.c.component,
1750 component = relation(Component),
1751 archive_id = self.tbl_location.c.archive,
1752 archive = relation(Archive),
1753 archive_type = self.tbl_location.c.type))
1755 mapper(Maintainer, self.tbl_maintainer,
1756 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1758 mapper(NewComment, self.tbl_new_comments,
1759 properties = dict(comment_id = self.tbl_new_comments.c.id))
1761 mapper(Override, self.tbl_override,
1762 properties = dict(suite_id = self.tbl_override.c.suite,
1763 suite = relation(Suite),
1764 component_id = self.tbl_override.c.component,
1765 component = relation(Component),
1766 priority_id = self.tbl_override.c.priority,
1767 priority = relation(Priority),
1768 section_id = self.tbl_override.c.section,
1769 section = relation(Section),
1770 overridetype_id = self.tbl_override.c.type,
1771 overridetype = relation(OverrideType)))
1773 mapper(OverrideType, self.tbl_override_type,
1774 properties = dict(overridetype = self.tbl_override_type.c.type,
1775 overridetype_id = self.tbl_override_type.c.id))
1777 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1778 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1779 filepath_id = self.tbl_pending_content_associations.c.filepath,
1780 filepath = relation(ContentFilepath),
1781 filename_id = self.tbl_pending_content_associations.c.filename,
1782 filename = relation(ContentFilename)))
1784 mapper(Priority, self.tbl_priority,
1785 properties = dict(priority_id = self.tbl_priority.c.id))
1787 mapper(Queue, self.tbl_queue,
1788 properties = dict(queue_id = self.tbl_queue.c.id))
1790 mapper(QueueBuild, self.tbl_queue_build,
1791 properties = dict(suite_id = self.tbl_queue_build.c.suite,
1792 queue_id = self.tbl_queue_build.c.queue,
1793 queue = relation(Queue, backref='queuebuild')))
1795 mapper(Section, self.tbl_section,
1796 properties = dict(section_id = self.tbl_section.c.id))
1798 mapper(DBSource, self.tbl_source,
1799 properties = dict(source_id = self.tbl_source.c.id,
1800 version = self.tbl_source.c.version,
1801 maintainer_id = self.tbl_source.c.maintainer,
1802 maintainer = relation(Maintainer,
1803 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1804 poolfile_id = self.tbl_source.c.file,
1805 poolfile = relation(PoolFile),
1806 fingerprint_id = self.tbl_source.c.sig_fpr,
1807 fingerprint = relation(Fingerprint),
1808 changedby_id = self.tbl_source.c.changedby,
1809 changedby = relation(Maintainer,
1810 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1811 srcfiles = relation(DSCFile,
1812 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1813 srcassociations = relation(SrcAssociation,
1814 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1816 mapper(SrcAssociation, self.tbl_src_associations,
1817 properties = dict(sa_id = self.tbl_src_associations.c.id,
1818 suite_id = self.tbl_src_associations.c.suite,
1819 suite = relation(Suite),
1820 source_id = self.tbl_src_associations.c.source,
1821 source = relation(DBSource)))
1823 mapper(SrcUploader, self.tbl_src_uploaders,
1824 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1825 source_id = self.tbl_src_uploaders.c.source,
1826 source = relation(DBSource,
1827 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1828 maintainer_id = self.tbl_src_uploaders.c.maintainer,
1829 maintainer = relation(Maintainer,
1830 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1832 mapper(Suite, self.tbl_suite,
1833 properties = dict(suite_id = self.tbl_suite.c.id))
1835 mapper(SuiteArchitecture, self.tbl_suite_architectures,
1836 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1837 suite = relation(Suite, backref='suitearchitectures'),
1838 arch_id = self.tbl_suite_architectures.c.architecture,
1839 architecture = relation(Architecture)))
1841 mapper(Uid, self.tbl_uid,
1842 properties = dict(uid_id = self.tbl_uid.c.id,
1843 fingerprint = relation(Fingerprint)))
1845 ## Connection functions
1846 def __createconn(self):
1847 from config import Config
1851 connstr = "postgres://%s" % cnf["DB::Host"]
1852 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1853 connstr += ":%s" % cnf["DB::Port"]
1854 connstr += "/%s" % cnf["DB::Name"]
1857 connstr = "postgres:///%s" % cnf["DB::Name"]
1858 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1859 connstr += "?port=%s" % cnf["DB::Port"]
1861 self.db_pg = create_engine(connstr, echo=self.debug)
1862 self.db_meta = MetaData()
1863 self.db_meta.bind = self.db_pg
1864 self.db_smaker = sessionmaker(bind=self.db_pg,
1868 self.__setuptables()
1869 self.__setupmappers()
1872 return self.db_smaker()
1874 __all__.append('DBConn')