]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/dbconn.py
miscellaneous fixups from testing
[dak.git] / daklib / dbconn.py
index 593b668f1889e54ec50910f80bef3af57619b518..f32a94bb91d550fa82886943a80c83d620ec613e 100755 (executable)
@@ -59,6 +59,18 @@ class Architecture(object):
     def __init__(self, *args, **kwargs):
         pass
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.arch_string== val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.arch_string != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def __repr__(self):
         return '<Architecture %s>' % self.arch_string
 
@@ -171,6 +183,24 @@ class DBBinary(object):
 
 __all__.append('DBBinary')
 
+def get_suites_binary_in(package, session=None):
+    """
+    Returns list of Suite objects which given C{package} name is in
+
+    @type source: str
+    @param source: DBBinary package name to search for
+
+    @rtype: list
+    @return: list of Suite objects for the given package
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
+
+__all__.append('get_suites_binary_in')
+
 def get_binary_from_id(id, session=None):
     """
     Returns DBBinary object for given C{id}
@@ -194,13 +224,19 @@ def get_binary_from_id(id, session=None):
 
 __all__.append('get_binary_from_id')
 
-def get_binaries_from_name(package, session=None):
+def get_binaries_from_name(package, version=None, architecture=None, session=None):
     """
     Returns list of DBBinary objects for given C{package} name
 
     @type package: str
     @param package: DBBinary package name to search for
 
+    @type version: str or None
+    @param version: Version to search for (or None)
+
+    @type package: str, list or None
+    @param package: Architectures to limit to (or None if no limit)
+
     @type session: Session
     @param session: Optional SQL session object (a temporary one will be
     generated if not supplied)
@@ -210,10 +246,42 @@ def get_binaries_from_name(package, session=None):
     """
     if session is None:
         session = DBConn().session()
-    return session.query(DBBinary).filter_by(package=package).all()
+
+    q = session.query(DBBinary).filter_by(package=package)
+
+    if version is not None:
+        q = q.filter_by(version=version)
+
+    if architecture is not None:
+        if not isinstance(architecture, list):
+            architecture = [architecture]
+        q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
+
+    return q.all()
 
 __all__.append('get_binaries_from_name')
 
+def get_binaries_from_source_id(source_id, session=None):
+    """
+    Returns list of DBBinary objects for given C{source_id}
+
+    @type source_id: int
+    @param source_id: source_id to search for
+
+    @type session: Session
+    @param session: Optional SQL session object (a temporary one will be
+    generated if not supplied)
+
+    @rtype: list
+    @return: list of DBBinary objects for the given name (may be empty)
+    """
+    if session is None:
+        session = DBConn().session()
+    return session.query(DBBinary).filter_by(source_id=source_id).all()
+
+__all__.append('get_binaries_from_source_id')
+
+
 def get_binary_from_name_suite(package, suitename, session=None):
     ### For dak examine-package
     ### XXX: Doesn't use object API yet
@@ -252,12 +320,25 @@ def get_binary_components(package, suitename, arch, session=None):
     return session.execute(query, vals)
 
 __all__.append('get_binary_components')
+
 ################################################################################
 
 class Component(object):
     def __init__(self, *args, **kwargs):
         pass
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.component_name == val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.component_name != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def __repr__(self):
         return '<Component %s>' % self.component_name
 
@@ -336,6 +417,9 @@ def get_or_set_contents_file_id(filename, session=None):
             session.add(cf)
             if privatetrans:
                 session.commit()
+                session.close()
+            else:
+                session.flush()
             return cf.cafilename_id
         else:
             return q.one().cafilename_id
@@ -410,7 +494,7 @@ class ContentFilepath(object):
 
 __all__.append('ContentFilepath')
 
