dbconn.py 94.5 KB
Newer Older
1
#!/usr/bin/python
M
Mark Hymers 已提交
2

3 4 5 6 7
""" DB access class

@contact: Debian FTPMaster <ftpmaster@debian.org>
@copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
@copyright: 2008-2009  Mark Hymers <mhy@debian.org>
J
Joerg Jaspert 已提交
8
@copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
9
@copyright: 2009  Mike O'Connor <stew@debian.org>
10 11
@license: GNU General Public License version 2 or later
"""
M
Mark Hymers 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

################################################################################

# < mhy> I need a funny comment
# < sgran> two peanuts were walking down a dark street
# < sgran> one was a-salted
#  * mhy looks up the definition of "funny"

################################################################################

36
import apt_pkg
37
import daklib.daksubprocess
38
import os
39
from os.path import normpath
M
Mark Hymers 已提交
40
import re
M
Mark Hymers 已提交
41
import psycopg2
42
import subprocess
43
import traceback
T
Torsten Werner 已提交
44 45 46 47 48 49 50 51

try:
    # python >= 2.6
    import json
except:
    # python <= 2.5
    import simplejson as json

M
Mark Hymers 已提交
52 53 54
from datetime import datetime, timedelta
from errno import ENOENT
from tempfile import mkstemp, mkdtemp
55
from tarfile import TarFile
M
Mark Hymers 已提交
56

57 58
from inspect import getargspec

59
import sqlalchemy
60 61
from sqlalchemy import create_engine, Table, MetaData, Column, Integer, desc, \
    Text, ForeignKey
62
from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \
63
    backref, MapperExtension, EXT_CONTINUE, object_mapper, clear_mappers
64
from sqlalchemy import types as sqltypes
65 66
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.associationproxy import association_proxy
M
Mark Hymers 已提交
67

M
Mark Hymers 已提交
68 69
# Don't remove this, we re-export the exceptions to scripts which import us
from sqlalchemy.exc import *
70
from sqlalchemy.orm.exc import NoResultFound
M
Mark Hymers 已提交
71

72 73 74
# Only import Config until Queue stuff is changed to store its config
# in the database
from config import Config
M
Mark Hymers 已提交
75
from textutils import fix_maintainer
76
from dak_exceptions import DBUpdateError, NoSourceFieldError, FileExistsError
M
Mark Hymers 已提交
77

78 79 80 81 82
# suppress some deprecation warnings in squeeze related to sqlalchemy
import warnings
warnings.filterwarnings('ignore', \
    "The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'.*", \
    SADeprecationWarning)
83 84 85
warnings.filterwarnings('ignore', \
    "Predicate of partial index .* ignored during reflection", \
    SAWarning)
86 87


M
Mark Hymers 已提交
88 89
################################################################################

90 91 92
# Patch in support for the debversion field type so that it works during
# reflection

T
Torsten Werner 已提交
93 94 95 96 97 98 99 100
try:
    # that is for sqlalchemy 0.6
    UserDefinedType = sqltypes.UserDefinedType
except:
    # this one for sqlalchemy 0.5
    UserDefinedType = sqltypes.TypeEngine

class DebVersion(UserDefinedType):
101 102 103
    def get_col_spec(self):
        return "DEBVERSION"

104 105 106
    def bind_processor(self, dialect):
        return None

T
Torsten Werner 已提交
107 108
    # ' = None' is needed for sqlalchemy 0.5:
    def result_processor(self, dialect, coltype = None):
109 110
        return None

111
sa_major_version = sqlalchemy.__version__[0:3]
112
if sa_major_version in ["0.5", "0.6", "0.7", "0.8", "0.9"]:
C
Chris Lamb 已提交
113 114
    from sqlalchemy.databases import postgres
    postgres.ischema_names['debversion'] = DebVersion
115
else:
116
    raise Exception("dak only ported to SQLA versions 0.5 to 0.9.  See daklib/dbconn.py")
117 118 119

################################################################################

120
__all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
121 122 123

################################################################################

124
def session_wrapper(fn):
C
Chris Lamb 已提交
125 126 127 128
    """
    Wrapper around common ".., session=None):" handling. If the wrapped
    function is called without passing 'session', we create a local one
    and destroy it when the function ends.
129 130 131 132

    Also attaches a commit_or_flush method to the session; if we created a
    local session, this is a synonym for session.commit(), otherwise it is a
    synonym for session.flush().
C
Chris Lamb 已提交
133 134
    """

135 136 137
    def wrapped(*args, **kwargs):
        private_transaction = False

138
        # Find the session object
C
Chris Lamb 已提交
139 140 141
        session = kwargs.get('session')

        if session is None:
142 143 144 145 146 147 148
            if len(args) <= len(getargspec(fn)[0]) - 1:
                # No session specified as last argument or in kwargs
                private_transaction = True
                session = kwargs['session'] = DBConn().session()
            else:
                # Session is last argument in args
                session = args[-1]
149
                if session is None:
M
fixup  
Mark Hymers 已提交
150
                    args = list(args)
151 152
                    session = args[-1] = DBConn().session()
                    private_transaction = True
153 154 155 156 157

        if private_transaction:
            session.commit_or_flush = session.commit
        else:
            session.commit_or_flush = session.flush
158 159 160 161 162 163

        try:
            return fn(*args, **kwargs)
        finally:
            if private_transaction:
                # We created a session; close it.
164
                session.close()
165

166 167 168
    wrapped.__doc__ = fn.__doc__
    wrapped.func_name = fn.func_name

169 170
    return wrapped

F
Frank Lichtenheld 已提交
171 172
__all__.append('session_wrapper')

173 174
################################################################################

175 176 177
class ORMObject(object):
    """
    ORMObject is a base class for all ORM classes mapped by SQLalchemy. All
T
Torsten Werner 已提交
178
    derived classes must implement the properties() method.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    """

    def properties(self):
        '''
        This method should be implemented by all derived classes and returns a
        list of the important properties. The properties 'created' and
        'modified' will be added automatically. A suffix '_count' should be
        added to properties that are lists or query objects. The most important
        property name should be returned as the first element in the list
        because it is used by repr().
        '''
        return []

    def json(self):
        '''
        Returns a JSON representation of the object based on the properties
        returned from the properties() method.
        '''
        data = {}
        # add created and modified
        all_properties = self.properties() + ['created', 'modified']
        for property in all_properties:
            # check for list or query
            if property[-6:] == '_count':
203 204 205 206
                real_property = property[:-6]
                if not hasattr(self, real_property):
                    continue
                value = getattr(self, real_property)
207 208 209 210
                if hasattr(value, '__len__'):
                    # list
                    value = len(value)
                elif hasattr(value, 'count'):
211 212 213
                    # query (but not during validation)
                    if self.in_validation:
                        continue
214 215 216 217
                    value = value.count()
                else:
                    raise KeyError('Do not understand property %s.' % property)
            else:
218 219
                if not hasattr(self, property):
                    continue
220 221 222 223
                # plain object
                value = getattr(self, property)
                if value is None:
                    # skip None
224
                    continue
225 226 227 228 229
                elif isinstance(value, ORMObject):
                    # use repr() for ORMObject types
                    value = repr(value)
                else:
                    # we want a string for all other types because json cannot
T
Torsten Werner 已提交
230
                    # encode everything
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
                    value = str(value)
            data[property] = value
        return json.dumps(data)

    def classname(self):
        '''
        Returns the name of the class.
        '''
        return type(self).__name__

    def __repr__(self):
        '''
        Returns a short string representation of the object using the first
        element from the properties() method.
        '''
        primary_property = self.properties()[0]
        value = getattr(self, primary_property)
        return '<%s %s>' % (self.classname(), str(value))

    def __str__(self):
        '''
        Returns a human readable form of the object using the properties()
        method.
        '''
        return '<%s %s>' % (self.classname(), self.json())

257 258 259 260 261 262 263 264 265 266
    def not_null_constraints(self):
        '''
        Returns a list of properties that must be not NULL. Derived classes
        should override this method if needed.
        '''
        return []

    validation_message = \
        "Validation failed because property '%s' must not be empty in object\n%s"

267 268
    in_validation = False

269 270
    def validate(self):
        '''
271 272 273
        This function validates the not NULL constraints as returned by
        not_null_constraints(). It raises the DBUpdateError exception if
        validation fails.
274
        '''
275
        for property in self.not_null_constraints():
276 277 278 279 280 281
            # TODO: It is a bit awkward that the mapper configuration allow
            # directly setting the numeric _id columns. We should get rid of it
            # in the long run.
            if hasattr(self, property + '_id') and \
                getattr(self, property + '_id') is not None:
                continue
282
            if not hasattr(self, property) or getattr(self, property) is None:
283 284 285 286 287
                # str() might lead to races due to a 2nd flush
                self.in_validation = True
                message = self.validation_message % (property, str(self))
                self.in_validation = False
                raise DBUpdateError(message)
288

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    @classmethod
    @session_wrapper
    def get(cls, primary_key,  session = None):
        '''
        This is a support function that allows getting an object by its primary
        key.

        Architecture.get(3[, session])

        instead of the more verbose

        session.query(Architecture).get(3)
        '''
        return session.query(cls).get(primary_key)

304 305 306 307 308 309 310 311 312
    def session(self, replace = False):
        '''
        Returns the current session that is associated with the object. May
        return None is object is in detached state.
        '''

        return object_session(self)

    def clone(self, session = None):
T
Tollef Fog Heen 已提交
313
        """
314 315
        Clones the current object in a new session and returns the new clone. A
        fresh session is created if the optional session parameter is not
316 317
        provided. The function will fail if a session is provided and has
        unflushed changes.
318

319 320 321
        RATIONALE: SQLAlchemy's session is not thread safe. This method clones
        an existing object to allow several threads to work with their own
        instances of an ORMObject.
322

323 324 325
        WARNING: Only persistent (committed) objects can be cloned. Changes
        made to the original object that are not committed yet will get lost.
        The session of the new object will always be rolled back to avoid
T
Tollef Fog Heen 已提交
326 327
        resource leaks.
        """
328 329

        if self.session() is None:
330 331
            raise RuntimeError( \
                'Method clone() failed for detached object:\n%s' % self)
332 333 334 335
        self.session().flush()
        mapper = object_mapper(self)
        primary_key = mapper.primary_key_from_instance(self)
        object_class = self.__class__
336 337 338 339 340
        if session is None:
            session = DBConn().session()
        elif len(session.new) + len(session.dirty) + len(session.deleted) > 0:
            raise RuntimeError( \
                'Method clone() failed due to unflushed changes in session.')
341
        new_object = session.query(object_class).get(primary_key)
342
        session.rollback()
343 344 345 346 347
        if new_object is None:
            raise RuntimeError( \
                'Method clone() failed for non-persistent object:\n%s' % self)
        return new_object

348 349 350 351
__all__.append('ORMObject')

################################################################################

352 353 354 355 356 357 358 359
class Validator(MapperExtension):
    '''
    This class calls the validate() method for each instance for the
    'before_update' and 'before_insert' events. A global object validator is
    used for configuring the individual mappers.
    '''

    def before_update(self, mapper, connection, instance):
M
Mark Hymers 已提交
360
        instance.validate()
361 362 363
        return EXT_CONTINUE

    def before_insert(self, mapper, connection, instance):
M
Mark Hymers 已提交
364
        instance.validate()
365 366 367 368 369 370
        return EXT_CONTINUE

validator = Validator()

################################################################################

371 372 373 374 375 376 377 378 379 380 381 382 383 384
class ACL(ORMObject):
    def __repr__(self):
        return "<ACL {0}>".format(self.name)

__all__.append('ACL')

