# This file is part of Buildbot.  Buildbot 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, version 2.
#
# 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., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

import sqlalchemy as sa
import migrate
from buildbot.util import sautils

def migrate_claims(migrate_engine, metadata, buildrequests, objects,
                   buildrequest_claims):

    # First, ensure there is an object row for each master
    null_id = sa.null().label('id')
    if migrate_engine.dialect.name == 'postgresql':
        # postgres needs NULL cast to an integer:
        null_id = sa.cast(null_id, sa.INTEGER)
    new_objects = sa.select([
            null_id,
            buildrequests.c.claimed_by_name.label("name"),
            sa.literal_column("'BuildMaster'").label("class_name"),
        ],
        whereclause=buildrequests.c.claimed_by_name != None,
        distinct=True)

    # this doesn't seem to work without str() -- verified in sqla 0.6.0 - 0.7.1
    migrate_engine.execute(
            str(sautils.InsertFromSelect(objects, new_objects)))

    # now make a buildrequest_claims row for each claimed build request
    join = buildrequests.join(objects,
            (buildrequests.c.claimed_by_name == objects.c.name)
            # (have to use sa.text because str, below, doesn't work
            # with placeholders)
            & (objects.c.class_name == sa.text("'BuildMaster'")))
    claims = sa.select([
            buildrequests.c.id.label('brid'),
            objects.c.id.label('objectid'),
            buildrequests.c.claimed_at,
        ], from_obj=[ join ],
        whereclause=buildrequests.c.claimed_by_name != None)
    migrate_engine.execute(
            str(sautils.InsertFromSelect(buildrequest_claims, claims)))

def drop_columns(metadata, buildrequests):
    # sqlalchemy-migrate <0.7.0 has a bug with sqlalchemy >=0.7.0, where
    # it tries to change an immutable column; this is the workaround, from
    # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=112
    if not sa.__version__.startswith('0.6.'):
        if not hasattr(migrate, '__version__'): # that is, older than 0.7
            buildrequests.columns = buildrequests._columns

    buildrequests.c.claimed_at.drop()
    buildrequests.c.claimed_by_name.drop()
    buildrequests.c.claimed_by_incarnation.drop()

def upgrade(migrate_engine):
    metadata = sa.MetaData()
    metadata.bind = migrate_engine

    # a copy of the buildrequests table, but with the foreign keys stripped
    buildrequests = sa.Table('buildrequests', metadata,
        sa.Column('id', sa.Integer,  primary_key=True),
        sa.Column('buildsetid', sa.Integer, nullable=False),
        sa.Column('buildername', sa.String(length=256), nullable=False),
        sa.Column('priority', sa.Integer, nullable=False,
            server_default=sa.DefaultClause("0")),
        sa.Column('claimed_at', sa.Integer,
            server_default=sa.DefaultClause("0")),
        sa.Column('claimed_by_name', sa.String(length=256)),
        sa.Column('claimed_by_incarnation', sa.String(length=256)),
        sa.Column('complete', sa.Integer,
            server_default=sa.DefaultClause("0")),
        sa.Column('results', sa.SmallInteger),
        sa.Column('submitted_at', sa.Integer, nullable=False),
        sa.Column('complete_at', sa.Integer),
    )

    # existing objects table, used as a foreign key
    objects = sa.Table("objects", metadata,
        # unique ID for this object
        sa.Column("id", sa.Integer, primary_key=True),
        # object's user-given name
        sa.Column('name', sa.String(128), nullable=False),
        # object's class name, basically representing a "type" for the state
        sa.Column('class_name', sa.String(128), nullable=False),

        # prohibit multiple id's for the same object
        sa.UniqueConstraint('name', 'class_name', name='object_identity'),
    )

    # and a new buildrequest_claims table
    buildrequest_claims = sa.Table('buildrequest_claims', metadata,
        sa.Column('brid', sa.Integer, sa.ForeignKey('buildrequests.id'),
            index=True, unique=True),
        sa.Column('objectid', sa.Integer, sa.ForeignKey('objects.id'),
            index=True, nullable=True),
        sa.Column('claimed_at', sa.Integer, nullable=False),
    )

    # create the new table
    buildrequest_claims.create()

    # migrate the claims into that table
    migrate_claims(migrate_engine, metadata, buildrequests,
                   objects, buildrequest_claims)

    # and drop the claim-related columns in buildrequests
    drop_columns(metadata, buildrequests)