-def get_or_set_contents_path_id(filepath, session):
+def get_or_set_contents_path_id(filepath, session=None):
     """
     Returns database id for given path.
 
@@ -439,6 +523,9 @@ def get_or_set_contents_path_id(filepath, session):
             session.add(cf)
             if privatetrans:
                 session.commit()
+                session.close()
+            else:
+                session.flush()
             return cf.cafilepath_id
         else:
             return q.one().cafilepath_id
@@ -485,27 +572,40 @@ def insert_content_paths(binary_id, fullpaths, session=None):
         privatetrans = True
 
     try:
+        # Insert paths
+        pathcache = {}
         for fullpath in fullpaths:
+            # Get the necessary IDs ...
             (path, file) = os.path.split(fullpath)
 
-            # Get the necessary IDs ...
+            filepath_id = get_or_set_contents_path_id(path, session)
+            filename_id = get_or_set_contents_file_id(file, session)
+
+            pathcache[fullpath] = (filepath_id, filename_id)
+
+        for fullpath, dat in pathcache.items():
             ca = ContentAssociation()
             ca.binary_id = binary_id
-            ca.filename_id = get_or_set_contents_file_id(file)
-            ca.filepath_id = get_or_set_contents_path_id(path)
+            ca.filepath_id = dat[0]
+            ca.filename_id = dat[1]
             session.add(ca)
 
         # Only commit if we set up the session ourself
         if privatetrans:
             session.commit()
+            session.close()
+        else:
+            session.flush()
 
         return True
+
     except:
         traceback.print_exc()
 
         # Only rollback if we set up the session ourself
         if privatetrans:
             session.rollback()
+            session.close()
 
         return False
 
@@ -522,6 +622,41 @@ class DSCFile(object):
 
 __all__.append('DSCFile')
 
+def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
+    """
+    Returns a list of DSCFiles which may be empty
+
+    @type dscfile_id: int (optional)
+    @param dscfile_id: the dscfile_id of the DSCFiles to find
+
+    @type source_id: int (optional)
+    @param source_id: the source id related to the DSCFiles to find
+
+    @type poolfile_id: int (optional)
+    @param poolfile_id: the poolfile id related to the DSCFiles to find
+
+    @rtype: list
+    @return: Possibly empty list of DSCFiles
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    q = session.query(DSCFile)
+
+    if dscfile_id is not None:
+        q = q.filter_by(dscfile_id=dscfile_id)
+
+    if source_id is not None:
+        q = q.filter_by(source_id=source_id)
+
+    if poolfile_id is not None:
+        q = q.filter_by(poolfile_id=poolfile_id)
+
+    return q.all()
+
+__all__.append('get_dscfiles')
+
 ################################################################################
 
 class PoolFile(object):
@@ -533,6 +668,76 @@ class PoolFile(object):
 
 __all__.append('PoolFile')
 
+def check_poolfile(filename, filesize, md5sum, location_id, session=None):
+    """
+    Returns a tuple:
+     (ValidFileFound [boolean or None], PoolFile object or None)
+
+    @type filename: string
+    @param filename: the filename of the file to check against the DB
+
+    @type filesize: int
+    @param filesize: the size of the file to check against the DB
+
+    @type md5sum: string
+    @param md5sum: the md5sum of the file to check against the DB
+
+    @type location_id: int
+    @param location_id: the id of the location to look in
+
+    @rtype: tuple
+    @return: Tuple of length 2.
+             If more than one file found with that name:
+                    (None,  None)
+             If valid pool file found: (True, PoolFile object)
+             If valid pool file not found:
+                    (False, None) if no file found
+                    (False, PoolFile object) if file found with size/md5sum mismatch
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    q = session.query(PoolFile).filter_by(filename=filename)
+    q = q.join(Location).filter_by(location_id=location_id)
+
+    if q.count() > 1:
+        return (None, None)
+    if q.count() < 1:
+        return (False, None)
+
+    obj = q.one()
+    if obj.md5sum != md5sum or obj.filesize != filesize:
+        return (False, obj)
+
+    return (True, obj)
+
+__all__.append('check_poolfile')
+
+def get_poolfile_by_id(file_id, session=None):
+    """
+    Returns a PoolFile objects or None for the given id
+
+    @type file_id: int
+    @param file_id: the id of the file to look for
+
+    @rtype: PoolFile or None
+    @return: either the PoolFile object or None
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    q = session.query(PoolFile).filter_by(file_id=file_id)
+
+    if q.count() > 0:
+        return q.one()
+
+    return None
+
+__all__.append('get_poolfile_by_id')
+
+
 def get_poolfile_by_name(filename, location_id=None, session=None):
     """
     Returns an array of PoolFile objects for the given filename and
@@ -548,7 +753,7 @@ def get_poolfile_by_name(filename, location_id=None, session=None):
     @return: array of PoolFile objects
     """
 