class ACLPerSource(ORMObject):
    def __repr__(self):
        return "<ACLPerSource acl={0} fingerprint={1} source={2} reason={3}>".format(self.acl.name, self.fingerprint.fingerprint, self.source, self.reason)

__all__.append('ACLPerSource')

################################################################################

385
class Architecture(ORMObject):
T
Torsten Werner 已提交
386 387 388
    def __init__(self, arch_string = None, description = None):
        self.arch_string = arch_string
        self.description = description
M
Mark Hymers 已提交
389

390 391 392 393 394 395 396 397 398 399 400 401
    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

402 403
    def properties(self):
        return ['arch_string', 'arch_id', 'suites_count']
M
Mark Hymers 已提交
404

405 406
    def not_null_constraints(self):
        return ['arch_string']
407

408 409
__all__.append('Architecture')

410
@session_wrapper
411 412 413 414 415 416 417 418 419 420 421 422 423 424
def get_architecture(architecture, session=None):
    """
    Returns database id for given C{architecture}.

    @type architecture: string
    @param architecture: The name of the architecture

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Architecture
    @return: Architecture object for the given arch (None if not present)
    """
425

426
    q = session.query(Architecture).filter_by(arch_string=architecture)
427

428 429 430 431
    try:
        return q.one()
    except NoResultFound:
        return None
432

433 434
__all__.append('get_architecture')

M
Mark Hymers 已提交
435 436
################################################################################

M
Mark Hymers 已提交
437
class Archive(object):
M
Mark Hymers 已提交
438 439
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
440 441

    def __repr__(self):
C
Chris Lamb 已提交
442
        return '<Archive %s>' % self.archive_name
M
Mark Hymers 已提交
443

444 445
__all__.append('Archive')

446
@session_wrapper
447 448
def get_archive(archive, session=None):
    """
F
Frank Lichtenheld 已提交
449
    returns database id for given C{archive}.
450 451 452 453 454 455 456 457 458 459 460 461 462

    @type archive: string
    @param archive: the name of the arhive

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Archive
    @return: Archive object for the given name (None if not present)

    """
    archive = archive.lower()
463

464
    q = session.query(Archive).filter_by(archive_name=archive)
465

466 467 468 469
    try:
        return q.one()
    except NoResultFound:
        return None
470

471
__all__.append('get_archive')
472

M
Mark Hymers 已提交
473 474
################################################################################

475 476 477 478 479 480 481 482 483 484 485 486 487
class ArchiveFile(object):
    def __init__(self, archive=None, component=None, file=None):
        self.archive = archive
        self.component = component
        self.file = file
    @property
    def path(self):
        return os.path.join(self.archive.path, 'pool', self.component.component_name, self.file.filename)

__all__.append('ArchiveFile')

################################################################################

488
class BinContents(ORMObject):
489 490 491 492 493
    def __init__(self, file = None, binary = None):
        self.file = file
        self.binary = binary

    def properties(self):
494
        return ['file', 'binary']
M
Mike O'Connor 已提交
495 496 497 498 499

__all__.append('BinContents')

################################################################################

500 501 502
class DBBinary(ORMObject):
    def __init__(self, package = None, source = None, version = None, \
        maintainer = None, architecture = None, poolfile = None, \
503
        binarytype = 'deb', fingerprint=None):
504 505 506 507 508 509 510
        self.package = package
        self.source = source
        self.version = version
        self.maintainer = maintainer
        self.architecture = architecture
        self.poolfile = poolfile
        self.binarytype = binarytype
511
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
512

M
Mark Hymers 已提交
513 514 515 516
    @property
    def pkid(self):
        return self.binary_id

517 518 519
    def properties(self):
        return ['package', 'version', 'maintainer', 'source', 'architecture', \
            'poolfile', 'binarytype', 'fingerprint', 'install_date', \
M
Mark Hymers 已提交
520
            'suites_count', 'binary_id', 'contents_count', 'extra_sources']
521 522

    def not_null_constraints(self):
523 524
        return ['package', 'version', 'maintainer', 'source',  'poolfile', \
            'binarytype']
M
Mark Hymers 已提交
525

526 527
    metadata = association_proxy('key', 'value')

528 529 530
    def scan_contents(self):
        '''
        Yields the contents of the package. Only regular files are yielded and
531 532 533
        the path names are normalized after converting them from either utf-8
        or iso8859-1 encoding. It yields the string ' <EMPTY PACKAGE>' if the
        package does not contain any regular file.
534 535
        '''
        fullpath = self.poolfile.fullpath
536 537
        dpkg_cmd = ('dpkg-deb', '--fsys-tarfile', fullpath)
        dpkg = daklib.daksubprocess.Popen(dpkg_cmd, stdout=subprocess.PIPE)
538
        tar = TarFile.open(fileobj = dpkg.stdout, mode = 'r|')
539
        for member in tar.getmembers():
540
            if not member.isdir():
541 542
                name = normpath(member.name)
                # enforce proper utf-8 encoding
543
                try:
544
                    name.decode('utf-8')
T
bugfix  
Torsten Werner 已提交
545
                except UnicodeDecodeError:
546 547
                    name = name.decode('iso8859-1').encode('utf-8')
                yield name
548
        tar.close()
549 550
        dpkg.stdout.close()
        dpkg.wait()
551

M
Mark Hymers 已提交
552 553 554 555
    def read_control(self):
        '''
        Reads the control information from a binary.

M
Mark Hymers 已提交
556 557
        @rtype: text
        @return: stanza text of the control section.
M
Mark Hymers 已提交
558
        '''
559
        import utils
M
Mark Hymers 已提交
560
        fullpath = self.poolfile.fullpath
561 562
        with open(fullpath, 'r') as deb_file:
            return utils.deb_extract_control(deb_file)
M
Mark Hymers 已提交
563 564 565 566 567

    def read_control_fields(self):
        '''
        Reads the control information from a binary and return
        as a dictionary.
M
Mark Hymers 已提交
568

M
Mark Hymers 已提交
569 570 571 572 573
        @rtype: dict
        @return: fields of the control section as a dictionary.
        '''
        stanza = self.read_control()
        return apt_pkg.TagSection(stanza)
M
Mark Hymers 已提交
574

575
__all__.append('DBBinary')
576

577
@session_wrapper
578 579 580 581
def get_suites_binary_in(package, session=None):
    """
    Returns list of Suite objects which given C{package} name is in

J
Joerg Jaspert 已提交
582 583
    @type package: str
    @param package: DBBinary package name to search for
584 585 586 587 588

    @rtype: list
    @return: list of Suite objects for the given package
    """

589
    return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all()
590 591 592

__all__.append('get_suites_binary_in')

593
@session_wrapper
594
def get_component_by_package_suite(package, suite_list, arch_list=[], session=None):
595 596
    '''
    Returns the component name of the newest binary package in suite_list or
597 598
    None if no package is found. The result can be optionally filtered by a list
    of architecture names.
599 600 601

    @type package: str
    @param package: DBBinary package name to search for
602

603 604 605
    @type suite_list: list of str
    @param suite_list: list of suite_name items

606 607 608
    @type arch_list: list of str
    @param arch_list: optional list of arch_string items that defaults to []

609 610 611 612
    @rtype: str or NoneType
    @return: name of component or None
    '''

613 614 615 616 617 618
    q = session.query(DBBinary).filter_by(package = package). \
        join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list))
    if len(arch_list) > 0:
        q = q.join(DBBinary.architecture). \
            filter(Architecture.arch_string.in_(arch_list))
    binary = q.order_by(desc(DBBinary.version)).first()
619 620 621
    if binary is None:
        return None
    else:
622
        return binary.poolfile.component.component_name
623 624

__all__.append('get_component_by_package_suite')
M
Mark Hymers 已提交
625

M
Mark Hymers 已提交
626 627
################################################################################

628 629 630 631 632
class BuildQueue(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
633
        return '<BuildQueue %s>' % self.queue_name
634 635 636 637 638

__all__.append('BuildQueue')

################################################################################

639 640 641
class Component(ORMObject):
    def __init__(self, component_name = None):
        self.component_name = component_name
M
Mark Hymers 已提交
642

643 644 645 646 647 648 649 650 651 652 653 654
    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

655
    def properties(self):
656
        return ['component_name', 'component_id', 'description', \
A
Ansgar Burchardt 已提交
657
            'meets_dfsg', 'overrides_count']
658 659 660

    def not_null_constraints(self):
        return ['component_name']
M
Mark Hymers 已提交
661

662 663 664

__all__.append('Component')

665
@session_wrapper
666 667 668 669 670 671 672 673 674 675 676 677
def get_component(component, session=None):
    """
    Returns database id for given C{component}.

    @type component: string
    @param component: The name of the override type

    @rtype: int
    @return: the database id for the given component

    """
    component = component.lower()
678

679
    q = session.query(Component).filter_by(component_name=component)
680

681 682 683 684
    try:
        return q.one()
    except NoResultFound:
        return None
685

686 687
__all__.append('get_component')

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
@session_wrapper
def get_mapped_component(component_name, session=None):
    """get component after mappings

    Evaluate component mappings from ComponentMappings in dak.conf for the
    given component name.

    @todo: ansgar wants to get rid of this. It's currently only used for
           the security archive

    @type  component_name: str
    @param component_name: component name

    @param session: database session

    @rtype:  L{daklib.dbconn.Component} or C{None}
    @return: component after applying maps or C{None}
    """
    cnf = Config()
    for m in cnf.value_list("ComponentMappings"):
        (src, dst) = m.split()
        if component_name == src:
            component_name = dst
    component = session.query(Component).filter_by(component_name=component_name).first()
    return component

__all__.append('get_mapped_component')

M
Mark Hymers 已提交
716 717 718 719 720 721 722 723 724 725 726 727 728
@session_wrapper
def get_component_names(session=None):
    """
    Returns list of strings of component names.

    @rtype: list
    @return: list of strings of component names
    """

    return [ x.component_name for x in session.query(Component).all() ]

__all__.append('get_component_names')

M
Mark Hymers 已提交
729 730
################################################################################

M
Mark Hymers 已提交
731
class DBConfig(object):
M
Mark Hymers 已提交
732 733
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
734 735 736 737

    def __repr__(self):
        return '<DBConfig %s>' % self.name

738 739
__all__.append('DBConfig')

M
Mark Hymers 已提交
740 741
################################################################################

742
@session_wrapper
743 744 745 746 747 748 749 750 751 752
def get_or_set_contents_file_id(filename, session=None):
    """
    Returns database id for given filename.

    If no matching file is found, a row is inserted.

    @type filename: string
    @param filename: The filename
    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
M
Mark Hymers 已提交
753 754
    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.
755 756 757 758 759

    @rtype: int
    @return: the database id for the given component
    """

760
    q = session.query(ContentFilename).filter_by(filename=filename)
761 762 763 764

    try:
        ret = q.one().cafilename_id
    except NoResultFound:
765 766 767
        cf = ContentFilename()
        cf.filename = filename
        session.add(cf)
768
        session.commit_or_flush()
769
        ret = cf.cafilename_id
770

771
    return ret
772 773 774

__all__.append('get_or_set_contents_file_id')

775
@session_wrapper
M
Mark Hymers 已提交
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
def get_contents(suite, overridetype, section=None, session=None):
    """
    Returns contents for a suite / overridetype combination, limiting
    to a section if not None.

    @type suite: Suite
    @param suite: Suite object

    @type overridetype: OverrideType
    @param overridetype: OverrideType object

    @type section: Section
    @param section: Optional section object to limit results to

    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: ResultsProxy
    @return: ResultsProxy object set up to return tuples of (filename, section,
    package, arch_id)
    """

    # find me all of the contents for a given suite
    contents_q = """SELECT (p.path||'/'||n.file) AS fn,
                            s.section,
                            b.package,
                            b.architecture
                   FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
                   JOIN content_file_names n ON (c.filename=n.id)
                   JOIN binaries b ON (b.id=c.binary_pkg)
                   JOIN override o ON (o.package=b.package)
                   JOIN section s ON (s.id=o.section)
                   WHERE o.suite = :suiteid AND o.type = :overridetypeid
                   AND b.type=:overridetypename"""

    vals = {'suiteid': suite.suite_id,
            'overridetypeid': overridetype.overridetype_id,
            'overridetypename': overridetype.overridetype}

    if section is not None:
        contents_q += " AND s.id = :sectionid"
        vals['sectionid'] = section.section_id

    contents_q += " ORDER BY fn"

822
    return session.execute(contents_q, vals)
M
Mark Hymers 已提交
823 824 825

__all__.append('get_contents')

M
Mark Hymers 已提交
826 827
################################################################################

M
Mark Hymers 已提交
828
class ContentFilepath(object):
M
Mark Hymers 已提交
829 830
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
831 832 833 834

    def __repr__(self):
        return '<ContentFilepath %s>' % self.filepath

835 836
__all__.append('ContentFilepath')

837
@session_wrapper
M
Mark Hymers 已提交
838
def get_or_set_contents_path_id(filepath, session=None):
839 840 841 842 843
    """
    Returns database id for given path.

    If no matching file is found, a row is inserted.

J
Joerg Jaspert 已提交
844 845 846
    @type filepath: string
    @param filepath: The filepath

847 848
    @type session: SQLAlchemy
    @param session: Optional SQL session object (a temporary one will be
M
Mark Hymers 已提交
849 850
    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.
851 852 853 854 855

    @rtype: int
    @return: the database id for the given path
    """

856
    q = session.query(ContentFilepath).filter_by(filepath=filepath)
857 858 859 860

    try:
        ret = q.one().cafilepath_id
    except NoResultFound:
861 862 863
        cf = ContentFilepath()
        cf.filepath = filepath
        session.add(cf)
864
        session.commit_or_flush()
865
        ret = cf.cafilepath_id
866

867
    return ret
868 869 870

__all__.append('get_or_set_contents_path_id')

M
Mark Hymers 已提交
871 872
################################################################################

873
class ContentAssociation(object):
M
Mark Hymers 已提交
874 875
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
876 877 878 879

    def __repr__(self):
        return '<ContentAssociation %s>' % self.ca_id

880 881
__all__.append('ContentAssociation')

882 883 884 885 886 887 888 889 890 891 892 893
def insert_content_paths(binary_id, fullpaths, session=None):
    """
    Make sure given path is associated with given binary id

    @type binary_id: int
    @param binary_id: the id of the binary
    @type fullpaths: list
    @param fullpaths: the list of paths of the file being associated with the binary
    @type session: SQLAlchemy session
    @param session: Optional SQLAlchemy session.  If this is passed, the caller
    is responsible for ensuring a transaction has begun and committing the
    results or rolling back based on the result code.  If not passed, a commit
M
Mark Hymers 已提交
894 895
    will be performed at the end of the function, otherwise the caller is
    responsible for commiting.
896 897 898 899 900 901 902 903 904 905

    @return: True upon success
    """

    privatetrans = False
    if session is None:
        session = DBConn().session()
        privatetrans = True

    try:
M
Mark Hymers 已提交
906
        # Insert paths
907 908 909 910 911
        def generate_path_dicts():
            for fullpath in fullpaths:
                if fullpath.startswith( './' ):
                    fullpath = fullpath[2:]

912
                yield {'filename':fullpath, 'id': binary_id }
913

914 915 916
        for d in generate_path_dicts():
            session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
                         d )
917

M
Mike O'Connor 已提交
918
        session.commit()
919
        if privatetrans:
M
Mark Hymers 已提交
920
            session.close()
921
        return True
M
Mark Hymers 已提交
922

923 924 925 926 927 928
    except:
        traceback.print_exc()

        # Only rollback if we set up the session ourself
        if privatetrans:
            session.rollback()
M
Mark Hymers 已提交
929
            session.close()
930 931 932 933 934

        return False

__all__.append('insert_content_paths')

M
Mark Hymers 已提交
935 936
################################################################################

M
Mark Hymers 已提交
937
class DSCFile(object):
M
Mark Hymers 已提交
938 939
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
940 941 942 943

    def __repr__(self):
        return '<DSCFile %s>' % self.dscfile_id

944 945
__all__.append('DSCFile')

946
@session_wrapper
M
Mark Hymers 已提交
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
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
    """

    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)

975
    return q.all()
M
Mark Hymers 已提交
976 977 978

__all__.append('get_dscfiles')

M
Mark Hymers 已提交
979 980
################################################################################

A
Ansgar Burchardt 已提交
981 982 983 984 985 986 987 988 989 990 991
class ExternalOverride(ORMObject):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<ExternalOverride %s = %s: %s>' % (self.package, self.key, self.value)

__all__.append('ExternalOverride')

################################################################################

992
class PoolFile(ORMObject):
A
Ansgar Burchardt 已提交
993
    def __init__(self, filename = None, filesize = -1, \
994 995 996 997
        md5sum = None):
        self.filename = filename
        self.filesize = filesize
        self.md5sum = md5sum
M
Mark Hymers 已提交
998

M
Mark Hymers 已提交
999 1000
    @property
    def fullpath(self):
1001
        session = DBConn().session().object_session(self)
1002 1003 1004
        af = session.query(ArchiveFile).join(Archive) \
                    .filter(ArchiveFile.file == self) \
                    .order_by(Archive.tainted.desc()).first()
1005
        return af.path
M
Mark Hymers 已提交
1006

1007 1008 1009 1010 1011 1012 1013
    @property
    def component(self):
        session = DBConn().session().object_session(self)
        component_id = session.query(ArchiveFile.component_id).filter(ArchiveFile.file == self) \
                              .group_by(ArchiveFile.component_id).one()
        return session.query(Component).get(component_id)

1014 1015 1016 1017
    @property
    def basename(self):
        return os.path.basename(self.filename)

1018
    def is_valid(self, filesize = -1, md5sum = None):
1019
        return self.filesize == long(filesize) and self.md5sum == md5sum
T
Torsten Werner 已提交
1020

1021 1022
    def properties(self):
        return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum', \
1023
            'sha256sum', 'source', 'binary', 'last_used']
1024

1025
    def not_null_constraints(self):
1026
        return ['filename', 'md5sum']
T
Torsten Werner 已提交
1027

1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    def identical_to(self, filename):
        """
        compare size and hash with the given file

        @rtype: bool
        @return: true if the given file has the same size and hash as this object; false otherwise
        """
        st = os.stat(filename)
        if self.filesize != st.st_size:
            return False

        f = open(filename, "r")
        sha256sum = apt_pkg.sha256sum(f)
        if sha256sum != self.sha256sum:
            return False

        return True

1046 1047
__all__.append('PoolFile')

1048
@session_wrapper
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
def get_poolfile_like_name(filename, session=None):
    """
    Returns an array of PoolFile objects which are like the given name

    @type filename: string
    @param filename: the filename of the file to check against the DB

    @rtype: array
    @return: array of PoolFile objects
    """

    # TODO: There must be a way of properly using bind parameters with %FOO%
M
Mark Hymers 已提交
1061
    q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1062

1063
    return q.all()
1064 1065 1066

__all__.append('get_poolfile_like_name')

M
Mark Hymers 已提交
1067 1068
################################################################################

1069
class Fingerprint(ORMObject):
T
Torsten Werner 已提交
1070 1071
    def __init__(self, fingerprint = None):
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
1072

1073 1074 1075 1076 1077 1078
    def properties(self):
        return ['fingerprint', 'fingerprint_id', 'keyring', 'uid', \
            'binary_reject']

    def not_null_constraints(self):
        return ['fingerprint']
M
Mark Hymers 已提交
1079

1080 1081
__all__.append('Fingerprint')

M
Mark Hymers 已提交
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
@session_wrapper
def get_fingerprint(fpr, session=None):
    """
    Returns Fingerprint object for given fpr.

    @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).

    @rtype: Fingerprint
    @return: the Fingerprint object for the given fpr or None
    """

    q = session.query(Fingerprint).filter_by(fingerprint=fpr)

    try:
        ret = q.one()
    except NoResultFound:
        ret = None

    return ret

__all__.append('get_fingerprint')

1109
@session_wrapper
M
Mark Hymers 已提交
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
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
    """

1129
    q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1130 1131 1132 1133

    try:
        ret = q.one()
    except NoResultFound:
1134 1135 1136
        fingerprint = Fingerprint()
        fingerprint.fingerprint = fpr
        session.add(fingerprint)
1137
        session.commit_or_flush()
1138
        ret = fingerprint
M
Mark Hymers 已提交
1139

1140
    return ret
M
Mark Hymers 已提交
1141 1142 1143

__all__.append('get_or_set_fingerprint')

M
Mark Hymers 已提交
1144 1145
################################################################################

M
Mark Hymers 已提交
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
# Helper routine for Keyring class
def get_ldap_name(entry):
    name = []
    for k in ["cn", "mn", "sn"]:
        ret = entry.get(k)
        if ret and ret[0] != "" and ret[0] != "-":
            name.append(ret[0])
    return " ".join(name)

################################################################################

M
Mark Hymers 已提交
1157
class Keyring(object):
M
Mark Hymers 已提交
1158 1159 1160
    keys = {}
    fpr_lookup = {}

M
Mark Hymers 已提交
1161 1162
    def __init__(self, *args, **kwargs):
        pass
M
Mark Hymers 已提交
1163 1164 1165 1166

    def __repr__(self):
        return '<Keyring %s>' % self.keyring_name

1167 1168
    def de_escape_gpg_str(self, txt):
        esclist = re.split(r'(\\x..)', txt)
M
Mark Hymers 已提交
1169 1170 1171 1172
        for x in range(1,len(esclist),2):
            esclist[x] = "%c" % (int(esclist[x][2:],16))
        return "".join(esclist)

T
Torsten Werner 已提交
1173 1174
    def parse_address(self, uid):
        """parses uid and returns a tuple of real name and email address"""
M
Mark Hymers 已提交
1175
        import email.Utils
T
Torsten Werner 已提交
1176 1177 1178 1179 1180 1181
        (name, address) = email.Utils.parseaddr(uid)
        name = re.sub(r"\s*[(].*[)]", "", name)
        name = self.de_escape_gpg_str(name)
        if name == "":
            name = uid
        return (name, address)
M
Mark Hymers 已提交
1182

T
Torsten Werner 已提交
1183
    def load_keys(self, keyring):
M
Mark Hymers 已提交
1184 1185 1186
        if not self.keyring_id:
            raise Exception('Must be initialized with database information')

1187 1188 1189 1190
        cmd = ["gpg", "--no-default-keyring", "--keyring", keyring,
               "--with-colons", "--fingerprint", "--fingerprint"]
        p = daklib.daksubprocess.Popen(cmd, stdout=subprocess.PIPE)

M
Mark Hymers 已提交
1191
        key = None
1192
        need_fingerprint = False
M
Mark Hymers 已提交
1193

1194
        for line in p.stdout:
M
Mark Hymers 已提交
1195 1196 1197
            field = line.split(":")
            if field[0] == "pub":
                key = field[4]
T
Torsten Werner 已提交
1198 1199 1200 1201
                self.keys[key] = {}
                (name, addr) = self.parse_address(field[9])
                if "@" in addr:
                    self.keys[key]["email"] = addr