-    if session is not None:
+    if session is None:
         session = DBConn().session()
 
     q = session.query(PoolFile).filter_by(filename=filename)
@@ -571,7 +776,7 @@ def get_poolfile_like_name(filename, session=None):
     @return: array of PoolFile objects
     """
 
-    if session is not None:
+    if session is None:
         session = DBConn().session()
 
     # TODO: There must be a way of properly using bind parameters with %FOO%
@@ -592,6 +797,49 @@ class Fingerprint(object):
 
 __all__.append('Fingerprint')
 
+def get_or_set_fingerprint(fpr, session=None):
+    """
+    Returns Fingerprint object for given fpr.
+
+    If no matching fpr is found, a row is inserted.
+
+    @type fpr: string
+    @param fpr: The fpr to find / add
+
+    @type session: SQLAlchemy
+    @param session: Optional SQL session object (a temporary one will be
+    generated if not supplied).  If not passed, a commit will be performed at
+    the end of the function, otherwise the caller is responsible for commiting.
+    A flush will be performed either way.
+
+    @rtype: Fingerprint
+    @return: the Fingerprint object for the given fpr
+    """
+    privatetrans = False
+    if session is None:
+        session = DBConn().session()
+        privatetrans = True
+
+    try:
+        q = session.query(Fingerprint).filter_by(fingerprint=fpr)
+        if q.count() < 1:
+            fingerprint = Fingerprint()
+            fingerprint.fingerprint = fpr
+            session.add(fingerprint)
+            if privatetrans:
+                session.commit()
+            else:
+                session.flush()
+            return fingerprint
+        else:
+            return q.one()
+
+    except:
+        traceback.print_exc()
+        raise
+
+__all__.append('get_or_set_fingerprint')
+
 ################################################################################
 
 class Keyring(object):
@@ -667,6 +915,123 @@ class Maintainer(object):
 
 __all__.append('Maintainer')
 
+def get_or_set_maintainer(name, session=None):
+    """
+    Returns Maintainer object for given maintainer name.
+
+    If no matching maintainer name is found, a row is inserted.
+
+    @type name: string
+    @param name: The maintainer name to add
+
+    @type session: SQLAlchemy
+    @param session: Optional SQL session object (a temporary one will be
+    generated if not supplied).  If not passed, a commit will be performed at
+    the end of the function, otherwise the caller is responsible for commiting.
+    A flush will be performed either way.
+
+    @rtype: Maintainer
+    @return: the Maintainer object for the given maintainer
+    """
+    privatetrans = False
+    if session is None:
+        session = DBConn().session()
+        privatetrans = True
+
+    try:
+        q = session.query(Maintainer).filter_by(name=name)
+        if q.count() < 1:
+            maintainer = Maintainer()
+            maintainer.name = name
+            session.add(maintainer)
+            if privatetrans:
+                session.commit()
+            else:
+                session.flush()
+            return maintainer
+        else:
+            return q.one()
+
+    except:
+        traceback.print_exc()
+        raise
+
+__all__.append('get_or_set_maintainer')
+
+################################################################################
+
+class NewComment(object):
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def __repr__(self):
+        return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
+
+__all__.append('NewComment')
+
+def has_new_comment(package, version, session=None):
+    """
+    Returns true if the given combination of C{package}, C{version} has a comment.
+
+    @type package: string
+    @param package: name of the package
+
+    @type version: string
+    @param version: package version
+
+    @type session: Session
+    @param session: Optional SQLA session object (a temporary one will be
+    generated if not supplied)
+
+    @rtype: boolean
+    @return: true/false
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    q = session.query(NewComment)
+    q = q.filter_by(package=package)
+    q = q.filter_by(version=version)
+    return q.count() > 0
+
+__all__.append('has_new_comment')
+
+def get_new_comments(package=None, version=None, comment_id=None, session=None):
+    """
+    Returns (possibly empty) list of NewComment objects for the given
+    parameters
+
+    @type package: string (optional)
+    @param package: name of the package
+
+    @type version: string (optional)
+    @param version: package version
+
+    @type comment_id: int (optional)
+    @param comment_id: An id of a comment
+
+    @type session: Session
+    @param session: Optional SQLA session object (a temporary one will be
+    generated if not supplied)
+
+    @rtype: list
+    @return: A (possibly empty) list of NewComment objects will be returned
+
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    q = session.query(NewComment)
+    if package is not None: q = q.filter_by(package=package)
+    if version is not None: q = q.filter_by(version=version)
+    if comment_id is not None: q = q.filter_by(comment_id=comment_id)
+
+    return q.all()
+
+__all__.append('get_new_comments')
+
 ################################################################################
 
 class Override(object):
@@ -810,23 +1175,32 @@ def insert_pending_content_paths(package, fullpaths, session=None):
         q.delete()
 
         # Insert paths
+        pathcache = {}
         for fullpath in fullpaths:
             (path, file) = os.path.split(fullpath)
 
             if path.startswith( "./" ):
                 path = path[2:]
 
+            filepath_id = get_or_set_contents_path_id(path, session)
+            filename_id = get_or_set_contents_file_id(file, session)
+
+            pathcache[fullpath] = (filepath_id, filename_id)
+
+        for fullpath, dat in pathcache.items():
             pca = PendingContentAssociation()
             pca.package = package['Package']
             pca.version = package['Version']
-            pca.filename_id = get_or_set_contents_file_id(file, session)
-            pca.filepath_id = get_or_set_contents_path_id(path, session)
+            pca.filepath_id = dat[0]
+            pca.filename_id = dat[1]
             pca.architecture = arch_id
             session.add(pca)
 
         # Only commit if we set up the session ourself
         if privatetrans:
             session.commit()
+        else:
+            session.flush()
 
         return True
     except:
@@ -846,6 +1220,18 @@ class Priority(object):
     def __init__(self, *args, **kwargs):
         pass
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.priority == val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.priority != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def __repr__(self):
         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
 
@@ -875,6 +1261,29 @@ def get_priority(priority, session=None):
 
 __all__.append('get_priority')
 
+def get_priorities(session=None):
+    """
+    Returns dictionary of priority names -> id mappings
+
+    @type session: Session
+    @param session: Optional SQL session object (a temporary one will be
+    generated if not supplied)
+
+    @rtype: dictionary
+    @return: dictionary of priority names -> id mappings
+    """
+    if session is None:
+        session = DBConn().session()
+
+    ret = {}
+    q = session.query(Priority)
+    for x in q.all():
+        ret[x.priority] = x.priority_id
+
+    return ret
+
+__all__.append('get_priorities')
+
 ################################################################################
 
 class Queue(object):
@@ -938,7 +1347,7 @@ class Queue(object):
                 dest = os.path.join(dest_dir, file_entry)
 
                 # TODO: Move into database as above
-                if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+                if conf.FindB("Dinstall::SecurityQueueBuild"):
                     # Copy it since the original won't be readable by www-data
                     utils.copy(src, dest)
                 else:
@@ -1032,15 +1441,15 @@ class QueueBuild(object):
 
 __all__.append('QueueBuild')
 
-def get_queue_build(filename, suite_id, session=None):
+def get_queue_build(filename, suite, session=None):
     """