M
Mark Hymers 已提交
1202
                    self.keys[key]["name"] = name
1203
                need_fingerprint = True
M
Mark Hymers 已提交
1204
            elif key and field[0] == "uid":
T
Torsten Werner 已提交
1205 1206 1207 1208
                (name, addr) = self.parse_address(field[9])
                if "email" not in self.keys[key] and "@" in addr:
                    self.keys[key]["email"] = addr
                    self.keys[key]["name"] = name
1209 1210
            elif need_fingerprint and field[0] == "fpr":
                self.keys[key]["fingerprints"] = [field[9]]
M
Mark Hymers 已提交
1211
                self.fpr_lookup[field[9]] = key
1212
                need_fingerprint = False
M
Mark Hymers 已提交
1213

1214 1215 1216 1217
        r = p.wait()
        if r != 0:
            raise subprocess.CalledProcessError(r, cmd)

M
Mark Hymers 已提交
1218 1219 1220 1221 1222 1223
    def import_users_from_ldap(self, session):
        import ldap
        cnf = Config()

        LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
        LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1224
        ca_cert_file = cnf.get('Import-LDAP-Fingerprints::CACertFile')
M
Mark Hymers 已提交
1225 1226

        l = ldap.open(LDAPServer)
1227 1228

        if ca_cert_file:
1229 1230 1231
            # TODO: This should request a new context and use
            # connection-specific options (i.e. "l.set_option(...)")

1232 1233
            # Request a new TLS context. If there was already one, libldap
            # would not change the TLS options (like which CAs to trust).
1234 1235 1236 1237
            #l.set_option(ldap.OPT_X_TLS_NEWCTX, True)
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD)
            #ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, None)
            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ca_cert_file)
1238 1239
            l.start_tls_s()

M
Mark Hymers 已提交
1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
        l.simple_bind_s("","")
        Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
               "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
               ["uid", "keyfingerprint", "cn", "mn", "sn"])

        ldap_fin_uid_id = {}

        byuid = {}
        byname = {}

        for i in Attrs:
            entry = i[1]
            uid = entry["uid"][0]
            name = get_ldap_name(entry)
            fingerprints = entry["keyFingerPrint"]
            keyid = None
            for f in fingerprints:
                key = self.fpr_lookup.get(f, None)
                if key not in self.keys:
                    continue
                self.keys[key]["uid"] = uid

                if keyid != None:
                    continue
                keyid = get_or_set_uid(uid, session).uid_id
                byuid[keyid] = (uid, name)
                byname[uid] = (keyid, name)

        return (byname, byuid)

    def generate_users_from_keyring(self, format, session):
        byuid = {}
        byname = {}
        any_invalid = False
        for x in self.keys.keys():
T
Torsten Werner 已提交
1275
            if "email" not in self.keys[x]:
M
Mark Hymers 已提交
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
                any_invalid = True
                self.keys[x]["uid"] = format % "invalid-uid"
            else:
                uid = format % self.keys[x]["email"]
                keyid = get_or_set_uid(uid, session).uid_id
                byuid[keyid] = (uid, self.keys[x]["name"])
                byname[uid] = (keyid, self.keys[x]["name"])
                self.keys[x]["uid"] = uid

        if any_invalid:
            uid = format % "invalid-uid"
            keyid = get_or_set_uid(uid, session).uid_id
            byuid[keyid] = (uid, "ungeneratable user id")
            byname[uid] = (keyid, "ungeneratable user id")

        return (byname, byuid)

1293 1294
__all__.append('Keyring')

1295
@session_wrapper
M
Mark Hymers 已提交
1296
def get_keyring(keyring, session=None):
1297
    """
M
Mark Hymers 已提交
1298
    If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1299 1300 1301 1302 1303 1304 1305 1306 1307
    If C{keyring} already has an entry, simply return the existing Keyring

    @type keyring: string
    @param keyring: the keyring name

    @rtype: Keyring
    @return: the Keyring object for this keyring
    """

1308
    q = session.query(Keyring).filter_by(keyring_name=keyring)
1309

1310 1311 1312
    try:
        return q.one()
    except NoResultFound:
M
Mark Hymers 已提交
1313
        return None
1314

M
Mark Hymers 已提交
1315
__all__.append('get_keyring')
1316

M
Mark Hymers 已提交
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
@session_wrapper
def get_active_keyring_paths(session=None):
    """
    @rtype: list
    @return: list of active keyring paths
    """
    return [ x.keyring_name for x in session.query(Keyring).filter(Keyring.active == True).order_by(desc(Keyring.priority)).all() ]

__all__.append('get_active_keyring_paths')

@session_wrapper
def get_primary_keyring_path(session=None):
    """
    Get the full path to the highest priority active keyring

    @rtype: str or None
    @return: path to the active keyring with the highest priority or None if no
             keyring is configured
    """
    keyrings = get_active_keyring_paths()

    if len(keyrings) > 0:
        return keyrings[0]
    else:
        return None

__all__.append('get_primary_keyring_path')

M
Mark Hymers 已提交
1345
################################################################################
1346

M
Mark Hymers 已提交
1347
class DBChange(object):
J
Joerg Jaspert 已提交
1348 1349 1350 1351
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
M
Mark Hymers 已提交
1352
        return '<DBChange %s>' % self.changesname
J
Joerg Jaspert 已提交
1353

M
Mark Hymers 已提交
1354
__all__.append('DBChange')
J
Joerg Jaspert 已提交
1355 1356

@session_wrapper
M
Mark Hymers 已提交
1357
def get_dbchange(filename, session=None):
J
Joerg Jaspert 已提交
1358
    """
M
Mark Hymers 已提交
1359
    returns DBChange object for given C{filename}.
J
Joerg Jaspert 已提交
1360

J
Joerg Jaspert 已提交
1361 1362
    @type filename: string
    @param filename: the name of the file
J
Joerg Jaspert 已提交
1363 1364 1365 1366 1367

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

J
Joerg Jaspert 已提交
1368 1369
    @rtype: DBChange
    @return:  DBChange object for the given filename (C{None} if not present)
1370

J
Joerg Jaspert 已提交
1371
    """
M
Mark Hymers 已提交
1372
    q = session.query(DBChange).filter_by(changesname=filename)
J
Joerg Jaspert 已提交
1373 1374 1375 1376 1377 1378

    try:
        return q.one()
    except NoResultFound:
        return None

M
Mark Hymers 已提交
1379
__all__.append('get_dbchange')
1380

M
Mark Hymers 已提交
1381 1382
################################################################################

1383
class Maintainer(ORMObject):
1384 1385
    def __init__(self, name = None):
        self.name = name
M
Mark Hymers 已提交
1386

1387 1388 1389 1390 1391
    def properties(self):
        return ['name', 'maintainer_id']

    def not_null_constraints(self):
        return ['name']
M
Mark Hymers 已提交
1392

M
Mark Hymers 已提交
1393 1394 1395 1396 1397 1398
    def get_split_maintainer(self):
        if not hasattr(self, 'name') or self.name is None:
            return ('', '', '', '')

        return fix_maintainer(self.name.strip())

1399 1400
__all__.append('Maintainer')

1401
@session_wrapper
M
Mark Hymers 已提交
1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420
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
    """

1421
    q = session.query(Maintainer).filter_by(name=name)
1422 1423 1424
    try:
        ret = q.one()
    except NoResultFound:
1425 1426 1427
        maintainer = Maintainer()
        maintainer.name = name
        session.add(maintainer)
1428
        session.commit_or_flush()
1429
        ret = maintainer
M
Mark Hymers 已提交
1430

1431
    return ret
M
Mark Hymers 已提交
1432 1433 1434

__all__.append('get_or_set_maintainer')

1435
@session_wrapper
C
Chris Lamb 已提交
1436
def get_maintainer(maintainer_id, session=None):
C
Chris Lamb 已提交
1437
    """
1438 1439
    Return the name of the maintainer behind C{maintainer_id} or None if that
    maintainer_id is invalid.
C
Chris Lamb 已提交
1440 1441 1442 1443

    @type maintainer_id: int
    @param maintainer_id: the id of the maintainer

1444 1445
    @rtype: Maintainer
    @return: the Maintainer with this C{maintainer_id}
C
Chris Lamb 已提交
1446 1447
    """

1448
    return session.query(Maintainer).get(maintainer_id)
C
Chris Lamb 已提交
1449 1450 1451

__all__.append('get_maintainer')

M
Mark Hymers 已提交
1452 1453
################################################################################

M
Mark Hymers 已提交
1454 1455 1456 1457 1458 1459 1460 1461 1462
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')

1463
@session_wrapper
1464
def has_new_comment(policy_queue, package, version, session=None):
M
Mark Hymers 已提交
1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
    """
    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
    """

1482
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
M
Mark Hymers 已提交
1483 1484
    q = q.filter_by(package=package)
    q = q.filter_by(version=version)
1485

1486
    return bool(q.count() > 0)
M
Mark Hymers 已提交
1487 1488 1489

__all__.append('has_new_comment')

1490
@session_wrapper
1491
def get_new_comments(policy_queue, package=None, version=None, comment_id=None, session=None):
M
Mark Hymers 已提交
1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512
    """
    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
    """

1513
    q = session.query(NewComment).filter_by(policy_queue=policy_queue)
M
Mark Hymers 已提交
1514 1515 1516 1517
    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)

1518
    return q.all()
M
Mark Hymers 已提交
1519 1520 1521 1522 1523

__all__.append('get_new_comments')

################################################################################

1524 1525 1526 1527 1528 1529 1530 1531 1532
class Override(ORMObject):
    def __init__(self, package = None, suite = None, component = None, overridetype = None, \
        section = None, priority = None):
        self.package = package
        self.suite = suite
        self.component = component
        self.overridetype = overridetype
        self.section = section
        self.priority = priority
M
Mark Hymers 已提交
1533

1534 1535 1536 1537 1538 1539
    def properties(self):
        return ['package', 'suite', 'component', 'overridetype', 'section', \
            'priority']

    def not_null_constraints(self):
        return ['package', 'suite', 'component', 'overridetype', 'section']
M
Mark Hymers 已提交
1540

1541 1542
__all__.append('Override')

1543
@session_wrapper
1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585
def get_override(package, suite=None, component=None, overridetype=None, session=None):
    """
    Returns Override object for the given parameters

    @type package: string
    @param package: The name of the package

    @type suite: string, list or None
    @param suite: The name of the suite (or suites if a list) to limit to.  If
                  None, don't limit.  Defaults to None.

    @type component: string, list or None
    @param component: The name of the component (or components if a list) to
                      limit to.  If None, don't limit.  Defaults to None.

    @type overridetype: string, list or None
    @param overridetype: The name of the overridetype (or overridetypes if a list) to
                         limit to.  If None, don't limit.  Defaults to None.

    @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 Override objects will be returned
    """

    q = session.query(Override)
    q = q.filter_by(package=package)

    if suite is not None:
        if not isinstance(suite, list): suite = [suite]
        q = q.join(Suite).filter(Suite.suite_name.in_(suite))

    if component is not None:
        if not isinstance(component, list): component = [component]
        q = q.join(Component).filter(Component.component_name.in_(component))

    if overridetype is not None:
        if not isinstance(overridetype, list): overridetype = [overridetype]
        q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))

1586
    return q.all()
1587 1588 1589 1590

__all__.append('get_override')


M
Mark Hymers 已提交
1591 1592
################################################################################

1593 1594 1595
class OverrideType(ORMObject):
    def __init__(self, overridetype = None):
        self.overridetype = overridetype
M
Mark Hymers 已提交
1596

1597
    def properties(self):
1598
        return ['overridetype', 'overridetype_id', 'overrides_count']
1599 1600 1601

    def not_null_constraints(self):
        return ['overridetype']
M
Mark Hymers 已提交
1602

1603 1604
__all__.append('OverrideType')

1605
@session_wrapper
1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619
def get_override_type(override_type, session=None):
    """
    Returns OverrideType object for given C{override type}.

    @type override_type: string
    @param override_type: The name of the override type

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: int
    @return: the database id for the given override type
    """
1620

M
Mark Hymers 已提交
1621
    q = session.query(OverrideType).filter_by(overridetype=override_type)
1622

1623 1624 1625 1626
    try:
        return q.one()
    except NoResultFound:
        return None
1627

1628 1629
__all__.append('get_override_type')

M
Mark Hymers 已提交
1630 1631
################################################################################

1632 1633 1634 1635 1636 1637 1638 1639 1640
class PolicyQueue(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<PolicyQueue %s>' % self.queue_name

__all__.append('PolicyQueue')

1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665
@session_wrapper
def get_policy_queue(queuename, session=None):
    """
    Returns PolicyQueue object for given C{queue name}

    @type queuename: string
    @param queuename: The name of the queue

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: PolicyQueue
    @return: PolicyQueue object for the given queue
    """

    q = session.query(PolicyQueue).filter_by(queue_name=queuename)

    try:
        return q.one()
    except NoResultFound:
        return None

__all__.append('get_policy_queue')

1666 1667
################################################################################

1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
class PolicyQueueUpload(object):
    def __cmp__(self, other):
        ret = cmp(self.changes.source, other.changes.source)
        if ret == 0:
            ret = apt_pkg.version_compare(self.changes.version, other.changes.version)
        if ret == 0:
            if self.source is not None and other.source is None:
                ret = -1
            elif self.source is None and other.source is not None:
                ret = 1
        if ret == 0:
            ret = cmp(self.changes.changesname, other.changes.changesname)
        return ret

__all__.append('PolicyQueueUpload')

################################################################################

class PolicyQueueByhandFile(object):
    pass

__all__.append('PolicyQueueByhandFile')

################################################################################

1693 1694 1695 1696 1697 1698 1699 1700 1701 1702
class Priority(ORMObject):
    def __init__(self, priority = None, level = None):
        self.priority = priority
        self.level = level

    def properties(self):
        return ['priority', 'priority_id', 'level', 'overrides_count']

    def not_null_constraints(self):
        return ['priority', 'level']
M
Mark Hymers 已提交
1703

1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715
    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

1716 1717
__all__.append('Priority')

1718
@session_wrapper
1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732
def get_priority(priority, session=None):
    """
    Returns Priority object for given C{priority name}.

    @type priority: string
    @param priority: The name of the priority

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Priority
    @return: Priority object for the given priority
    """
1733

1734
    q = session.query(Priority).filter_by(priority=priority)
1735

1736 1737 1738 1739
    try:
        return q.one()
    except NoResultFound:
        return None
1740

1741 1742
__all__.append('get_priority')

1743
@session_wrapper
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764
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
    """

    ret = {}
    q = session.query(Priority)
    for x in q.all():
        ret[x.priority] = x.priority_id

    return ret

__all__.append('get_priorities')

M
Mark Hymers 已提交
1765 1766
################################################################################

1767 1768 1769 1770 1771 1772 1773 1774 1775
class Section(ORMObject):
    def __init__(self, section = None):
        self.section = section

    def properties(self):
        return ['section', 'section_id', 'overrides_count']

    def not_null_constraints(self):
        return ['section']
M
Mark Hymers 已提交
1776

1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788
    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

1789 1790
__all__.append('Section')

1791
@session_wrapper
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805
def get_section(section, session=None):
    """
    Returns Section object for given C{section name}.

    @type section: string
    @param section: The name of the section

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Section
    @return: Section object for the given section name
    """
1806

1807
    q = session.query(Section).filter_by(section=section)
1808

1809 1810 1811 1812
    try:
        return q.one()
    except NoResultFound:
        return None
1813

1814 1815
__all__.append('get_section')

1816
@session_wrapper
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837
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
    """

    ret = {}
    q = session.query(Section)
    for x in q.all():
        ret[x.section] = x.section_id

    return ret

__all__.append('get_sections')

M
Mark Hymers 已提交
1838 1839
################################################################################

1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859
class SignatureHistory(ORMObject):
    @classmethod
    def from_signed_file(cls, signed_file):
        """signature history entry from signed file

        @type  signed_file: L{daklib.gpg.SignedFile}
        @param signed_file: signed file

        @rtype: L{SignatureHistory}
        """
        self = cls()
        self.fingerprint = signed_file.primary_fingerprint
        self.signature_timestamp = signed_file.signature_timestamp
        self.contents_sha1 = signed_file.contents_sha1()
        return self

__all__.append('SignatureHistory')

################################################################################

1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871
class SrcContents(ORMObject):
    def __init__(self, file = None, source = None):
        self.file = file
        self.source = source

    def properties(self):
        return ['file', 'source']

__all__.append('SrcContents')

################################################################################

1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925
from debian.debfile import Deb822

# Temporary Deb822 subclass to fix bugs with : handling; see #597249
class Dak822(Deb822):
    def _internal_parser(self, sequence, fields=None):
        # The key is non-whitespace, non-colon characters before any colon.
        key_part = r"^(?P<key>[^: \t\n\r\f\v]+)\s*:\s*"
        single = re.compile(key_part + r"(?P<data>\S.*?)\s*$")
        multi = re.compile(key_part + r"$")
        multidata = re.compile(r"^\s(?P<data>.+?)\s*$")

        wanted_field = lambda f: fields is None or f in fields

        if isinstance(sequence, basestring):
            sequence = sequence.splitlines()

        curkey = None
        content = ""
        for line in self.gpg_stripped_paragraph(sequence):
            m = single.match(line)
            if m:
                if curkey:
                    self[curkey] = content

                if not wanted_field(m.group('key')):
                    curkey = None
                    continue

                curkey = m.group('key')
                content = m.group('data')
                continue

            m = multi.match(line)
            if m:
                if curkey:
                    self[curkey] = content

                if not wanted_field(m.group('key')):
                    curkey = None
                    continue

                curkey = m.group('key')
                content = ""
                continue

            m = multidata.match(line)
            if m:
                content += '\n' + line # XXX not m.group('data')?
                continue

        if curkey:
            self[curkey] = content


1926
class DBSource(ORMObject):
T
Torsten Werner 已提交
1927
    def __init__(self, source = None, version = None, maintainer = None, \
1928
        changedby = None, poolfile = None, install_date = None, fingerprint = None):
T
Torsten Werner 已提交
1929 1930
        self.source = source
        self.version = version
1931 1932
        self.maintainer = maintainer
        self.changedby = changedby
T
Torsten Werner 已提交
1933 1934
        self.poolfile = poolfile
        self.install_date = install_date
1935
        self.fingerprint = fingerprint
M
Mark Hymers 已提交
1936

M
Mark Hymers 已提交
1937 1938 1939 1940
    @property
    def pkid(self):
        return self.source_id

1941 1942 1943
    def properties(self):
        return ['source', 'source_id', 'maintainer', 'changedby', \
            'fingerprint', 'poolfile', 'version', 'suites_count', \
1944
            'install_date', 'binaries_count', 'uploaders_count']
1945 1946

    def not_null_constraints(self):
1947
        return ['source', 'version', 'install_date', 'maintainer', \
1948
            'changedby', 'poolfile']
M
Mark Hymers 已提交
1949

M
Mark Hymers 已提交
1950
    def read_control_fields(self):
M
Mark Hymers 已提交
1951 1952 1953 1954
        '''
        Reads the control information from a dsc

        @rtype: tuple
M
Mark Hymers 已提交
1955
        @return: fields is the dsc information in a dictionary form
M
Mark Hymers 已提交
1956 1957
        '''
        fullpath = self.poolfile.fullpath
1958
        fields = Dak822(open(self.poolfile.fullpath, 'r'))
M
Mark Hymers 已提交
1959 1960
        return fields

1961 1962
    metadata = association_proxy('key', 'value')

1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981
    def scan_contents(self):
        '''
        Returns a set of names for non directories. The path names are
        normalized after converting them from either utf-8 or iso8859-1
        encoding.
        '''
        fullpath = self.poolfile.fullpath
        from daklib.contents import UnpackedSource
        unpacked = UnpackedSource(fullpath)
        fileset = set()
        for name in unpacked.get_all_filenames():
            # enforce proper utf-8 encoding
            try:
                name.decode('utf-8')
            except UnicodeDecodeError:
                name = name.decode('iso8859-1').encode('utf-8')
            fileset.add(name)
        return fileset

1982
__all__.append('DBSource')
1983

1984
@session_wrapper
1985 1986 1987 1988 1989 1990 1991
def source_exists(source, source_version, suites = ["any"], session=None):
    """
    Ensure that source exists somewhere in the archive for the binary
    upload being processed.
      1. exact match     => 1.0-3
      2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1

J
Joerg Jaspert 已提交
1992 1993
    @type source: string
    @param source: source name
1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010

    @type source_version: string
    @param source_version: expected source version

    @type suites: list
    @param suites: list of suites to check in, default I{any}

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: int
    @return: returns 1 if a source with expected version is found, otherwise 0

    """

    cnf = Config()
2011 2012 2013 2014
    ret = True

    from daklib.regexes import re_bin_only_nmu
    orig_source_version = re_bin_only_nmu.sub('', source_version)
2015 2016

    for suite in suites:
2017 2018
        q = session.query(DBSource).filter_by(source=source). \
            filter(DBSource.version.in_([source_version, orig_source_version]))
2019
        if suite != "any":
2020 2021
            # source must exist in 'suite' or a suite that is enhanced by 'suite'
            s = get_suite(suite, session)
2022 2023 2024 2025
            if s:
                enhances_vcs = session.query(VersionCheck).filter(VersionCheck.suite==s).filter_by(check='Enhances')
                considered_suites = [ vc.reference for vc in enhances_vcs ]
                considered_suites.append(s)
2026

2027
                q = q.filter(DBSource.suites.any(Suite.suite_id.in_([s.suite_id for s in considered_suites])))
2028

2029
        if q.count() > 0:
2030 2031 2032
            continue

        # No source found so return not ok
2033
        ret = False
2034 2035

    return ret
2036 2037 2038

__all__.append('source_exists')

2039
@session_wrapper
2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
def get_suites_source_in(source, session=None):
    """
    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
    """

2051
    return session.query(Suite).filter(Suite.sources.any(source=source)).all()
2052 2053 2054

__all__.append('get_suites_source_in')

2055
@session_wrapper
2056
def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
M
Mark Hymers 已提交
2057
    """
2058
    Returns list of DBSource objects for given C{source} name and other parameters
M
Mark Hymers 已提交
2059 2060

    @type source: str
2061
    @param source: DBSource package name to search for
M
Mark Hymers 已提交
2062

J
Joerg Jaspert 已提交
2063 2064
    @type version: str or None
    @param version: DBSource version name to search for or None if not applicable
2065

2066 2067 2068 2069
    @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

M
Mark Hymers 已提交
2070 2071 2072 2073 2074
    @type session: Session
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: list
2075
    @return: list of DBSource objects for the given name (may be empty)
M
Mark Hymers 已提交
2076
    """
2077 2078

    q = session.query(DBSource).filter_by(source=source)
2079 2080 2081 2082

    if version is not None:
        q = q.filter_by(version=version)

2083 2084 2085
    if dm_upload_allowed is not None:
        q = q.filter_by(dm_upload_allowed=dm_upload_allowed)

2086
    return q.all()
M
Mark Hymers 已提交
2087

2088 2089
__all__.append('get_sources_from_name')

T
Torsten Werner 已提交
2090 2091
# FIXME: This function fails badly if it finds more than 1 source package and
# its implementation is trivial enough to be inlined.
2092
@session_wrapper
2093
def get_source_in_suite(source, suite_name, session=None):
2094
    """