-    Returns QueueBuild object for given C{filename} and C{suite id}.
+    Returns QueueBuild object for given C{filename} and C{suite}.
 
     @type filename: string
     @param filename: The name of the file
 
-    @type suiteid: int
-    @param suiteid: Suite ID
+    @type suiteid: int or str
+    @param suiteid: Suite name or ID
 
     @type session: Session
     @param session: Optional SQLA session object (a temporary one will be
@@ -1052,7 +1461,12 @@ def get_queue_build(filename, suite_id, session=None):
     """
     if session is None:
         session = DBConn().session()
-    q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
+    if isinstance(suite, int):
+        q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
+    else:
+        q = session.query(QueueBuild).filter_by(filename=filename)
+        q = q.join(Suite).filter_by(suite_name=suite)
+
     if q.count() == 0:
         return None
     return q.one()
@@ -1065,6 +1479,18 @@ class Section(object):
     def __init__(self, *args, **kwargs):
         pass
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.section == val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.section != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def __repr__(self):
         return '<Section %s>' % self.section
 
@@ -1094,6 +1520,29 @@ def get_section(section, session=None):
 
 __all__.append('get_section')
 
+def get_sections(session=None):
+    """
+    Returns dictionary of section names -> id mappings
+
+    @type session: Session
+    @param session: Optional SQL session object (a temporary one will be
+    generated if not supplied)
+
+    @rtype: dictionary
+    @return: dictionary of section names -> id mappings
+    """
+    if session is None:
+        session = DBConn().session()
+
+    ret = {}
+    q = session.query(Section)
+    for x in q.all():
+        ret[x.section] = x.section_id
+
+    return ret
+
+__all__.append('get_sections')
+
 ################################################################################
 
 class DBSource(object):
@@ -1175,13 +1624,34 @@ def source_exists(source, source_version, suites = ["any"], session=None):
 
 __all__.append('source_exists')
 
-def get_sources_from_name(source, dm_upload_allowed=None, session=None):
+def get_suites_source_in(source, session=None):
     """
-    Returns list of DBSource objects for given C{source} name
+    Returns list of Suite objects which given C{source} name is in
 
     @type source: str
     @param source: DBSource package name to search for
 
+    @rtype: list
+    @return: list of Suite objects for the given source
+    """
+
+    if session is None:
+        session = DBConn().session()
+
+    return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
+
+__all__.append('get_suites_source_in')
+
+def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
+    """
+    Returns list of DBSource objects for given C{source} name and other parameters
+
+    @type source: str
+    @param source: DBSource package name to search for
+
+    @type source: str or None
+    @param source: DBSource version name to search for or None if not applicable
+
     @type dm_upload_allowed: bool
     @param dm_upload_allowed: If None, no effect.  If True or False, only
     return packages with that dm_upload_allowed setting
@@ -1197,6 +1667,10 @@ def get_sources_from_name(source, dm_upload_allowed=None, session=None):
         session = DBConn().session()
 
     q = session.query(DBSource).filter_by(source=source)
+
+    if version is not None:
+        q = q.filter_by(version=version)
+
     if dm_upload_allowed is not None:
         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
 
@@ -1284,6 +1758,18 @@ class Suite(object):
     def __repr__(self):
         return '<Suite %s>' % self.suite_name
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.suite_name == val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.suite_name != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def details(self):
         ret = []
         for disp, field in SUITE_FIELDS:
@@ -1405,6 +1891,18 @@ class Uid(object):
     def __init__(self, *args, **kwargs):
         pass
 
+    def __eq__(self, val):
+        if isinstance(val, str):
+            return (self.uid == val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
+    def __ne__(self, val):
+        if isinstance(val, str):
+            return (self.uid != val)
+        # This signals to use the normal comparison operator
+        return NotImplemented
+
     def __repr__(self):
         return '<Uid %s (%s)>' % (self.uid, self.name)
 
@@ -1470,6 +1968,8 @@ def get_or_set_uid(uidname, session=None):
             session.add(uid)
             if privatetrans:
                 session.commit()
+            else:
+                session.flush()
             return uid
         else:
             return q.one()
@@ -1526,6 +2026,7 @@ class DBConn(Singleton):
         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
         self.tbl_location = Table('location', self.db_meta, autoload=True)
         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
+        self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
         self.tbl_override = Table('override', self.db_meta, autoload=True)
         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
@@ -1634,6 +2135,9 @@ class DBConn(Singleton):
         mapper(Maintainer, self.tbl_maintainer,
                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
 
+        mapper(NewComment, self.tbl_new_comments,
+               properties = dict(comment_id = self.tbl_new_comments.c.id))
+
         mapper(Override, self.tbl_override,
                properties = dict(suite_id = self.tbl_override.c.suite,
                                  suite = relation(Suite),