2095
    Returns a DBSource object for a combination of C{source} and C{suite_name}.
2096 2097

      - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2098
      - B{suite_name} - a suite name, eg. I{unstable}
2099 2100 2101 2102

    @type source: string
    @param source: source package name

2103
    @type suite_name: string
2104 2105 2106 2107 2108 2109
    @param suite: the suite name

    @rtype: string
    @return: the version for I{source} in I{suite}

    """
2110 2111 2112
    suite = get_suite(suite_name, session)
    if suite is None:
        return None
2113
    try:
2114
        return suite.get_sources(source).one()
2115 2116
    except NoResultFound:
        return None
2117

2118 2119
__all__.append('get_source_in_suite')

M
Mark Hymers 已提交
2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146
@session_wrapper
def import_metadata_into_db(obj, session=None):
    """
    This routine works on either DBBinary or DBSource objects and imports
    their metadata into the database
    """
    fields = obj.read_control_fields()
    for k in fields.keys():
        try:
            # Try raw ASCII
            val = str(fields[k])
        except UnicodeEncodeError:
            # Fall back to UTF-8
            try:
                val = fields[k].encode('utf-8')
            except UnicodeEncodeError:
                # Finally try iso8859-1
                val = fields[k].encode('iso8859-1')
                # Otherwise we allow the exception to percolate up and we cause
                # a reject as someone is playing silly buggers

        obj.metadata[get_or_set_metadatakey(k, session)] = val

    session.commit_or_flush()

__all__.append('import_metadata_into_db')

2147 2148
################################################################################

2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159
class SrcFormat(object):
    def __init__(self, *args, **kwargs):
        pass

    def __repr__(self):
        return '<SrcFormat %s>' % (self.format_name)

__all__.append('SrcFormat')

################################################################################

M
Mark Hymers 已提交
2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173
SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
                 ('SuiteID', 'suite_id'),
                 ('Version', 'version'),
                 ('Origin', 'origin'),
                 ('Label', 'label'),
                 ('Description', 'description'),
                 ('Untouchable', 'untouchable'),
                 ('Announce', 'announce'),
                 ('Codename', 'codename'),
                 ('OverrideCodename', 'overridecodename'),
                 ('ValidTime', 'validtime'),
                 ('Priority', 'priority'),
                 ('NotAutomatic', 'notautomatic'),
                 ('CopyChanges', 'copychanges'),
J
Joerg Jaspert 已提交
2174
                 ('OverrideSuite', 'overridesuite')]
M
Mark Hymers 已提交
2175

T
Torsten Werner 已提交
2176 2177
# Why the heck don't we have any UNIQUE constraints in table suite?
# TODO: Add UNIQUE constraints for appropriate columns.
2178
class Suite(ORMObject):
2179 2180 2181
    def __init__(self, suite_name = None, version = None):
        self.suite_name = suite_name
        self.version = version
M
Mark Hymers 已提交
2182

2183
    def properties(self):
T
Torsten Werner 已提交
2184 2185
        return ['suite_name', 'version', 'sources_count', 'binaries_count', \
            'overrides_count']
2186 2187

    def not_null_constraints(self):
M
Mark Hymers 已提交
2188
        return ['suite_name']
M
Mark Hymers 已提交
2189

2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201
    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

M
Mark Hymers 已提交
2202 2203 2204 2205 2206 2207 2208 2209 2210
    def details(self):
        ret = []
        for disp, field in SUITE_FIELDS:
            val = getattr(self, field, None)
            if val is not None:
                ret.append("%s: %s" % (disp, val))

        return "\n".join(ret)

2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226
    def get_architectures(self, skipsrc=False, skipall=False):
        """
        Returns list of Architecture objects

        @type skipsrc: boolean
        @param skipsrc: Whether to skip returning the 'source' architecture entry
        (Default False)

        @type skipall: boolean
        @param skipall: Whether to skip returning the 'all' architecture entry
        (Default False)

        @rtype: list
        @return: list of Architecture objects for the given name (may be empty)
        """

2227
        q = object_session(self).query(Architecture).with_parent(self)
2228 2229 2230 2231 2232 2233
        if skipsrc:
            q = q.filter(Architecture.arch_string != 'source')
        if skipall:
            q = q.filter(Architecture.arch_string != 'all')
        return q.order_by(Architecture.arch_string).all()

T
Torsten Werner 已提交
2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249
    def get_sources(self, source):
        """
        Returns a query object representing DBSource that is part of C{suite}.

          - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}

        @type source: string
        @param source: source package name

        @rtype: sqlalchemy.orm.query.Query
        @return: a query of DBSource

        """

        session = object_session(self)
        return session.query(DBSource).filter_by(source = source). \
2250
            with_parent(self)
T
Torsten Werner 已提交
2251

2252 2253 2254 2255 2256 2257
    def get_overridesuite(self):
        if self.overridesuite is None:
            return self
        else:
            return object_session(self).query(Suite).filter_by(suite_name=self.overridesuite).one()

2258 2259 2260 2261
    @property
    def path(self):
        return os.path.join(self.archive.path, 'dists', self.suite_name)

2262 2263
__all__.append('Suite')

2264
@session_wrapper
2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276
def get_suite(suite, session=None):
    """
    Returns Suite object for given C{suite name}.

    @type suite: string
    @param suite: The name of the suite

    @type session: Session
    @param session: Optional SQLA session object (a temporary one will be
    generated if not supplied)

    @rtype: Suite
F
Frank Lichtenheld 已提交
2277
    @return: Suite object for the requested suite name (None if not present)
2278
    """
2279

2280
    q = session.query(Suite).filter_by(suite_name=suite)
2281

2282 2283 2284 2285
    try:
        return q.one()
    except NoResultFound:
        return None
2286

2287 2288
__all__.append('get_suite')

M
Mark Hymers 已提交
2289 2290
################################################################################

2291
@session_wrapper
2292
def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
M
Mark Hymers 已提交
2293
    """
2294 2295
    Returns list of Architecture objects for given C{suite} name. The list is
    empty if suite does not exist.
M
Mark Hymers 已提交
2296

J
Joerg Jaspert 已提交
2297 2298
    @type suite: str
    @param suite: Suite name to search for
M
Mark Hymers 已提交
2299

2300 2301 2302 2303 2304 2305 2306 2307
    @type skipsrc: boolean
    @param skipsrc: Whether to skip returning the 'source' architecture entry
    (Default False)

    @type skipall: boolean
    @param skipall: Whether to skip returning the 'all' architecture entry
    (Default False)

M
Mark Hymers 已提交
2308 2309 2310 2311 2312 2313 2314 2315
    @type session: Session
    @param session: Optional SQL session object (a temporary one will be
    generated if not supplied)

    @rtype: list
    @return: list of Architecture objects for the given name (may be empty)
    """

2316 2317 2318
    try:
        return get_suite(suite, session).get_architectures(skipsrc, skipall)
    except AttributeError:
2319
        return []
M
Mark Hymers 已提交
2320

2321
__all__.append('get_suite_architectures')
M
Mark Hymers 已提交
2322

M
Mark Hymers 已提交
2323 2324
################################################################################

2325
class Uid(ORMObject):
T
Torsten Werner 已提交
2326 2327 2328
    def __init__(self, uid = None, name = None):
        self.uid = uid
        self.name = name
M
Mark Hymers 已提交
2329

2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341
    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

2342 2343 2344 2345 2346
    def properties(self):
        return ['uid', 'name', 'fingerprint']

    def not_null_constraints(self):
        return ['uid']
M
Mark Hymers 已提交
2347

2348 2349
__all__.append('Uid')

2350
@session_wrapper
M
Mark Hymers 已提交
2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367
def get_or_set_uid(uidname, session=None):
    """
    Returns uid object for given uidname.

    If no matching uidname is found, a row is inserted.

    @type uidname: string
    @param uidname: The uid 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.

    @rtype: Uid
    @return: the uid object for the given uidname
    """
2368 2369 2370

    q = session.query(Uid).filter_by(uid=uidname)

2371 2372 2373
    try:
        ret = q.one()
    except NoResultFound:
2374 2375 2376
        uid = Uid()
        uid.uid = uidname
        session.add(uid)
2377
        session.commit_or_flush()
2378
        ret = uid
M
Mark Hymers 已提交
2379

2380
    return ret
M
Mark Hymers 已提交
2381 2382 2383

__all__.append('get_or_set_uid')

2384
@session_wrapper
2385 2386 2387 2388
def get_uid_from_fingerprint(fpr, session=None):
    q = session.query(Uid)
    q = q.join(Fingerprint).filter_by(fingerprint=fpr)

2389 2390 2391 2392
    try:
        return q.one()
    except NoResultFound:
        return None
2393 2394 2395

__all__.append('get_uid_from_fingerprint')

M
Mark Hymers 已提交
2396 2397
################################################################################

T
Torsten Werner 已提交
2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409
class MetadataKey(ORMObject):
    def __init__(self, key = None):
        self.key = key

    def properties(self):
        return ['key']

    def not_null_constraints(self):
        return ['key']

__all__.append('MetadataKey')

M
Mark Hymers 已提交
2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441
@session_wrapper
def get_or_set_metadatakey(keyname, session=None):
    """
    Returns MetadataKey object for given uidname.

    If no matching keyname is found, a row is inserted.

    @type uidname: string
    @param uidname: The keyname 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.

    @rtype: MetadataKey
    @return: the metadatakey object for the given keyname
    """

    q = session.query(MetadataKey).filter_by(key=keyname)

    try:
        ret = q.one()
    except NoResultFound:
        ret = MetadataKey(keyname)
        session.add(ret)
        session.commit_or_flush()

    return ret

__all__.append('get_or_set_metadatakey')

T
Torsten Werner 已提交
2442 2443 2444
################################################################################

class BinaryMetadata(ORMObject):
2445
    def __init__(self, key = None, value = None, binary = None):
T
Torsten Werner 已提交
2446 2447
        self.key = key
        self.value = value
2448
        self.binary = binary
T
Torsten Werner 已提交
2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460

    def properties(self):
        return ['binary', 'key', 'value']

    def not_null_constraints(self):
        return ['value']

__all__.append('BinaryMetadata')

################################################################################

class SourceMetadata(ORMObject):
2461
    def __init__(self, key = None, value = None, source = None):
T
Torsten Werner 已提交
2462 2463
        self.key = key
        self.value = value
2464
        self.source = source
T
Torsten Werner 已提交
2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475

    def properties(self):
        return ['source', 'key', 'value']

    def not_null_constraints(self):
        return ['value']

__all__.append('SourceMetadata')

################################################################################

2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492
class VersionCheck(ORMObject):
    def __init__(self, *args, **kwargs):
	pass

    def properties(self):
        #return ['suite_id', 'check', 'reference_id']
        return ['check']

    def not_null_constraints(self):
        return ['suite', 'check', 'reference']

__all__.append('VersionCheck')

@session_wrapper
def get_version_checks(suite_name, check = None, session = None):
    suite = get_suite(suite_name, session)
    if not suite:
M
Mark Hymers 已提交
2493 2494 2495
        # Make sure that what we return is iterable so that list comprehensions
        # involving this don't cause a traceback
        return []
2496 2497 2498 2499 2500 2501 2502 2503 2504
    q = session.query(VersionCheck).filter_by(suite=suite)
    if check:
        q = q.filter_by(check=check)
    return q.all()

__all__.append('get_version_checks')

################################################################################

2505
class DBConn(object):
M
Mark Hymers 已提交
2506
    """
2507
    database module init.
M
Mark Hymers 已提交
2508
    """
2509 2510
    __shared_state = {}

M
Mark Hymers 已提交
2511
    def __init__(self, *args, **kwargs):
2512
        self.__dict__ = self.__shared_state
M
Mark Hymers 已提交
2513

2514 2515 2516 2517
        if not getattr(self, 'initialised', False):
            self.initialised = True
            self.debug = kwargs.has_key('debug')
            self.__createconn()
M
Mark Hymers 已提交
2518

M
Mark Hymers 已提交
2519
    def __setuptables(self):
T
Torsten Werner 已提交
2520
        tables = (
2521 2522 2523 2524
            'acl',
            'acl_architecture_map',
            'acl_fingerprint_map',
            'acl_per_source',
C
Chris Lamb 已提交
2525 2526 2527
            'architecture',
            'archive',
            'bin_associations',
T
Torsten Werner 已提交
2528
            'bin_contents',
C
Chris Lamb 已提交
2529
            'binaries',
T
Torsten Werner 已提交
2530
            'binaries_metadata',
C
Chris Lamb 已提交
2531
            'build_queue',
2532
            'changelogs_text',
2533
            'changes',
C
Chris Lamb 已提交
2534
            'component',
2535
            'component_suite',
C
Chris Lamb 已提交
2536 2537
            'config',
            'dsc_files',
A
Ansgar Burchardt 已提交
2538
            'external_overrides',
T
Torsten Werner 已提交
2539
            'extra_src_references',
C
Chris Lamb 已提交
2540
            'files',
2541
            'files_archive_map',
C
Chris Lamb 已提交
2542 2543 2544
            'fingerprint',
            'keyrings',
            'maintainer',
T
Torsten Werner 已提交
2545
            'metadata_keys',
C
Chris Lamb 已提交
2546
            'new_comments',
T
Torsten Werner 已提交
2547 2548
            # TODO: the maintainer column in table override should be removed.
            'override',
C
Chris Lamb 已提交
2549 2550
            'override_type',
            'policy_queue',
2551 2552 2553
            'policy_queue_upload',
            'policy_queue_upload_binaries_map',
            'policy_queue_byhand_file',
C
Chris Lamb 已提交
2554 2555
            'priority',
            'section',
2556
            'signature_history',
C
Chris Lamb 已提交
2557
            'source',
T
Torsten Werner 已提交
2558
            'source_metadata',
C
Chris Lamb 已提交
2559
            'src_associations',
2560
            'src_contents',
C
Chris Lamb 已提交
2561 2562 2563
            'src_format',
            'src_uploaders',
            'suite',
2564
            'suite_acl_map',
C
Chris Lamb 已提交
2565 2566
            'suite_architectures',
            'suite_build_queue_copy',
T
Torsten Werner 已提交
2567 2568
            'suite_src_formats',
            'uid',
2569
            'version_check',
C
Chris Lamb 已提交
2570 2571
        )

2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593
        views = (
            'almost_obsolete_all_associations',
            'almost_obsolete_src_associations',
            'any_associations_source',
            'bin_associations_binaries',
            'binaries_suite_arch',
            'changelogs',
            'file_arch_suite',
            'newest_all_associations',
            'newest_any_associations',
            'newest_source',
            'newest_src_association',
            'obsolete_all_associations',
            'obsolete_any_associations',
            'obsolete_any_by_all_associations',
            'obsolete_src_associations',
            'source_suite',
            'src_associations_bin',
            'src_associations_src',
            'suite_arch_by_name',
        )

T
Torsten Werner 已提交
2594
        for table_name in tables:
2595 2596 2597 2598
            table = Table(table_name, self.db_meta, \
                autoload=True, useexisting=True)
            setattr(self, 'tbl_%s' % table_name, table)

2599 2600 2601 2602
        for view_name in views:
            view = Table(view_name, self.db_meta, autoload=True)
            setattr(self, 'view_%s' % view_name, view)

M
Mark Hymers 已提交
2603
    def __setupmappers(self):
M
Mark Hymers 已提交
2604
        mapper(Architecture, self.tbl_architecture,
2605
            properties = dict(arch_id = self.tbl_architecture.c.id,
2606
               suites = relation(Suite, secondary=self.tbl_suite_architectures,
2607 2608
                   order_by=self.tbl_suite.c.suite_name,
                   backref=backref('architectures', order_by=self.tbl_architecture.c.arch_string))),
2609
            extension = validator)
M
Mark Hymers 已提交
2610

2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621
        mapper(ACL, self.tbl_acl,
               properties = dict(
                architectures = relation(Architecture, secondary=self.tbl_acl_architecture_map, collection_class=set),
                fingerprints = relation(Fingerprint, secondary=self.tbl_acl_fingerprint_map, collection_class=set),
                match_keyring = relation(Keyring, primaryjoin=(self.tbl_acl.c.match_keyring_id == self.tbl_keyrings.c.id)),
                per_source = relation(ACLPerSource, collection_class=set),
                ))

        mapper(ACLPerSource, self.tbl_acl_per_source,
               properties = dict(
                acl = relation(ACL),
2622 2623
                fingerprint = relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.fingerprint_id == self.tbl_fingerprint.c.id)),
                created_by = relation(Fingerprint, primaryjoin=(self.tbl_acl_per_source.c.created_by_id == self.tbl_fingerprint.c.id)),
2624 2625
                ))

M
Mark Hymers 已提交
2626 2627 2628
        mapper(Archive, self.tbl_archive,
               properties = dict(archive_id = self.tbl_archive.c.id,
                                 archive_name = self.tbl_archive.c.name))
M
Mike O'Connor 已提交
2629

2630 2631 2632 2633 2634
        mapper(ArchiveFile, self.tbl_files_archive_map,
               properties = dict(archive = relation(Archive, backref='files'),
                                 component = relation(Component),
                                 file = relation(PoolFile, backref='archives')))

2635
        mapper(BuildQueue, self.tbl_build_queue,
2636 2637
               properties = dict(queue_id = self.tbl_build_queue.c.id,
                                 suite = relation(Suite, primaryjoin=(self.tbl_build_queue.c.suite_id==self.tbl_suite.c.id))))
2638

2639
        mapper(DBBinary, self.tbl_binaries,
M
Mark Hymers 已提交
2640
               properties = dict(binary_id = self.tbl_binaries.c.id,
M
Mark Hymers 已提交
2641 2642
                                 package = self.tbl_binaries.c.package,
                                 version = self.tbl_binaries.c.version,
M
Mark Hymers 已提交
2643
                                 maintainer_id = self.tbl_binaries.c.maintainer,
M
Mark Hymers 已提交
2644
                                 maintainer = relation(Maintainer),
M
Mark Hymers 已提交
2645
                                 source_id = self.tbl_binaries.c.source,
2646
                                 source = relation(DBSource, backref='binaries'),
M
Mark Hymers 已提交
2647
                                 arch_id = self.tbl_binaries.c.architecture,
M
Mark Hymers 已提交
2648 2649
                                 architecture = relation(Architecture),
                                 poolfile_id = self.tbl_binaries.c.file,
2650
                                 poolfile = relation(PoolFile),
M
Mark Hymers 已提交
2651 2652 2653 2654
                                 binarytype = self.tbl_binaries.c.type,
                                 fingerprint_id = self.tbl_binaries.c.sig_fpr,
                                 fingerprint = relation(Fingerprint),
                                 install_date = self.tbl_binaries.c.install_date,
2655
                                 suites = relation(Suite, secondary=self.tbl_bin_associations,
M
Mark Hymers 已提交
2656 2657
                                     backref=backref('binaries', lazy='dynamic')),
                                 extra_sources = relation(DBSource, secondary=self.tbl_extra_src_references,
2658
                                     backref=backref('extra_binary_references', lazy='dynamic')),
2659
                                 key = relation(BinaryMetadata, cascade='all',
2660
                                     collection_class=attribute_mapped_collection('key'))),
2661
                extension = validator)
M
Mark Hymers 已提交
2662 2663 2664

        mapper(Component, self.tbl_component,
               properties = dict(component_id = self.tbl_component.c.id,
2665
                                 component_name = self.tbl_component.c.name),
2666
               extension = validator)
M
Mark Hymers 已提交
2667 2668 2669 2670 2671 2672 2673

        mapper(DBConfig, self.tbl_config,
               properties = dict(config_id = self.tbl_config.c.id))

        mapper(DSCFile, self.tbl_dsc_files,
               properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
                                 source_id = self.tbl_dsc_files.c.source,
2674
                                 source = relation(DBSource),
M
Mark Hymers 已提交
2675 2676
                                 poolfile_id = self.tbl_dsc_files.c.file,
                                 poolfile = relation(PoolFile)))
M
Mark Hymers 已提交
2677

2678 2679 2680 2681 2682 2683
        mapper(ExternalOverride, self.tbl_external_overrides,
                properties = dict(
                    suite_id = self.tbl_external_overrides.c.suite,
                    suite = relation(Suite),
                    component_id = self.tbl_external_overrides.c.component,
                    component = relation(Component)))
A
Ansgar Burchardt 已提交
2684

M
Mark Hymers 已提交
2685 2686
        mapper(PoolFile, self.tbl_files,
               properties = dict(file_id = self.tbl_files.c.id,
2687
                                 filesize = self.tbl_files.c.size),
2688
                extension = validator)
M
Mark Hymers 已提交
2689 2690 2691 2692

        mapper(Fingerprint, self.tbl_fingerprint,
               properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
                                 uid_id = self.tbl_fingerprint.c.uid,
M
Mark Hymers 已提交
2693 2694
                                 uid = relation(Uid),
                                 keyring_id = self.tbl_fingerprint.c.keyring,
2695
                                 keyring = relation(Keyring),
2696
                                 acl = relation(ACL)),
2697
               extension = validator)
M
Mark Hymers 已提交
2698 2699 2700

        mapper(Keyring, self.tbl_keyrings,
               properties = dict(keyring_name = self.tbl_keyrings.c.name,
2701 2702
                                 keyring_id = self.tbl_keyrings.c.id,
                                 acl = relation(ACL, primaryjoin=(self.tbl_keyrings.c.acl_id == self.tbl_acl.c.id)))),
M
Mark Hymers 已提交
2703

M
Mark Hymers 已提交
2704 2705
        mapper(DBChange, self.tbl_changes,
               properties = dict(change_id = self.tbl_changes.c.id,
2706
                                 seen = self.tbl_changes.c.seen,
2707 2708 2709 2710 2711 2712 2713 2714
                                 source = self.tbl_changes.c.source,
                                 binaries = self.tbl_changes.c.binaries,
                                 architecture = self.tbl_changes.c.architecture,
                                 distribution = self.tbl_changes.c.distribution,
                                 urgency = self.tbl_changes.c.urgency,
                                 maintainer = self.tbl_changes.c.maintainer,
                                 changedby = self.tbl_changes.c.changedby,
                                 date = self.tbl_changes.c.date,
A
Ansgar Burchardt 已提交
2715
                                 version = self.tbl_changes.c.version))
J
Joerg Jaspert 已提交
2716

M
Mark Hymers 已提交
2717
        mapper(Maintainer, self.tbl_maintainer,
2718 2719 2720 2721
               properties = dict(maintainer_id = self.tbl_maintainer.c.id,
                   maintains_sources = relation(DBSource, backref='maintainer',
                       primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.maintainer)),
                   changed_sources = relation(DBSource, backref='changedby',
2722 2723
                       primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.changedby))),
                extension = validator)
M
Mark Hymers 已提交
2724

M
Mark Hymers 已提交
2725
        mapper(NewComment, self.tbl_new_comments,
2726 2727
               properties = dict(comment_id = self.tbl_new_comments.c.id,
                                 policy_queue = relation(PolicyQueue)))
M
Mark Hymers 已提交
2728

M
Mark Hymers 已提交
2729 2730
        mapper(Override, self.tbl_override,
               properties = dict(suite_id = self.tbl_override.c.suite,
T
Torsten Werner 已提交
2731 2732
                                 suite = relation(Suite, \
                                    backref=backref('overrides', lazy='dynamic')),
2733
                                 package = self.tbl_override.c.package,
M
Mark Hymers 已提交
2734
                                 component_id = self.tbl_override.c.component,
2735 2736
                                 component = relation(Component, \
                                    backref=backref('overrides', lazy='dynamic')),
M
Mark Hymers 已提交
2737
                                 priority_id = self.tbl_override.c.priority,
2738 2739
                                 priority = relation(Priority, \
                                    backref=backref('overrides', lazy='dynamic')),
M
Mark Hymers 已提交
2740
                                 section_id = self.tbl_override.c.section,
2741 2742
                                 section = relation(Section, \
                                    backref=backref('overrides', lazy='dynamic')),
M
Mark Hymers 已提交
2743
                                 overridetype_id = self.tbl_override.c.type,
2744 2745
                                 overridetype = relation(OverrideType, \
                                    backref=backref('overrides', lazy='dynamic'))))
M
Mark Hymers 已提交
2746 2747 2748 2749 2750

        mapper(OverrideType, self.tbl_override_type,
               properties = dict(overridetype = self.tbl_override_type.c.type,
                                 overridetype_id = self.tbl_override_type.c.id))

2751
        mapper(PolicyQueue, self.tbl_policy_queue,
A
Ansgar Burchardt 已提交
2752 2753
               properties = dict(policy_queue_id = self.tbl_policy_queue.c.id,
                                 suite = relation(Suite, primaryjoin=(self.tbl_policy_queue.c.suite_id == self.tbl_suite.c.id))))
2754

2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769
        mapper(PolicyQueueUpload, self.tbl_policy_queue_upload,
               properties = dict(
                   changes = relation(DBChange),
                   policy_queue = relation(PolicyQueue, backref='uploads'),
                   target_suite = relation(Suite),
                   source = relation(DBSource),
                   binaries = relation(DBBinary, secondary=self.tbl_policy_queue_upload_binaries_map),
                ))

        mapper(PolicyQueueByhandFile, self.tbl_policy_queue_byhand_file,
               properties = dict(
                   upload = relation(PolicyQueueUpload, backref='byhand'),
                   )
               )

M
Mark Hymers 已提交
2770 2771 2772 2773
        mapper(Priority, self.tbl_priority,
               properties = dict(priority_id = self.tbl_priority.c.id))

        mapper(Section, self.tbl_section,
M
Mike O'Connor 已提交
2774 2775
               properties = dict(section_id = self.tbl_section.c.id,
                                 section=self.tbl_section.c.section))
M
Mark Hymers 已提交
2776

2777 2778
        mapper(SignatureHistory, self.tbl_signature_history)

2779
        mapper(DBSource, self.tbl_source,
M
Mark Hymers 已提交
2780
               properties = dict(source_id = self.tbl_source.c.id,
M
Mark Hymers 已提交
2781
                                 version = self.tbl_source.c.version,
M
Mark Hymers 已提交
2782
                                 maintainer_id = self.tbl_source.c.maintainer,
M
Mark Hymers 已提交
2783
                                 poolfile_id = self.tbl_source.c.file,
2784
                                 poolfile = relation(PoolFile),
M
Mark Hymers 已提交
2785
                                 fingerprint_id = self.tbl_source.c.sig_fpr,
M
Mark Hymers 已提交
2786 2787 2788 2789
                                 fingerprint = relation(Fingerprint),
                                 changedby_id = self.tbl_source.c.changedby,
                                 srcfiles = relation(DSCFile,
                                                     primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2790
                                 suites = relation(Suite, secondary=self.tbl_src_associations,
2791
                                     backref=backref('sources', lazy='dynamic')),
2792 2793
                                 uploaders = relation(Maintainer,
                                     secondary=self.tbl_src_uploaders),
2794
                                 key = relation(SourceMetadata, cascade='all',
2795
                                     collection_class=attribute_mapped_collection('key'))),
2796
               extension = validator)
M
Mark Hymers 已提交
2797

2798 2799 2800 2801
        mapper(SrcFormat, self.tbl_src_format,
               properties = dict(src_format_id = self.tbl_src_format.c.id,
                                 format_name = self.tbl_src_format.c.format_name))

M
Mark Hymers 已提交
2802
        mapper(Suite, self.tbl_suite,
2803
               properties = dict(suite_id = self.tbl_suite.c.id,
A
Ansgar Burchardt 已提交
2804
                                 policy_queue = relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.policy_queue_id == self.tbl_policy_queue.c.id)),
2805
                                 new_queue = relation(PolicyQueue, primaryjoin=(self.tbl_suite.c.new_queue_id == self.tbl_policy_queue.c.id)),
2806
                                 copy_queues = relation(BuildQueue,
M
Mark Hymers 已提交
2807 2808
                                     secondary=self.tbl_suite_build_queue_copy),
                                 srcformats = relation(SrcFormat, secondary=self.tbl_suite_src_formats,
2809
                                     backref=backref('suites', lazy='dynamic')),
2810
                                 archive = relation(Archive, backref='suites'),
2811 2812 2813
                                 acls = relation(ACL, secondary=self.tbl_suite_acl_map, collection_class=set),
                                 components = relation(Component, secondary=self.tbl_component_suite,
                                                   order_by=self.tbl_component.c.ordering,
2814
                                                   backref=backref('suites'))),
2815
                extension = validator)
M
Mark Hymers 已提交
2816 2817

        mapper(Uid, self.tbl_uid,
2818
               properties = dict(uid_id = self.tbl_uid.c.id,
2819 2820
                                 fingerprint = relation(Fingerprint)),
               extension = validator)
M
Mark Hymers 已提交
2821

2822 2823 2824
        mapper(BinContents, self.tbl_bin_contents,
            properties = dict(
                binary = relation(DBBinary,
2825
                    backref=backref('contents', lazy='dynamic', cascade='all')),
2826 2827
                file = self.tbl_bin_contents.c.file))

2828 2829 2830 2831 2832 2833
        mapper(SrcContents, self.tbl_src_contents,
            properties = dict(
                source = relation(DBSource,
                    backref=backref('contents', lazy='dynamic', cascade='all')),
                file = self.tbl_src_contents.c.file))

T
Torsten Werner 已提交
2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854
        mapper(MetadataKey, self.tbl_metadata_keys,
            properties = dict(
                key_id = self.tbl_metadata_keys.c.key_id,
                key = self.tbl_metadata_keys.c.key))

        mapper(BinaryMetadata, self.tbl_binaries_metadata,
            properties = dict(
                binary_id = self.tbl_binaries_metadata.c.bin_id,
                binary = relation(DBBinary),
                key_id = self.tbl_binaries_metadata.c.key_id,
                key = relation(MetadataKey),
                value = self.tbl_binaries_metadata.c.value))

        mapper(SourceMetadata, self.tbl_source_metadata,
            properties = dict(
                source_id = self.tbl_source_metadata.c.src_id,
                source = relation(DBSource),
                key_id = self.tbl_source_metadata.c.key_id,
                key = relation(MetadataKey),
                value = self.tbl_source_metadata.c.value))

2855 2856 2857 2858 2859 2860 2861
	mapper(VersionCheck, self.tbl_version_check,
	    properties = dict(
		suite_id = self.tbl_version_check.c.suite,
		suite = relation(Suite, primaryjoin=self.tbl_version_check.c.suite==self.tbl_suite.c.id),
		reference_id = self.tbl_version_check.c.reference,
		reference = relation(Suite, primaryjoin=self.tbl_version_check.c.reference==self.tbl_suite.c.id, lazy='joined')))

M
Mark Hymers 已提交
2862 2863
    ## Connection functions
    def __createconn(self):
M
Mark Hymers 已提交
2864
        from config import Config
2865
        cnf = Config()
M
Mark Hymers 已提交
2866
        if cnf.has_key("DB::Service"):
2867
            connstr = "postgresql://service=%s" % cnf["DB::Service"]
M
Mark Hymers 已提交
2868
        elif cnf.has_key("DB::Host"):
M
Mark Hymers 已提交
2869
            # TCP/IP
2870
            connstr = "postgresql://%s" % cnf["DB::Host"]
M
Mark Hymers 已提交
2871
            if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2872 2873 2874 2875
                connstr += ":%s" % cnf["DB::Port"]
            connstr += "/%s" % cnf["DB::Name"]
        else:
            # Unix Socket
2876
            connstr = "postgresql:///%s" % cnf["DB::Name"]
M
Mark Hymers 已提交
2877
            if cnf.has_key("DB::Port") and cnf["DB::Port"] != "-1":
M
Mark Hymers 已提交
2878
                connstr += "?port=%s" % cnf["DB::Port"]
2879 2880 2881 2882 2883 2884

        engine_args = { 'echo': self.debug }
        if cnf.has_key('DB::PoolSize'):
            engine_args['pool_size'] = int(cnf['DB::PoolSize'])
        if cnf.has_key('DB::MaxOverflow'):
            engine_args['max_overflow'] = int(cnf['DB::MaxOverflow'])
2885
        if sa_major_version != '0.5' and cnf.has_key('DB::Unicode') and \
2886 2887 2888
            cnf['DB::Unicode'] == 'false':
            engine_args['use_native_unicode'] = False

2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902
        # Monkey patch a new dialect in in order to support service= syntax
        import sqlalchemy.dialects.postgresql
        from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
        class PGDialect_psycopg2_dak(PGDialect_psycopg2):
            def create_connect_args(self, url):
                if str(url).startswith('postgresql://service='):
                    # Eww
                    servicename = str(url)[21:]
                    return (['service=%s' % servicename], {})
                else:
                    return PGDialect_psycopg2.create_connect_args(self, url)

        sqlalchemy.dialects.postgresql.base.dialect = PGDialect_psycopg2_dak

2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913
        try:
            self.db_pg   = create_engine(connstr, **engine_args)
            self.db_meta = MetaData()
            self.db_meta.bind = self.db_pg
            self.db_smaker = sessionmaker(bind=self.db_pg,
                                          autoflush=True,
                                          autocommit=False)

            self.__setuptables()
            self.__setupmappers()

2914
        except OperationalError as e:
2915 2916
            import utils
            utils.fubar("Cannot connect to database (%s)" % str(e))
M
Mark Hymers 已提交
2917

2918
        self.pid = os.getpid()
M
Mark Hymers 已提交
2919

2920 2921 2922 2923 2924 2925 2926
    def session(self, work_mem = 0):
        '''
        Returns a new session object. If a work_mem parameter is provided a new
        transaction is started and the work_mem parameter is set for this
        transaction. The work_mem parameter is measured in MB. A default value
        will be used if the parameter is not set.
        '''
2927 2928 2929 2930
        # reinitialize DBConn in new processes
        if self.pid != os.getpid():
            clear_mappers()
            self.__createconn()
2931 2932 2933 2934
        session = self.db_smaker()
        if work_mem > 0:
            session.execute("SET LOCAL work_mem TO '%d MB'" % work_mem)
        return session
M
Mark Hymers 已提交
2935

M
Mark Hymers 已提交
2936
__all__.append('DBConn')