#!/usr/bin/env python3
'''CMS Conditions DB command-line tool.
'''
from __future__ import print_function

import argparse
import datetime
import getpass
import logging
import os
import re
import sys
import stat
import subprocess
import tempfile
import textwrap
import time
import pwd
import socket

import calendar
import sqlalchemy
import json

from prettytable import PrettyTable

import CondCore.Utilities.conddblib as conddb
import CondCore.Utilities.cond2xml as cond2xml
import CondCore.Utilities.conddb_serialization_metadata as serialization_metadata
import CondCore.Utilities.conddb_time as conddb_time

from CondCore.Utilities.tier0 import Tier0Handler, Tier0Error, tier0Url
# -------------------------------------------------------------------------------------------------------

# TODO: Diffs may look better in the -/+ mode, instead the 2 columns mode.
# TODO: Colored diff! (green +, red -)
# TODO: Support the old connection string syntax, e.g. sqlite_file://...

maxSince = 18446744073709551615
sizeOfTimestamp = 19

tag_db_lock_access_code   = 8
tag_db_write_access_code  = 2
tag_db_read_access_code   = 1
tag_db_no_protection_code = 0

db_key_credential_type_code     = 1
db_cmsuser_credential_type_code = 2
db_session_credential_type_code = 4

db_access_code_map = { tag_db_write_access_code: "W", tag_db_lock_access_code: "L" }

db_credential_type_map = { db_key_credential_type_code : ("DB KEY",'K'), 
                           db_cmsuser_credential_type_code : ("CMS USER",'U'),
                           db_session_credential_type_code : ("DB SESSION",'S'), }

# Utility functions

def _rawdict(obj):
    return dict([(str(column), getattr(obj, column)) for column in obj.__table__.columns.keys()])

def _rawdict_selection(obj):
    return dict([(str(column), getattr(obj, column)) for column in obj._fields])

def _get_payload_full_hash(session, payload, check=True):
    # Limited to 2 to know whether there is more than one in a single query
    Payload = session.get_dbtype(conddb.Payload)
    payloads = session.query(Payload.hash).\
            filter(Payload.hash.like('%s%%' % payload.lower())).\
            limit(2).\
            all()

    if check:
        if len(payloads) == 0:
            raise Exception('There is no payload matching %s in the database.' % payload)
        if len(payloads) > 1:
            raise Exception('There is more than one payload matching %s in the database. Please provide a longer prefix.' % payload)

    return payloads[0].hash if len(payloads) == 1 else None

def _dump_payload(session, payload, loadonly):

    Payload = session.get_dbtype(conddb.Payload)
    data = session.query(Payload.data).\
        filter(Payload.hash == payload).\
        one()[0]
    logging.info('Loading %spayload %s of length %s ...', '' if loadonly else 'and dumping ', payload, len(data))
    print('Data (TODO: Replace with the call to the actual compiled C++ tool):', repr(data))


def _identify_object(session, objtype, name):
    # We can't just use get() here since frontier fetches the entire
    # BLOBs by default when requesting them in a column

    Tag = session.get_dbtype(conddb.Tag)
    GlobalTag = session.get_dbtype(conddb.GlobalTag)
    if objtype is not None:
        # Check the type is correct (i.e. if the object exists)
        if objtype == 'tag':
            if not _exists(session, Tag.name, name):
                raise Exception('There is no tag named %s in the database.' % name)
        elif objtype == 'gt':
            if not _exists(session, GlobalTag.name, name):
                # raise Exception('There is no global tag named %s in the database.' % name)
                logging.info('There is no global tag table in the database.')
        elif objtype == 'payload':
            # In the case of a payload, check and also return the full hash
            return objtype, _get_payload_full_hash(session, name)

        return objtype, name

    # Search for the object
    tag = _exists(session, Tag.name, name)
    global_tag = _exists(session, GlobalTag.name, name)
    payload_hash = _get_payload_full_hash(session, name, check = False)

    count = len([x for x in  filter(None, [tag, global_tag, payload_hash])])
    if count > 1:
        raise Exception('There is more than one object named %s in the database.' % name)
    if count == 0:
        raise Exception('There is no tag, global tag or (unique) payload named %s in the database.' % name)

    if tag:
        return 'tag', name
    elif global_tag:
        return 'gt', name
    elif payload_hash is not None:
        return 'payload', payload_hash

    raise Exception('Should not have arrived here.')


def _get_editor(args):
    if args.editor is not None:
        return args.editor

    editor = os.environ.get('EDITOR')
    if editor is None:
        raise Exception('An editor was not provided and the EDITOR environment variable does not exist either.')

    return editor


def _run_editor(editor, tempfd):
    tempfd.flush()
    subprocess.check_call('%s %s' % (editor, tempfd.name), shell=True)
    tempfd.seek(0)


def _parse_timestamp(timestamp):
    try:
        return datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f')
    except ValueError:
        pass

    try:
        return datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S')
    except ValueError:
        pass

    try:
        return datetime.datetime.strptime(timestamp, '%Y-%m-%d')
    except ValueError:
        pass

    raise Exception("Could not parse timestamp '%s'" % timestamp)


def _confirm_changes(args):
    if not args.yes:
        output(args, 'Confirm changes? [n]', newline=False)
        if input().lower() not in ['y', 'yes']:
            raise Exception('Aborted by the user.')

def _get_user_note(args,message):
    output(args, message, newline=False)
    note = input()
    if note == '' or note == ' ':
        output(args,'Provided note is invalid, please provide a non-empty string: ',newline=False)
        note = input()
        if note == '' or note == ' ':
            raise Exception('Sorry, bailing out...')
    return note 

def _exists(session, primary_key, value):
    ret = None
    try: 
        ret = session.query(primary_key).\
            filter(primary_key == value).\
            count() != 0
    except sqlalchemy.exc.OperationalError:
        pass

    return ret

def _regexp(connection, field, regexp):
    '''To be used inside filter().
    '''

    if connection.is_oracle or connection.is_frontier:
        return sqlalchemy.func.regexp_like(field, regexp)
    elif connection.is_sqlite:
        # Relies on being a SingletonThreadPool
        connection.engine.pool.connect().create_function('regexp', 2, lambda data, regexp: re.search(regexp, data) is not None)
        return sqlalchemy.func.regexp(field, regexp)
    else:
        raise Exception('Unimplemented.')


def _ilike_or_regexp(args, connection, field, term):
    '''To be used inside filter().
    '''

    if args.regexp:
        return _regexp(connection, field, term)

    return field.ilike('%%%s%%' % term)


def _ilike_or_regexp_highlight(args, string, term):
    '''Highlights the strings that would have matched _ilike_or_regexp()
    in the database, i.e. performs the same search client-side and adds
    colors around the matches
    '''

    highlight = colors.bold_red + '\\1' + colors.end

    if args.regexp:
        return re.sub('(%s)' % term, highlight, string)

    return re.sub('(%s)' % re.escape(term), highlight, string, flags=re.IGNORECASE)


def _list_object(obj):
    table = []

    for column in obj.__table__.columns.keys():
        table.append([column, getattr(obj, column)])

    return table


def _output_list_object(args, obj):
    output_table(args,
        _list_object(obj),
        ['Property', 'Value'],
    )


def _diff_objects(object1, object2):
    table = []

    columns = object1.__table__.columns.keys()
    columns.remove('name')
    for column in columns:
        value1 = getattr(object1, column)
        value2 = getattr(object2, column)
        if value1 != value2:
            table.append([column, value1, value2])

    return table


def _output_diff_objects(args, object1, object2):
    output_table(args,
        _diff_objects(object1, object2),
        ['Property', '%s Value' % str_db_object(args.db, args.first), '%s Value' % str_db_object(args.destdb, args.second)],
    )


def _default(value, default_value='-'):
    return default_value if value is None else value


def _truefalse(value):
    return 'Present' if value else '-'


def _check_same_object(args):
    if (args.destdb is None or args.db == args.destdb) and (args.second is None or args.first == args.second):
        raise Exception('The source object and the destination object are the same (i.e. same database and same name): %s' % str_db_object(args.db, args.first))

def _connect(db, init, read_only, args, as_admin=False):

    logging.debug('Preparing connection to %s ...', db)

    url = conddb.make_url( db, read_only)
    pretty_url = url
    if url.drivername == 'oracle+frontier':
        ws = url.host.rsplit('%2F')
        if ws is not None:
            pretty_url = 'frontier://%s/%s' %(ws[-1],url.database)
    connTo = '%s [%s]' %(db,pretty_url)
    logging.info('Connecting to %s', connTo)
    logging.debug('DB url: %s',url)
    verbose= 0 
    if args.verbose is not None:
       verbose = args.verbose - 1
    connection = conddb.connect(url, args.authPath, verbose, as_admin)


    if not read_only:
        if connection.is_read_only:
            raise Exception('Impossible to edit a read-only database.')

        if connection.is_official:
            if args.force:
                if not args.yes:
                    logging.warning('You are going to edit an official database. If you are not one of the Offline DB experts but have access to the password for other reasons, please stop now.')
            else:
                raise Exception('Editing official databases is forbidden. Use the official DropBox to upload conditions. If you need a special intervention on the database, see the contact help: %s' % conddb.contact_help)
        # for sqlite we trigger the implicit schema creation
        if url.drivername == 'sqlite':
            if init:
                connection.init()
    if not connection._is_valid:
        raise Exception('No valid schema found in the database.')

    return connection


def connect(args, init=False, read_only=True, as_admin=False):
    args.force = args.force if 'force' in dir(args) else False

    if 'destdb' in args:
        if args.destdb is None:
            args.destdb = args.db
        if args.db == args.destdb:
            conn1 = _connect(args.destdb, init, read_only, args)
            return conn1, conn1
        conn1 = _connect( args.db, init, True, args)
        conn2url = conddb.make_url(args.destdb, False)
        if conn2url.drivername == 'sqlite' and not os.path.exists(args.destdb): 
            init = True
        conn2 = _connect(args.destdb, init, False, args)
        return conn1, conn2

    return _connect( args.db, init, read_only, args, as_admin)

def str_db_object(db, name):
    return '%s::%s' % (db, name)


def str_iov(since, insertion_time):
    return '(%s, %s)' % (since, insertion_time)


def str_record(record, label):
    return '(%s, %s)' % (record, label)


class Colors(object):
    normal_template = '\033[9%sm'
    bold_template = '\033[9%s;1m'

    bold = '\033[1m'

    black   = normal_template % 0
    red     = normal_template % 1
    green   = normal_template % 2
    yellow  = normal_template % 3
    blue    = normal_template % 4
    magenta = normal_template % 5
    cyan    = normal_template % 6
    white   = normal_template % 7

    bold_black   = bold_template % 0
    bold_red     = bold_template % 1
    bold_green   = bold_template % 2
    bold_yellow  = bold_template % 3
    bold_blue    = bold_template % 4
    bold_magenta = bold_template % 5
    bold_cyan    = bold_template % 6
    bold_white   = bold_template % 7

    end = '\033[0m'

    def __init__(self, args):

        if ( stat.S_ISFIFO(os.fstat(sys.stdout.fileno()).st_mode)  or  # we are running in a pipe
             args.nocolors ):
            self.noColors()

    def noColors(self):
        for member in dir(self):
            if not member.startswith('_'):
                setattr(self, member, '')

colors = None


def output(args, string, *parameters, **kwargs):
    if args.quiet:
        return

    output_file = kwargs.get('output_file', sys.stdout)

    to_print = string + colors.end
    if len(parameters)>0:
        to_print = string % parameters + colors.end
    print(to_print, end=' ', file=output_file)

    if kwargs.get('newline', True):
        print(file=output_file)


def _strip_colors(args, string):
    '''Strips colors (i.e. ANSI sequences).
    '''

    if args.nocolors:
        return string

    return re.sub('\x1b\[[;\d]*[A-Za-z]', '', string)


def _ljust_colors(args, string, width, fillchar=' '):
    '''Same as string.ljust(width, fillchar) but supporting colors.
    '''

    if args.nocolors:
        return string.ljust(width, fillchar)

    return string + fillchar * (width - len(_strip_colors(args, string)))


def output_table(args, table, headers, filters=None, output_file=None, no_first_header=False, no_max_length=False):
    if args.quiet:
        return

    if output_file is None:
        output_file = sys.stdout

    if filters is None:
        filters = [None] * len(headers)

    def max_length_filter(s):
        #s = str(s).replace('\n', '\\n')
        s = str(s).replace('\n', '  ')
        s = str(s).replace(chr(13),'  ')
        return '%s...' % s[:conddb.name_length] if ( len(s) > conddb.name_length and not no_max_length ) else s

    new_table = [[] for i in range(len(table))]
    for column_index in range(len(headers)):
        for row_index, row in enumerate(table):
            cell = max_length_filter(row[column_index])
            if filters[column_index] is not None:
                cell = filters[column_index](cell)
            new_table[row_index].append(cell)

    # Calculate the width of each column
    widths = []
    for column_index in range(len(headers)):
        width = len(headers[column_index])
        for row in new_table:
            width = max(width, len(_strip_colors(args, row[column_index])))
        widths.append(width)

    # Print the table
    header_separator = '-'
    column_separator = ''

    for column_index, header in enumerate(headers):
        output(args, colors.bold + _ljust_colors(args, header, widths[column_index]) + ' ' + column_separator, newline=False, output_file=output_file)
    output(args, '', output_file=output_file)

    for column_index in range(len(headers)):
        output(args, (' ' if column_index == 0 and no_first_header else header_separator) * widths[column_index] + ' ' + column_separator, newline=False, output_file=output_file)
    output(args, '', output_file=output_file)

    for row in new_table:
        for column_index, cell in enumerate(row):
            output(args, _ljust_colors(args, cell, widths[column_index]) + ' ' + column_separator, newline=False, output_file=output_file)
        output(args, '', output_file=output_file)
    output(args, '', output_file=output_file)


# Commands
def help(args):
    output(args, colors.bold + 'CMS Condition DB command-line tool.')
    output(args, '')
    output(args, colors.bold + 'Usage')
    output(args, colors.bold + '-----')
    output(args, '')
    output(args, '  This tool provides several subcommands, each of those')
    output(args, '  serves a well-defined purpose.')
    output(args, '')
    output(args, '  To see the list of available subcommands and the global options, run:')
    output(args, '')
    output(args, '    conddb -h')
    output(args, '')
    output(args, '  To see the help of a subcommand and its options, run:')
    output(args, '')
    output(args, '    conddb <command> -h.')
    output(args, '    e.g. conddb list -h')
    output(args, '')
    output(args, '')
    output(args, colors.bold + 'Exit status')
    output(args, colors.bold + '-----------')
    output(args, '')
    output(args, '  0  =  OK.')
    output(args, '  1  =  Runtime error (i.e. any kind of error not related to syntax).')
    output(args, '  2  =  Usage/syntax error.')
    output(args, '')
    output(args, '')
    output(args, colors.bold + 'Database parameter (--db)')
    output(args, colors.bold + '-------------------------')
    output(args, '  ' + '\n  '.join(textwrap.dedent(conddb.database_help).splitlines()))
    output(args, '')
    output(args, '')
    output(args, colors.bold + 'Contact help')
    output(args, colors.bold + '------------')
    output(args, '')
    output(args, '  ' + '\n  '.join(textwrap.wrap(conddb.contact_help)))
    output(args, '')


def init(args):
    connection = connect(args, init=True, read_only=False)

def status(args):
    connection = connect(args)

    valid = connection.is_valid()

    output(args, 'Database Status:')
    output(args, '')
    output(args, '         Schema:  %s', 'OK (required tables are present)' if valid else 'Wrong (missing required tables)')
    if not valid:
        return

    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    Payload = session.get_dbtype(conddb.Payload)
    GlobalTag = session.get_dbtype(conddb.GlobalTag)

    tag_count = session.query(Tag.name).count()
    payload_count = session.query(Payload.hash).count()
    global_tag_count = session.query(GlobalTag.name).count()

    output(args, '         # tags:  %s  %s', tag_count, '(the last %s inserted are shown below)' % args.limit if tag_count > 0 else '')
    output(args, '     # payloads:  %s  %s', payload_count, '(the last %s inserted are shown below)' % args.limit if payload_count > 0 else '')
    output(args, '  # global tags:  %s  %s', global_tag_count, '(the last %s inserted are shown below)' % args.limit if global_tag_count > 0 else '')
    output(args, '')

    if tag_count > 0:
        output_table(args,
            session.query(Tag.name, Tag.time_type, Tag.object_type, Tag.synchronization, Tag.insertion_time, Tag.description).\
                order_by(Tag.insertion_time.desc()).\
                limit(args.limit).\
                all(),
            ['Name', 'Time Type', 'Object Type', 'Synchronization', 'Insertion Time', 'Description'],
        )

    if payload_count > 0:
        output_table(args,
            session.query(Payload.hash, Payload.object_type, Payload.version, Payload.insertion_time).\
                order_by(Payload.insertion_time.desc()).\
                limit(args.limit).\
                all(),
            ['Payload', 'Object Type', 'Version', 'Insertion Time'],
        )

    if global_tag_count > 0:
        output_table(args,
            session.query(GlobalTag.name, GlobalTag.release, GlobalTag.insertion_time, GlobalTag.description).\
                order_by(GlobalTag.insertion_time.desc()).\
                limit(args.limit).\
                all(),
            ['Global Tag', 'Release', 'Insertion Time', 'Description'],
        )


def search(args):
    connection = connect(args)
    session = connection.session()

    max_limit = 100
    if args.limit is not None and int(args.limit) > max_limit:
        raise Exception('The limit on the number of returned results is capped at %s. Please use a reasonable limit.' % max_limit)

    if connection.is_frontier and ':' in args.string:
        raise Exception('Sorry, the colon : character is not allowed in queries to Frontier (yet). Please use another search term or connect to Oracle directly.')

    logging.info('Searching with a limit of %s results per type of object, starting from the latest inserted ones. If you do not find your object, please try to be more specific or increase the limit of returned results.', args.limit)

    if args.nocolors:
        _ilike_or_regexp_highlight_filter = None
    else:
        def _ilike_or_regexp_highlight_filter(cell):
            return _ilike_or_regexp_highlight(args, cell, args.string)

    def size(cell):
        return str( sys.getsizeof( bytearray(cell,encoding='utf8') ) )

    Tag = session.get_dbtype(conddb.Tag)
    output_table(args,
        session.query(Tag.name, Tag.time_type, Tag.object_type, Tag.synchronization, Tag.insertion_time, Tag.description).\
            filter(
                _ilike_or_regexp(args, connection, Tag.name, args.string)
                | _ilike_or_regexp(args, connection, Tag.object_type, args.string)
                | _ilike_or_regexp(args, connection, Tag.description, args.string)
            ).\
            order_by(Tag.insertion_time.desc()).\
            limit(args.limit).\
            all(),
        ['Tag', 'Time Type', 'Object Type', 'Synchronization', 'Insertion Time', 'Description'],
        filters = [_ilike_or_regexp_highlight_filter, None, _ilike_or_regexp_highlight_filter, None, None, _ilike_or_regexp_highlight_filter],
    )

    Payload = session.get_dbtype(conddb.Payload)
    output_table(args,
        session.query(Payload.hash, Payload.object_type, Payload.version, Payload.insertion_time, Payload.data).\
            filter(
                _ilike_or_regexp(args, connection, Payload.hash, args.string)
                | _ilike_or_regexp(args, connection, Payload.object_type, args.string)
            ).\
            order_by(Payload.insertion_time.desc()).\
            limit(args.limit).\
            all(),
        ['Payload', 'Object Type', 'Version', 'Insertion Time', 'Size'],
        filters = [_ilike_or_regexp_highlight_filter, _ilike_or_regexp_highlight_filter, None, None, size],
    )

    try:
        GlobalTag = session.get_dbtype(conddb.GlobalTag)
        output_table(args,
            session.query(GlobalTag.name, GlobalTag.release, GlobalTag.insertion_time, GlobalTag.description).\
                filter(
                    _ilike_or_regexp(args, connection, GlobalTag.name, args.string)
                    | _ilike_or_regexp(args, connection, GlobalTag.release, args.string)
                    | _ilike_or_regexp(args, connection, GlobalTag.description, args.string)
                ).\
                order_by(GlobalTag.insertion_time.desc()).\
                limit(args.limit).\
                all(),
            ['Global Tag', 'Release', 'Insertion Time', 'Description'],
            filters = [_ilike_or_regexp_highlight_filter, _ilike_or_regexp_highlight_filter, None, _ilike_or_regexp_highlight_filter],
         )
    except sqlalchemy.exc.OperationalError:
        sys.stderr.write("No table for GlobalTags found in DB.\n\n")


def _inserted_before(_IOV,timestamp):
    '''To be used inside filter().
    '''

    if timestamp is None:
        # XXX: Returning None does not get optimized (skipped) by SQLAlchemy,
        #      and returning True does not work in Oracle (generates "and 1"
        #      which breaks Oracle but not SQLite). For the moment just use
        #      this dummy condition.
        return sqlalchemy.literal(True) == sqlalchemy.literal(True)

    return _IOV.insertion_time <= _parse_timestamp(timestamp)

def _high(n):
    return int(n) >> 32

def _low(n):
    return int(n) & 0xffffffff

def _convertTimeType(since):
    #try:
    #    return str(datetime.datetime.utcfromtimestamp(_high(since)).replace(microsecond = _low(since)))
    #except ValueError:
    #    return str(datetime.datetime.utcfromtimestamp(_high(since)).replace(microsecond = _low(since)/1000))
    # looks like the format of the lower part of the encoding is broken. Better ignore it...
    return str(datetime.datetime.utcfromtimestamp(_high(since)).replace(microsecond = 0))

def _since_filter(time_type):
    '''Returns a filter function for the given time type that returns
    a human-readable string of the given since.

    For run (sinces are 32-bit unsigned integers), hash (sinces are strings)
    and user (sinces are strings) the filter returns the sinces unchanged.

    The time sinces are 64-bit integers built from a pair (UNIX time,
    microseconds), each 32-bit wide. The filter returns a readable timestamp,
    including the microseconds.

    The lumi sinces are 64-bit integers built from a pair (run, lumi),
    each 32-bit wide. The filter returns a string with both numbers, split.
    '''

    if time_type == conddb.TimeType.Time.value:
        return lambda since: '%s (%s)' % (_convertTimeType(since), since)

    if time_type == conddb.TimeType.Lumi.value:
        return lambda since: '%s : %5s (%s)' % (_high(since), _low(since), since)

    return lambda since: since

def _get_hlt_fcsr( session, timeType ):
    RunInfo = session.get_dbtype(conddb.RunInfo)
    lastRun = session.query(sqlalchemy.func.max(RunInfo.run_number)).scalar()
    fcsr = lastRun+1
    if timeType == 'Time':
        raise Exception('Cannot find time for non-existing runs.')
    ret = _convert_time( session, timeType, fcsr )
    if timeType == 'Run':
        logging.info('Found fcsr for hlt: %s' %fcsr)
    else:
        logging.info('Found fcsr for hlt: %s ( %s as %s type )' %(fcsr,ret,timeType))
    return ret

def _get_prompt_fcsr( session, timeType ):
    tier0timeout = 5
    tier0retries = 3 
    tier0retryPeriod = 5
    tier0proxy = None
    try:
        t0DataSvc = Tier0Handler( tier0Url,
                                  tier0timeout, tier0retries, tier0retryPeriod,
                                  tier0proxy, False )
        try:
            fcsr = t0DataSvc.getFirstSafeRun()
        except ValueError as e:
            logging.error('ValueError for firstConditionSafeRun from Tier-0 %s ' % (str(e),) )
            # We got an answer but it is invalid. So far this usually means
            # "None" which is not JSON, when the Tier0 is stopped.
            raise Exception('Invalid firstConditionSafeRun from Tier-0')
        except Tier0Error:
            # Impossible to get anything from the server after retries,
            # i.e. unreachable, so no data.
            raise Exception('Tier-0 is unreachable, i.e. no firstConditionSafeRun')
    except Exception as e:
        raise Exception('Error setting up Tier-0 data service: %s' %str(e))
    ret = _convert_time( session, timeType, fcsr )
    if timeType == 'Run':
        logging.info('Found Tier0 fcsr for prompt: %s' %fcsr)
    else:
        logging.info('Found Tier0 fcsr for prompt: %s ( %s as %s type )' %(fcsr,ret,timeType))
    return ret

def _get_last_frozen_since( session, tagName, fcsr=None ):
    IOV = session.get_dbtype(conddb.IOV)
    query = session.query(sqlalchemy.func.max(IOV.since)).filter(IOV.tag_name == tagName )
    if fcsr is not None:
        query = query.filter(IOV.since<fcsr)
    res = query.scalar()
    logging.debug('Last frozen since in destination tag is %s' %res)
    return res

def _get_maxtime_for_boost_version( session, timeType, boost_version):
    BoostRunMap = session.get_dbtype(conddb.BoostRunMap)
    q = session.query(BoostRunMap).order_by(BoostRunMap.boost_version.asc())
    time = (maxSince,maxSince,maxSince)
    for r in q:
        r= _rawdict(r)
        if boost_version < r['boost_version']:
            tlumi = r['run_number']<<32|0x1
            time = (r['run_number'],tlumi,r['run_start_time'])
            break
    if timeType=='Run':
        return time[0]
    elif timeType=='Lumi':
        return time[1]
    elif timeType=='Time':
        return time[2]
    return None

class run_to_timestamp( object ):
    def __init__( self, session ):
        self.session = session

    def convertOne( self, runNumber ):
        logging.debug('Converting run %s to timestamp...' %runNumber)
        RunInfo = self.session.get_dbtype(conddb.RunInfo)
        bestRun = self.session.query(RunInfo.run_number,RunInfo.start_time, RunInfo.end_time).filter(RunInfo.run_number >= runNumber).first()
        if bestRun is None:
            raise Exception("Run %s can't be matched with an existing run in the database." %runNumber)
        return bestRun[1],bestRun[2]

    def convertIovs( self, iovs ):
        ks = sorted(iovs.keys())
        logging.info('Converting %s run-based iovs to time-based' %len(iovs) )         
        RunInfo = self.session.get_dbtype(conddb.RunInfo)
        maxRun = ks[-1]
        upperRun = self.session.query(RunInfo.run_number).filter(RunInfo.run_number >= maxRun ).first()
        if upperRun is None:
            raise Exception("Upper limit run %s cannot be matched with an existing run in the database." %maxRun)
        upperRun = upperRun[0]
        runs = self.session.query(RunInfo.run_number,RunInfo.start_time).filter(RunInfo.run_number >= ks[0],RunInfo.run_number <=upperRun).\
            order_by(RunInfo.run_number).all()
        newiovs = {}
        for since in ks:
            # take the smallest greater or equal to the target
            bestRun = min([run for run in runs if run[0] >= since],key=lambda x: x[0])
            bestRunTime = calendar.timegm( bestRun[1].utctimetuple() ) << 32
            newiovs[bestRunTime] =  iovs[since]
        return newiovs

class timestamp_to_run( object ):
    def __init__( self, session ):
        self.session = session

    def convertIovs( self, iovs ):
        ks = sorted(iovs.keys())
        logging.info('Converting %s time-based iovs to run-based' %len(iovs) )         
        RunInfo = self.session.get_dbtype(conddb.RunInfo)
        minTs = datetime.datetime.utcfromtimestamp( int(ks[0]) >> 32 )
        maxTs = datetime.datetime.utcfromtimestamp( int(ks[len(ks)-1]) >> 32 )
        if self.session.query(RunInfo.end_time).filter(RunInfo.end_time > minTs).count() == 0:
            raise Exception("Lower IOV sincetimestamp %s cannot be matched with an existing run in the database."%minTs)
        firstRun = self.session.query(sqlalchemy.func.min(RunInfo.run_number)).filter(RunInfo.end_time > minTs).one()[0]
        if self.session.query(RunInfo.start_time).filter(RunInfo.start_time < maxTs).count() == 0:
            raise Exception("Upper iov since timestamp %s cannot be matched with an existing run in the database"%maxTs)
        lastRun = self.session.query(sqlalchemy.func.max(RunInfo.run_number)).filter(RunInfo.start_time < maxTs).one()[0]
        runs = self.session.query(RunInfo.run_number,RunInfo.start_time,RunInfo.end_time).filter(RunInfo.run_number >= firstRun).filter(RunInfo.run_number <= lastRun ).order_by(RunInfo.run_number).all()
        newiovs = {}
        prevRun = None
        for since in ks:
            ts = datetime.datetime.utcfromtimestamp( since >> 32 )
            run = None
            for r in runs:
                if ts >= r[1] and ts <= r[2]:
                    run = r[0]
                    break
            if run is not None:
                if run == prevRun:
                    logging.info('Skipping iov with since %s, because it corresponds to an already mapped run %s' %(since,run))
                else:
                    prevRun = run
                    newiovs[run] = iovs[since]
            else:                                     
                logging.info('Skipping iov with since %s, because no run is matching that time.'%since )
        return newiovs

def _convert_time( session, toTimeType, runNumber ):
    if toTimeType == 'Run':
        return runNumber
    elif toTimeType == 'Lumi':
        lumiId = runNumber<<32|0x1
        logging.debug('Run %s encoded in lumi id %s' %(runNumber,lumiId))
        return lumiId
    elif toTimeType == 'Time':
        converter = run_to_timestamp( session )
        start, stop = converter.convertOne( runNumber )
        logging.debug('Run %s coverted in time (start) %s' %(runNumber,start))
        timest = calendar.timegm( start.utctimetuple() ) << 32
        return timest
    else:
        raise Exception('Cannot convert to runs to time type %s' %toTimeType)

class hlt_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        if timeType in ('Time','Lumi'):
            raise Exception("Can't synchronize a tag with time type '%s' to hlt" %timeType) 
        session = conddb.getSessionOnMasterDB( session1, session2 )
        self.fcsr = _get_hlt_fcsr( session, timeType  ) 
        self.lastFrozenSince = _get_last_frozen_since( session2, destTag, self.fcsr ) 

    def validate( self, iovs ):
        new_iovs = {}
        late_iovs = []
        for since in sorted(iovs.keys(),reverse=True):
            if since >= self.fcsr:
                new_iovs[since] = iovs[since]
            else:
                if self.lastFrozenSince is None or since > self.lastFrozenSince:
                    if self.fcsr not in new_iovs.keys():
                        new_iovs[self.fcsr] = iovs[since]
                    else:
                        late_iovs.append(since)
                else:
                    late_iovs.append(since)
        nlate = len(late_iovs)
        if nlate>0:
            logging.warning('%s IOV(s) with since less than the hlt FCSR (%s) have been discarded.' %(nlate,self.fcsr) ) 
        return True, new_iovs

class prompt_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        session = conddb.getSessionOnMasterDB( session1, session2 )
        self.fcsr = _get_prompt_fcsr( session, timeType ) 
        self.lastFrozenSince = _get_last_frozen_since( session2, destTag, self.fcsr ) 

    def validate( self, iovs ):
        new_iovs = {}
        late_iovs = []
        for since in sorted(iovs.keys(),reverse=True):
            if since >= self.fcsr:
                new_iovs[since] = iovs[since]
            else:
                if self.lastFrozenSince is None or since > self.lastFrozenSince:
                    if self.fcsr not in new_iovs.keys():
                        new_iovs[self.fcsr] = iovs[since]
                    else:
                        late_iovs.append(since)
                else:
                    late_iovs.append(since)
        nlate = len(late_iovs)
        if nlate>0:
            logging.warning('%s IOV(s) with since less than the tier0 FCSR (%s) have been discarded.' %(nlate,self.fcsr) ) 
        return True, new_iovs

class pcl_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        session = conddb.getSessionOnMasterDB( session1, session2 )
        self.fcsr = _get_prompt_fcsr(session, timeType) 

    def validate( self, iovs ):
        new_iovs = {}
        late_iovs = []
        ret = True
        for since in sorted(iovs.keys(),reverse=True):
            if since >= self.fcsr:
                new_iovs[since] = iovs[since]
            else:
                late_iovs.append(since)
        nlate = len(late_iovs)
        if nlate>0:
            logging.error('%s IOV(s) with since less than the tier0 FCSR (%s) have been discarded.' %(nlate,self.fcsr) ) 
            ret = False
        return ret, new_iovs

class mc_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        self.lastFrozenSince = _get_last_frozen_since( session2, destTag ) 

    def validate( self, iovs ):
        new_iovs = {}
        niovs = len(iovs)
        ret = True
        if self.lastFrozenSince is None:
            if 1 not in iovs.keys():
                raise Exception( 'Could not find an iov with since 1 in the source tag.')
            new_iovs[1] = iovs[1]
            if niovs>1:
                logging.warning('%s IOV(s) with since greater than the expected mc since=1 will be discarded.' %(niovs-1))
        else:
            if niovs>0:
                ret = False
                logging.warning('The destination tag is frozen - no iov can be added.')
        return ret, new_iovs

class runmc_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        self.lastFrozenSince = _get_last_frozen_since( session2, destTag ) 

    def validate( self, iovs ):
        new_iovs = {}
        niovs = len(iovs)
        ret = True
        if self.lastFrozenSince is not None:
            if niovs>0:
                ret = False
                logging.warning('The destination tag is frozen - no iov can be added.')
        return ret, new_iovs

class offline_synchro_policy( object ):
    def __init__(self, session1, session2, timeType, destTag):
        self.lastFrozenSince = _get_last_frozen_since( session2, destTag ) 

    def validate( self, iovs ):
        new_iovs = {}
        late_iovs = []
        for since in sorted(iovs.keys(),reverse=True):
            if self.lastFrozenSince is None or since > self.lastFrozenSince:
                new_iovs[since] = iovs[since]
            else:
                late_iovs.append(since)
        nlate = len(late_iovs)
        ret = True
        if nlate>0:
            ret = False
            logging.warning('%s IOV(s) with since less than the last since in the destination tag (%s) have been discarded.' %(nlate,self.lastFrozenSince) ) 
        return ret, new_iovs

class no_synchro_policy( object ):
    def __init__(self, session1=None, session2=None, timeType=None, destTag=None):
        pass

    def validate( self, iovs ):
        return True, iovs

_synchro_map = { 'hlt': hlt_synchro_policy, 'express': hlt_synchro_policy, 'prompt': prompt_synchro_policy, 'pcl':pcl_synchro_policy, 
                 'mc': mc_synchro_policy, 'offline': offline_synchro_policy, 'any':no_synchro_policy, 'validation':no_synchro_policy,
                 'runmc': runmc_synchro_policy }
def _get_synchro_policy( synchronization ):
    if synchronization not in _synchro_map.keys():
        raise Exception('Cannot handle synchronization %s' %synchronization)
    return _synchro_map[synchronization]

def mc_validate( session, tag ):
    IOV = session.get_dbtype(conddb.IOV)
    niovs = session.query(IOV).filter(IOV.tag_name==tag).count()
    if niovs>1:
        logging.error('Validation of tag content for synchronization "mc" failed: more than one IOV found.')
        return False
    if niovs>0:
        r = int(session.query(IOV.since).filter(IOV.tag_name==tag).one()[0])
        if r!=1:
            logging.error('Validation of tag content for synchronization "mc" failed: IOV since=%s (expected=1)' %r)
            return False
    return True

def listTags_(args):
    connection = connect(args)
    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    output_table(args,
        session.query(Tag.name, Tag.time_type, Tag.object_type, Tag.synchronization, Tag.end_of_validity, Tag.insertion_time, Tag.description ).\
            order_by(Tag.insertion_time, Tag.name).\
            all(),
        ['Name', 'TimeType', 'ObjectType', 'Synchronisation', 'EndOfValidity', 'Insertion_time', 'Description'],
    )
    return 0



def listParentTags_(args):
    connection = connect(args)
    session = connection.session()

    IOV = session.get_dbtype(conddb.IOV)
    Tag = session.get_dbtype(conddb.Tag)

    query_result = session.query(IOV.tag_name).filter(IOV.payload_hash == args.hash_name).all()
    tag_names = map(lambda entry : entry[0], query_result)

    listOfOccur=[]

    for tag in tag_names:
        synchro = session.query(Tag.synchronization).filter(Tag.name == tag).all()
        iovs = session.query(IOV.since).filter(IOV.tag_name == tag).filter(IOV.payload_hash == args.hash_name).all()
        times = session.query(IOV.insertion_time).filter(IOV.tag_name == tag).filter(IOV.payload_hash == args.hash_name).all()

        synchronization = [item[0] for item in synchro]
        listOfIOVs  = [item[0] for item in iovs]
        listOfTimes = [str(item[0]) for item in times]

        for iEntry in range(0,len(listOfIOVs)):
            listOfOccur.append({"tag": tag,
                                "synchronization" : synchronization[0],
                                "since" : listOfIOVs[iEntry] ,
                                "insertion_time" : listOfTimes[iEntry] })

    t = PrettyTable(['hash', 'since','tag','synch','insertion time'])
    for element in listOfOccur:
        t.add_row([args.hash_name,element['since'],element['tag'],element['synchronization'],element['insertion_time']])

    print(t)




def diffGlobalTagsAtRun_(args):
    connection = connect(args)
    session = connection.session()

    IOV     = session.get_dbtype(conddb.IOV)
    TAG     = session.get_dbtype(conddb.Tag)
    GT      = session.get_dbtype(conddb.GlobalTag)
    GTMAP   = session.get_dbtype(conddb.GlobalTagMap)
    RUNINFO = session.get_dbtype(conddb.RunInfo)

    ####################################
    # Get the time info for the test run
    ####################################

    if(not args.lastIOV):

        if(int(args.testRunNumber)<0):
            raise Exception("Run %s (default) can't be matched with an existing run in the database. \n\t\t Please specify a run with the option --run." % args.testRunNumber)

        if(int(args.testRunNumber)!=1):
            bestRun = session.query(RUNINFO.run_number, RUNINFO.start_time, RUNINFO.end_time).filter(RUNINFO.run_number == int(args.testRunNumber)).first()
            if bestRun is None:
                raise Exception("Run %s can't be matched with an existing run in the database." % args.testRunNumber)

            print("Run",args.testRunNumber," |Start time",bestRun[1]," |End time",bestRun[2],".")

    ####################################
    # Get the Global Tag snapshots
    ####################################

    refSnap = session.query(GT.snapshot_time).\
        filter(GT.name == args.refGT).all()[0][0]

    tarSnap = session.query(GT.snapshot_time).\
        filter(GT.name == args.tarGT).all()[0][0]

    print("reference GT (",args.refGT ,") snapshot: ",refSnap," | target GT (",args.tarGT,") snapshot",tarSnap)

    ####################################
    # Get the Global Tag maps
    ####################################

    GTMap_ref = session.query(GTMAP.record, GTMAP.label, GTMAP.tag_name).\
        filter(GTMAP.global_tag_name == args.refGT).\
        order_by(GTMAP.record, GTMAP.label).\
        all()

    GTMap_tar = session.query(GTMAP.record, GTMAP.label, GTMAP.tag_name).\
        filter(GTMAP.global_tag_name == args.tarGT).\
        order_by(GTMAP.record, GTMAP.label).\
        all()

    text_file = open(("diff_%s_vs_%s.twiki") % (args.refGT,args.tarGT), "w")

    differentTags = {}

    for element in GTMap_ref:
        RefRecord = element[0]
        RefLabel  = element[1]
        RefTag    = element[2]

        for element2 in GTMap_tar:
            if (RefRecord == element2[0] and RefLabel==element2[1]):
                if RefTag != element2[2]:
                    differentTags[(RefRecord,RefLabel)]=(RefTag,element2[2])

    ####################################
    ## Search for Records,Label not-found in the other list
    ####################################

    temp1 = [item for item in GTMap_ref if (item[0],item[1]) not in list(zip(list(zip(*GTMap_tar))[0],list(zip(*GTMap_tar))[1]))]
    for elem in temp1:
        differentTags[(elem[0],elem[1])]=(elem[2],"")

    temp2 = [item for item in GTMap_tar if (item[0],item[1]) not in list(zip(list(zip(*GTMap_ref))[0],list(zip(*GTMap_ref))[1]))]
    for elem in temp2:
        differentTags[(elem[0],elem[1])]=("",elem[2])

    text_file.write("| *Record* | *"+args.refGT+"* | *"+args.tarGT+"* | Remarks | \n")

    t = PrettyTable()

    if(args.isVerbose):
        t.field_names = ['/','',args.refGT,args.tarGT,refSnap,tarSnap]
    else:
        t.field_names = ['/','',args.refGT,args.tarGT]

    t.hrules=1

    if(args.isVerbose):
        t.add_row(['Record','label','Reference Tag','Target Tag','hash1:time1:since1','hash2:time2:since2'])
    else:
        t.add_row(['Record','label','Reference Tag','Target Tag'])

    isDifferent=False

    ####################################
    # Loop on the difference
    ####################################

    for Rcd in sorted(differentTags):

        # empty lists at the beginning
        refTagIOVs=[]
        tarTagIOVs=[]

        if( differentTags[Rcd][0]!=""):
            refTagIOVs = session.query(IOV.since,IOV.payload_hash,IOV.insertion_time).filter(IOV.tag_name == differentTags[Rcd][0]).all()
            refTagInfo = session.query(TAG.synchronization,TAG.time_type).filter(TAG.name == differentTags[Rcd][0]).all()[0]
        if( differentTags[Rcd][1]!=""):
            tarTagIOVs = session.query(IOV.since,IOV.payload_hash,IOV.insertion_time).filter(IOV.tag_name == differentTags[Rcd][1]).all()
            tarTagInfo = session.query(TAG.synchronization,TAG.time_type).filter(TAG.name == differentTags[Rcd][1]).all()[0]

        if(differentTags[Rcd][0]!="" and differentTags[Rcd][1]!=""):
            if(tarTagInfo[1] != refTagInfo[1]):
                print(colors.bold_red+" *** Warning *** found mismatched time type for",Rcd,"entry. \n"+differentTags[Rcd][0],"has time type",refTagInfo[1],"while",differentTags[Rcd][1],"has time type",tarTagInfo[1]+". These need to be checked by hand. \n\n"+ colors.end)

        if(args.lastIOV):

            if(sorted(differentTags).index(Rcd)==0):
                print("\n")
                print(33 * "=")
                print("|| COMPARING ONLY THE LAST IOV ||")
                print(33 * "=")
                print("\n")

            lastSinceRef=-1
            lastSinceTar=-1

            hash_lastRefTagIOV = ""
            time_lastRefTagIOV = ""

            hash_lastTarTagIOV = ""
            time_lastTarTagIOV = ""

            for i in refTagIOVs:
                if (i[0]>lastSinceRef):
                    lastSinceRef = i[0]
                    hash_lastRefTagIOV = i[1]
                    time_lastRefTagIOV = str(i[2])

            for j in tarTagIOVs:
                if (j[0]>lastSinceTar):
                    lastSinceTar = j[0]
                    hash_lastTarTagIOV = j[1]
                    time_lastTarTagIOV = str(j[2])

            if(hash_lastRefTagIOV!=hash_lastTarTagIOV):
                isDifferent=True
                text_file.write("| ="+Rcd[0]+"= ("+Rcd[1]+") | =="+differentTags[Rcd][0]+"==  | =="+differentTags[Rcd][1]+"== | | \n")
                text_file.write("|^|"+hash_lastRefTagIOV+" <br> ("+time_lastRefTagIOV+") "+ str(lastSinceRef) +" | "+hash_lastTarTagIOV+" <br> ("+time_lastTarTagIOV+") " + str(lastSinceTar)+" | ^| \n")

                if(args.isVerbose):
                    t.add_row([Rcd[0],Rcd[1],differentTags[Rcd][0],differentTags[Rcd][1],str(hash_lastRefTagIOV)+"\n"+str(time_lastRefTagIOV)+"\n"+str(lastSinceRef),str(hash_lastTarTagIOV)+"\n"+str(time_lastTarTagIOV)+"\n"+str(lastSinceTar)])
                else:
                    t.add_row([Rcd[0],Rcd[1],differentTags[Rcd][0]+"\n"+str(hash_lastRefTagIOV),differentTags[Rcd][1]+"\n"+str(hash_lastTarTagIOV)])

        else:

            ### reset all defaults

            theGoodRefIOV=-1
            theGoodTarIOV=-1
            sinceRefTagIOV=0
            sinceTarTagIOV=0

            RefIOVtime = datetime.datetime(1970, 1, 1, 0, 0, 0)
            TarIOVtime = datetime.datetime(1970, 1, 1, 0, 0, 0)

            theRefPayload=""
            theTarPayload=""
            theRefTime=""
            theTarTime=""

            ### loop on the reference IOV list
            for refIOV in refTagIOVs:

                ## logic for retrieving the the last payload active on a given IOV
                ## - the new IOV since is less than the run under consideration
                ## - the payload insertion time is lower than the GT snapshot
                ## - finall either:
                ##   - the new IOV since is larger then the last saved
                ##   - the new IOV since is equal to the last saved but it has a more recent insertion time

                if ( (refIOV[0] <= int(args.testRunNumber)) and (refIOV[0]>sinceRefTagIOV) or ((refIOV[0]==sinceRefTagIOV) and (refIOV[2]>RefIOVtime)) and (refIOV[2]<=refSnap) ):
                    sinceRefTagIOV = refIOV[0]
                    RefIOVtime = refIOV[2]
                    theGoodRefIOV=sinceRefTagIOV
                    theRefPayload=refIOV[1]
                    theRefTime=str(refIOV[2])

            ### loop on the target IOV list
            for tarIOV in tarTagIOVs:
                if ( (tarIOV[0] <= int(args.testRunNumber)) and (tarIOV[0]>sinceTarTagIOV) or ((tarIOV[0]==sinceTarTagIOV) and (tarIOV[2]>=TarIOVtime)) and (tarIOV[2]<=tarSnap) ):
                    sinceTarTagIOV = tarIOV[0]
                    tarIOVtime = tarIOV[2]
                    theGoodTarIOV=sinceTarTagIOV
                    theTarPayload=tarIOV[1]
                    theTarTime=str(tarIOV[2])

            #print Rcd[0],theRefPayload,theTarPayload

            if(theRefPayload!=theTarPayload):
                isDifferent=True
                text_file.write("| ="+Rcd[0]+"= ("+Rcd[1]+") | =="+differentTags[Rcd][0]+"==  | =="+differentTags[Rcd][1]+"== |\n")
                text_file.write("|^|"+theRefPayload+" ("+theRefTime+") | "+theTarPayload+" ("+theTarTime+") |\n")

                ### determinte if it is to be shown

                isMatched=False
                tokens=args.stringToMatch.split(",")
                decisions = [bool(Rcd[0].find(x)!=-1) for x in tokens]
                for decision in decisions:
                    isMatched = (isMatched or decision)

                if(args.isVerbose):
                    if (args.stringToMatch=="" or isMatched):
                        t.add_row([Rcd[0],Rcd[1],differentTags[Rcd][0],differentTags[Rcd][1],str(theRefPayload)+"\n"+str(theRefTime)+"\n"+str(theGoodRefIOV),str(theTarPayload)+"\n"+str(theTarTime)+"\n"+str(theGoodTarIOV)])
                else:
                    if (args.stringToMatch=="" or isMatched):
                        t.add_row([Rcd[0],Rcd[1],differentTags[Rcd][0]+"\n"+str(theRefPayload),differentTags[Rcd][1]+"\n"+str(theTarPayload)])

    if(not isDifferent):
        if(args.isVerbose):
            t.add_row(["None","None","None","None","None","None"])
        else:
            t.add_row(["None","None","None"])
    print(t)



def listGTsForTag_(args):
    connection = connect(args)
    session = connection.session()
    GlobalTagMap = session.get_dbtype(conddb.GlobalTagMap)
    output_table(args,
        session.query(GlobalTagMap.global_tag_name, GlobalTagMap.tag_name, GlobalTagMap.record, GlobalTagMap.label).\
            filter(GlobalTagMap.tag_name == args.name).\
            order_by(GlobalTagMap.global_tag_name).\
            all(),
        ['GT_name', 'Tag_name', 'record', 'label'],
    )


def listGTs_(args):
    connection = connect(args)
    session = connection.session()
    GlobalTag = session.get_dbtype(conddb.GlobalTag)
    output_table(args,
        session.query(GlobalTag.name, GlobalTag.description, GlobalTag.release, GlobalTag.snapshot_time, GlobalTag.insertion_time).\
            order_by(GlobalTag.insertion_time, GlobalTag.name).\
            all(),
        ['GT_name', 'Description', 'Release', 'Snapshot_time', 'Insertion_time'],
    )


def listRuns_(args):
    connection = connect(args)
    session = connection.session()
    RunInfo = session.get_dbtype(conddb.RunInfo)
    fromRun = None
    toRun = None
    match = args.match
    limit = None
    if args.last:
        match = session.query(sqlalchemy.func.max(RunInfo.run_number)).one()[0]
    if match is None:
        fromTime = getattr(args, 'from')
        if fromTime is not None:
            fromTime = str(fromTime)
            fromRun = fromTime
            start = None
            if fromTime.isnumeric():
                if len(fromTime) >=  sizeOfTimestamp:
                    start = datetime.datetime.utcfromtimestamp( int(fromTime) >> 32 )
            else:
                start = datetime.datetime.strptime(fromTime,'%Y-%m-%d %H:%M:%S')
            if start is not None:
                fromRun = session.query(sqlalchemy.func.min(RunInfo.run_number)).filter(RunInfo.end_time > start).one()[0]
                logging.debug('run lower boundary: %s (%s)'%(fromRun, start.strftime('%Y-%m-%d %H:%M:%S')))
        toTime = getattr(args, 'to')
        if toTime is not None:   
            toTime = str(toTime)
            toRun = toTime
            end = None
            if toTime.isnumeric():
                if len(toTime) >=  sizeOfTimestamp:
                    end = datetime.datetime.utcfromtimestamp( int(toTime) >> 32 )
            else:
                end=datetime.datetime.strptime(toTime,'%Y-%m-%d %H:%M:%S')
            if end is not None:
                toRun = session.query(sqlalchemy.func.max(RunInfo.run_number)).filter(RunInfo.start_time < end).one()[0]
                logging.debug('run upper boundary: %s (%s)' %(toRun,end.strftime('%Y-%m-%d %H:%M:%S')))
    q = session.query(RunInfo.run_number,RunInfo.start_time,RunInfo.end_time)
    sel = False
    if match is not None:
        q = q.filter(RunInfo.run_number == match)
    else:
        if fromRun is not None:
            q = q.filter(RunInfo.run_number >= fromRun)
            sel = True
        if toRun is not None:
            q = q.filter(RunInfo.run_number <= toRun)
            sel = True
        if not sel and args.limit is not None:
            limit = args.limit
            q = q.order_by(RunInfo.run_number.desc())
            q = q.limit(limit)
            q = q.from_self()
    table = q.order_by(RunInfo.run_number).all()
    if len(table)==0:
        sel = ''
        msg = 'No Run found'
        if args.match is not None:
            sel = 'matching Run=%s' %args.match
        else:
            fromTime = getattr(args, 'from')
            if fromTime is not None:
                sel = "with from='%s'"%fromTime
            if args.to is not None:
                if len(sel):
                    sel = sel +' and '
                else:
                    sel = 'with '
                sel = sel + "to='%s'"%args.to
        msg = msg + ' %s'%sel
        print(msg)
        return 1
    else:
        if limit is not None:
            logging.info('Run entries limited to %s'%limit)
        else:
            logging.info('Found %s Run entries.'%len(table))
    for i in range(len(table)):
        table[i] = table[i] + ( (calendar.timegm( table[i][1].utctimetuple() ) << 32), (calendar.timegm( table[i][2].utctimetuple() ) << 32) )
    # check if last run is ongoing
    last_start = table[len(table)-1][1]
    last_end = table[len(table)-1][2]
    if last_start==last_end:
        table[len(table)-1]=(table[len(table)-1][0],table[len(table)-1][1],'on going...',table[len(table)-1][3],'-')
    output_table(args, table, ['Run_number','Start_time','End_time','Start_IOV','End_IOV'],
    )
    return 0

def showFcsr_(args):
    connection = connect(args)
    session = connection.session()
    session = conddb.getSessionOnMasterDB( session, session )
    run_hlt_fcsr = _get_hlt_fcsr( session, 'Run' ) 
    lumi_hlt_fcsr = _convert_time( session, 'Lumi', run_hlt_fcsr )
    run_pcl_fcsr = _get_prompt_fcsr( session, 'Run' )
    lumi_pcl_fcsr = _convert_time( session, 'Lumi', run_pcl_fcsr )
    time_converter = run_to_timestamp( session )
    start, stop = time_converter.convertOne( run_pcl_fcsr )
    time_pcl_fcsr = calendar.timegm( start.utctimetuple() ) << 32
    output(args,'FCSR for HLT (last Run started +1): %s ( Lumi: %s, Time: undefined )' %(run_hlt_fcsr,lumi_hlt_fcsr))
    output(args,'FCSR for PCL (from Tier0 service) : %s ( Lumi: %s, Time: %s [%s])' %(run_pcl_fcsr,lumi_pcl_fcsr,time_pcl_fcsr,start), newLine=False)

def list_(args):
    connection = connect(args)
    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    IOV = session.get_dbtype(conddb.IOV)
    Payload = session.get_dbtype(conddb.Payload)
    GlobalTag = session.get_dbtype(conddb.GlobalTag)
    GlobalTagMap = session.get_dbtype(conddb.GlobalTagMap)
    for name in args.name:
        is_tag = _exists(session, Tag.name, name)
        if is_tag:
            if args.long:
                _output_list_object(args, session.query(Tag).get(name))

            logging.info('Listing with a limit of %s IOVs, starting from the highest since. If you need to see more, please increase the limit of returned results.', args.limit)

            time_type = session.query(Tag.time_type).\
                filter(Tag.name == name).\
                scalar()

            sinceLabel = 'Since: Run '
            if time_type == conddb.TimeType.Time.value:
                sinceLabel = 'Since: UTC          (timestamp)'
            if time_type == conddb.TimeType.Lumi.value:
                sinceLabel = '  Run  : Lumi  (rawSince)'

            output_table(args,
                session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
                    join(IOV.payload).\
                    filter(
                        IOV.tag_name == name,
                        _inserted_before(IOV,args.snapshot),
                    ).\
                    order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
                    limit(args.limit).\
                    from_self().\
                    order_by(IOV.since, IOV.insertion_time).\
                    all(),
                [sinceLabel, 'Insertion Time', 'Payload', 'Object Type'],
                filters = [_since_filter(time_type), None, None, None],
            )

        try:
            is_global_tag = _exists(session, GlobalTag.name, name)
            if is_global_tag:
                if args.long:
                    _output_list_object(args, session.query(GlobalTag).get(name))

                output_table(args,
                    session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
                        filter(GlobalTagMap.global_tag_name == name).\
                        order_by(GlobalTagMap.record, GlobalTagMap.label).\
                        all(),
                    ['Record', 'Label', 'Tag'],
                )
        except sqlalchemy.exc.OperationalError:
            sys.stderr.write("No table for GlobalTags found in DB.\n\n")


        if not is_tag and not is_global_tag:
            raise Exception('There is no tag or global tag named %s in the database.' % name)


def _diff_tags(args, session1, session2, first, second):
    Tag1 = session1.get_dbtype(conddb.Tag)
    Tag2 = session2.get_dbtype(conddb.Tag)
    IOV1 = session1.get_dbtype(conddb.IOV)
    IOV2 = session2.get_dbtype(conddb.IOV)
    tag1 = session1.query(Tag1).get(first)
    tag2 = session2.query(Tag2).get(second)

    if args.long:
        _output_diff_objects(args, tag1, tag2)

    if tag1.time_type != tag2.time_type:
        output(args, 'Skipping diff of IOVs, since the time_type is different.')
    else:
        iovs1 = dict(session1.query(IOV1.since, IOV1.payload_hash).\
            filter(
                IOV1.tag_name == first,
                _inserted_before(IOV1,args.snapshot),
            ).\
            all()
        )
        iovs2 = dict(session2.query(IOV2.since, IOV2.payload_hash).\
            filter(
                IOV2.tag_name == second,
                _inserted_before(IOV2,args.snapshot),
            ).\
            all()
        )

        table = []
        iovs = [(x, iovs1.get(x), iovs2.get(x)) for x in sorted(set(iovs1) | set(iovs2))]

        # Since 1 != 2 and both are != than any payload,
        # this will trigger printing the last line [last_since, Infinity)
        iovs.append(('Infinity', 1, 2))

        prev_since, prev_payload1, prev_payload2, prev_equal = None, None, None, None
        for since, payload1, payload2 in iovs:
            if prev_since is None:
                # First time
                prev_equal = payload1 == payload2
                prev_since = since
                prev_payload1, prev_payload2 = payload1, payload2
                continue

            # If None, the payloads are the previous one
            if payload1 is None:
                payload1 = prev_payload1
            if payload2 is None:
                payload2 = prev_payload2

            if prev_equal:
                # If the previous payloads were equal and these ones
                # were too, we do not print anything (and we do not update
                # the prev_since). If the previous were equal but these
                # are different, the equal-range has finished: we print it.
                if payload1 != payload2:
                    if not args.short:
                        table.append(('[%s, %s)' % (prev_since, since), '=', '='))
                    prev_since = since
            else:
                # If the previous payloads were not equal, we print them,
                # since we print all the different ranges (even if they are
                # contiguous). However, we skip in the case these payloads
                # and both equal to the previous ones (and we do not
                # update the prev_since). Should not be common, since
                # there is no point on having contiguous IOVs with the same
                # payloads in a tag.
                if payload1 != prev_payload1 or payload2 != prev_payload2:
                    table.append(('[%s, %s)' % (prev_since, since), _default(prev_payload1), _default(prev_payload2)))
                    prev_since = since

            prev_equal = payload1 == payload2
            prev_payload1, prev_payload2 = payload1, payload2

        output_table(args,
            table,
            ['Range', '%s Payload' % str_db_object(args.db, first), '%s Payload' % str_db_object(args.destdb, second)],
        )


def diff(args):
    _check_same_object(args)

    connection1, connection2 = connect(args)
    session1, session2 = connection1.session(), connection2.session()

    if args.second is None:
        args.second = args.first

    Tag1 = session1.get_dbtype(conddb.Tag)
    Tag2 = session2.get_dbtype(conddb.Tag)
    is_tag1 = _exists(session1, Tag1.name, args.first)
    is_tag2 = _exists(session2, Tag2.name, args.second)
    if is_tag1 and is_tag2:
        _diff_tags(args, session1, session2, args.first, args.second)

    GlobalTag1 = session1.get_dbtype(conddb.GlobalTag)
    GlobalTag2 = session2.get_dbtype(conddb.GlobalTag)
    is_global_tag1 = _exists(session1, GlobalTag1.name, args.first)
    is_global_tag2 = _exists(session2, GlobalTag2.name, args.second)
    if is_global_tag1 and is_global_tag2:
        global_tag1 = session1.query(GlobalTag1).get(args.first)
        global_tag2 = session2.query(GlobalTag2).get(args.second)

        if args.long:
            _output_diff_objects(args, global_tag1, global_tag2)

        GlobalTagMap1 = session1.get_dbtype(conddb.GlobalTagMap)
        GlobalTagMap2 = session2.get_dbtype(conddb.GlobalTagMap)
        map1 = dict([(tuple(x[:2]), x[2]) for x in session1.query(GlobalTagMap1.record, GlobalTagMap1.label, GlobalTagMap1.tag_name).\
            filter(GlobalTagMap1.global_tag_name == args.first)
        ])
        map2 = dict([(tuple(x[:2]), x[2]) for x in session2.query(GlobalTagMap2.record, GlobalTagMap2.label, GlobalTagMap2.tag_name).\
            filter(GlobalTagMap2.global_tag_name == args.second)
        ])

        records = sorted(set(map1) | set(map2))

        table = []
        diff_tags = set([])
        for record in records:
            value1 = map1.get(record)
            value2 = map2.get(record)

            if value1 is None or value2 is None or value1 != value2:
                table.append((record[0], record[1], _default(value1), _default(value2)))
                diff_tags.add((value1, value2))

        output_table(args,
            table,
            ['Record', 'Label', '%s Tag' % str_db_object(args.db, args.first), '%s Tag' % str_db_object(args.destdb, args.second)],
        )

        if args.deep:
            for tag1, tag2 in diff_tags:
                _diff_tags(args, session1, session2, tag1, tag2)

    if not (is_tag1 and is_tag2) and not (is_global_tag1 and is_global_tag2):
        raise Exception('There are no tag or global tag pairs named %s and %s in the database(s).' % (args.first, args.second))

def convertRunToTimes( session, fromRun, toRun ):

    fromTime = None
    fromLumi = None
    toTime = None
    toLumi = None
    # the time we get may be a bit delayed (7-10 sec according to Salvatore) 
    if not fromRun is None:
        if fromRun == 1:
            fromTime = 1
        else:
            conv = run_to_timestamp( session )
            startTime1, stopTime1 = conv.convertOne( fromRun )
            fromTime =  time.mktime( startTime1.timetuple() )-15.
        fromLumi = fromRun<<32|0x1
    if not toRun is None:
        if toRun == 1:
            toTime = 1
        else:
            conv = run_to_timestamp( session )
            startTime2, stopTime2 = conv.convertOne( toRun )
            toTime = time.mktime( stopTime2.timetuple() )+15.
        toLumi = toRun<<32|0x1

    timeMap = { 'from' : {
                            'hash' : None,
                            'run'  : fromRun,
                            'time' : fromTime, # the time we get may be a bit delayed (7-10 sec according to Salvatore)
                            'lumi' : fromLumi,
                          },
                'to'  : {
                            'hash' : None,
                            'run'  : toRun,
                            'time' : toTime, # the time we get may be a bit delayed (7-10 sec according to Salvatore)
                            'lumi' : toLumi,
                          } 
              }

    logging.debug("convertRunToTimes> start: %s stop %s \n    timeMap: %s " % (fromRun, toRun, str(timeMap)))

    return timeMap

def _update_tag_log(session,the_tag,the_timestamp,the_action,note):
    # run parameters
    userName = pwd.getpwuid(os.getuid()).pw_name
    hostName = socket.getfqdn()
    userCommand = " ".join(sys.argv[0:])
    TagLog = session.get_dbtype(conddb.TagLog)
    session.add(TagLog(tag_name=the_tag, event_time=the_timestamp, action=the_action, user_name=userName, host_name=hostName, command=userCommand, user_text=note ))

def _copy_payload( args, copyTime, session1, session2, payloadHash, payloadSerializationVersionMap=None ):
    Payload1 = session1.get_dbtype(conddb.Payload)
    Payload2 = session2.get_dbtype(conddb.Payload)
    ret = False
    streamerInfo = None
    if _exists(session2, Payload2.hash, payloadHash):
        logging.debug('Skipping copy of payload %s to %s since it already exists...', str_db_object(args.db, payloadHash), str_db_object(args.destdb, payloadHash))
        if payloadSerializationVersionMap is not None:
            q = session1.query(Payload1.streamer_info).filter(Payload1.hash == payloadHash).one()
            streamerInfo = q[0]
    else:
        logging.info('Copying payload %s to %s ...', str_db_object(args.db, payloadHash), str_db_object(args.destdb, payloadHash))
        q = session1.query(Payload1).filter(Payload1.hash == payloadHash).one()
        payload = _rawdict(q)
        payload['insertion_time'] = copyTime
        streamerInfo = payload['streamer_info']
        session2.add(Payload2(** payload))
        ret = True
    if payloadSerializationVersionMap is not None:
        serialization_version = serialization_metadata.get_boost_version_from_streamer_info(streamerInfo)
        payloadSerializationVersionMap[payloadHash] = serialization_version
    return ret

def _copy_tag(args, copyTime, session1, session2, first, second, fromIOV=None, toIOV=None, timeMap=None):
    ret = True
    Tag1 = session1.get_dbtype(conddb.Tag)
    Tag2 = session2.get_dbtype(conddb.Tag)
    # Copy the tag
    obj = session1.query(Tag1.name, Tag1.time_type, Tag1.object_type, Tag1.synchronization, Tag1.description, Tag1.last_validated_time, Tag1.end_of_validity, Tag1.insertion_time, Tag1.modification_time).filter(Tag1.name == first).first()
    tag = _rawdict_selection(obj)
    tag['name'] = second

    if session2._is_sqlite:
        if tag['end_of_validity'] >= maxSince:
            tag['end_of_validity'] = -1
    else:
        if tag['end_of_validity'] == -1 or tag['end_of_validity'] > maxSince :
            tag['end_of_validity'] = maxSince
    tag['insertion_time'] = copyTime
    tag['modification_time'] = copyTime

    if timeMap:
        fromIOV = timeMap['from'][ tag['time_type'].lower().strip() ]
        toIOV   = timeMap['to'] [ tag['time_type'].lower().strip() ]

    if fromIOV is None:
        fromIOV = 1

    selectStr = 'from since=%s' %fromIOV
    if toIOV is not None:
        selectStr += ' to since=%s' %toIOV
    if args.snapshot is not None:
        selectStr += ' selecting insertion time < %s' %args.snapshot

    logging.info('Copying tag %s to %s %s', str_db_object(args.db, first), str_db_object(args.destdb, second), selectStr)
    query = session2.query(Tag2.time_type, Tag2.object_type, Tag2.synchronization).filter(Tag2.name == second )
    destExists = False
    destPayloadType = None
    destTimeType = None
    destSynchro = None
    for t in query:
        destExists = True
        t = _rawdict_selection(t)
        destPayloadType = t['object_type']
        destTimeType = t['time_type']
        destSynchro = t['synchronization']
    if args.toTimestamp:
        if tag['time_type'] == 'Time':
            logging.info('Source Tag timeType=Time. Ignoring request of conversion to Timestamp')
            args.toTimestamp = False
        else:
            if not tag['time_type']=='Run':
                logging.error('Conversion from %s to Timestamp is not supported.' %tag['time_type'])
                raise Exception("Cannot execute the copy.")
    if args.toRun:
        if tag['time_type'] == 'Run':
            logging.info('Source Tag timeType=Run. Ignoring request of conversion to Run')
            args.toRun = False
        else:
            if not tag['time_type']=='Time':
                logging.error('Conversion from %s to Run is not supported.' %tag['time_type'])
                raise Exception("Cannot execute the copy.")
    if destExists:
        logging.warning('Destination tag "%s" already exists.' %second )
        if destPayloadType != tag['object_type']:
            logging.error('Cannot copy iovs from tag %s (object type: %s) to tag %s (object type: %s), since the object types are different.' %(first,tag['object_type'],second,destPayloadType))
            raise Exception('Object type mismatch, bailing out.')
        destTimeTypeOk = (destTimeType == tag['time_type'])
        if args.toTimestamp:
            if not destTimeType=='Time':
                logging.error('TimeType of target tag %s does not allow conversion to Time.' %destTimeType)
                raise Exception("Cannot execute the copy.")
            else:
                destTimeTypeOk = True
        if args.toRun:
            if not destTimeType=='Run':
                logging.error('TimeType of target tag %s does not allow conversion to Run.' %destTimeType)
                raise Exception("Cannot execute the copy.")
            else:
                destTimeTypeOk = True
        if not destTimeTypeOk:
            logging.error('Cannot copy iovs from tag %s (time type: %s) to tag %s (time type: %s), since the time types are different.' %(first,tag['time_type'],second,destTimeType))
            raise Exception('Time type mismatch, bailing out.')
        if not args.yes:
            output(args, 'Confirm the update of the existing tag "%s" in %s [n]?' %(second,args.destdb), newline=False)
            if input().lower() not in ['y', 'yes']:
                raise Exception('Aborted by the user.')
    else:
        destSynchro = 'any'
        if args.toTimestamp:
            tag['time_type'] = 'Time'
        if args.toRun:
            tag['time_type'] = 'Run'
        destTimeType = tag['time_type']
        dest = Tag2(**tag)
        dest.synchronization = destSynchro
        session2.add(dest)
        note = args.note 
        if note is None or note=='' or note==' ':
            note = '-'
        _update_tag_log(session2,second,copyTime,'New tag created.',note)

    IOV1 = session1.get_dbtype(conddb.IOV)
    IOV2 = session2.get_dbtype(conddb.IOV)
    # Get the closest smaller IOV than the given starting point (args.from),
    # since it may lie between two sinces. For the ending point (args.to)
    # is not needed, since the last IOV of a tag always goes up to infinity.
    # In the case where the starting point is before any IOV, we do not need
    # to cut the query anyway.
    prev_iov = None
    if fromIOV is not None:
        fromVal = fromIOV
        logging.debug("checking FROM %s of type %s for tag: %s "  % (fromIOV, tag['time_type'], str(tag['name'])) )
        prev_iov = session1.query(IOV1.since).\
            filter(
                IOV1.tag_name == first,
                IOV1.since <= fromVal,
                _inserted_before(IOV1,args.snapshot)
            ).\
            order_by(IOV1.since.desc()).\
            limit(1).\
            scalar()
        logging.debug('The closest smaller IOV than the given starting one (--from %s) is %s...', fromVal, prev_iov)

    # Select the input IOVs
    query = session1.query(IOV1).filter(IOV1.tag_name == first)
    if prev_iov is not None:
        query = query.filter(IOV1.since >= prev_iov)
    if toIOV is not None:
        query = query.filter(IOV1.since <= toIOV)
    query = query.filter(_inserted_before(IOV1,args.snapshot))
    iovs = {}
    hashes = set()
    payloadSerializationVersionMap = None
    if session2.is_oracle:
        payloadSerializationVersionMap = {}
    if not args.o2oTest:
        query = query.order_by(IOV1.since, IOV1.insertion_time.desc())
        for iov in query:
            iov = _rawdict(iov)

            # In the first IOV of the tag we need to use the starting point given
            # by the user, instead of the one coming from the source tag; unless
            # the starting point was before any IOV: in such case, up to the first
            # IOV there is no payload, so we use the one from the source tag.
            # Note that we need to replace it for every insertion time (since
            # the primary key is (since, insertion_time).
            if prev_iov is not None and iov['since'] == prev_iov:
                iov['since'] = fromIOV
            since = iov['since']

            if since not in iovs.keys():
            # for a given since, only the most recent will be added.
                iovs[since] = iov['payload_hash']
            else:
                logging.warning('Skipping older iov with since %s...', since)
        sourceIovSize = len(iovs)
        logging.info('Selected %s source iov(s)' %sourceIovSize)
        if not args.nosynchro:
            # synchronize lower boundary when required
            logging.info('Destination tag synchronization is %s' %destSynchro)
            policy_type = _get_synchro_policy( destSynchro )
            synchro_policy = policy_type( session1, session2, destTimeType, second )
            ret, iovs = synchro_policy.validate( iovs )
        if args.toTimestamp:
            converter = run_to_timestamp( conddb.getSessionOnMasterDB( session1, session2 ) )
            iovs = converter.convertIovs( iovs )
        if args.toRun:
            converter = timestamp_to_run( conddb.getSessionOnMasterDB( session1, session2 ) )
            iovs = converter.convertIovs( iovs )            
        # making the list of the payloads to export...         
        for since in iovs.keys():
            hashes.add( iovs[since] )

        logging.debug('%s iov(s) to copy with %s payload(s)' %(len(iovs),len(hashes)))
    else:
        maxTime = _get_maxtime_for_boost_version( session1, tag['time_type'], cond2xml.boost_version_for_this_release())
        logging.info('Max time for boost version %s is %s'%(cond2xml.boost_version_for_this_release(),maxTime))
        query = query.order_by(IOV1.since.desc(), IOV1.insertion_time.desc())
        lastIov = None
        prevIovSince = None
        targetIovSince = None
        targetIovPayload = None
        for iov in query:
            iov = _rawdict(iov)
            since = iov['since'] 
            if lastIov is None:
                lastIov = since
            else:
                if lastIov != since:
                    if targetIovSince is None:
                        targetIovSince = since
                    if since < maxTime: 
                        targetIovPayload = iov['payload_hash']
                        prevIovSince = since
                        break
        iovs[prevIovSince]=targetIovPayload
        iovs[targetIovSince]=targetIovPayload
        hashes.add(targetIovPayload)
    logfun = logging.info 
    if len(iovs)==0:
        logfun = logging.warning
    logfun('Found %s iovs and %s referenced payloads to copy.',len(iovs), len(hashes))
    # Copy the payloads referenced in the selected iovs
    np = 0
    for h in hashes:
        if _copy_payload( args, copyTime, session1, session2, h, payloadSerializationVersionMap ):
            np += 1
    if not np==0:
        logging.info('%s payload(s) copied.',np)
    # Calculate if extra iovs are needed - for the override mode ( they will have already their payloads copied )                        
    extraiovs = {}
    if args.override:
        # the interval to be overriden is defined by the new iov set boundaries, 
        # or by the actual user-provided boundaries - when available 
        l_b = sorted(iovs.keys())[0]
        h_b = sorted(iovs.keys())[-1]
        if fromIOV is not None:
            l_b = fromIOV
        if toIOV is not None:
            h_b = toIOV
        query = session2.query(IOV2).filter(IOV2.tag_name == second)
        query = query.filter(IOV2.since >= l_b).filter(IOV2.since <= h_b)
        query = query.order_by(IOV2.since, IOV2.insertion_time.desc())
        for iov in query:
            iov = _rawdict(iov)
            since = iov['since']
            if since not in extraiovs.keys() and since not in iovs.keys():
                for newSince in sorted(iovs.keys(),reverse=True):
                    if newSince < since:
                        extraiovs[since] = iovs[newSince]
                        break

    # re-assemble the 2 iov set
    if len(extraiovs):
        logging.info('Adding %s extra iovs for overriding the existing ones with the payloads from the new iovs...' %len(extraiovs))
    if args.override and len(extraiovs)==0:
        logging.info('No extra iovs required for overriding the existing ones with the new ones.')
    for k,v in extraiovs.items():
        iovs[k] = extraiovs[k]

    # Copy the set of IOVs collected        
    session2.merge(Tag2(name=second,modification_time=copyTime))
    minIov = None
    if payloadSerializationVersionMap is not None:
        BoostRunMap = session2.get_dbtype(conddb.BoostRunMap)
        q = session2.query(BoostRunMap).order_by(BoostRunMap.run_number)
        boost_run_map = []
        for r in q:
            r = _rawdict(r)
            boost_run_map.append( (r['run_number'],r['run_start_time'],r['boost_version']) )
        TagMetadata = session2.get_dbtype(conddb.TagMetadata)
        q = session2.query(TagMetadata.min_serialization_v,TagMetadata.min_since).filter(TagMetadata.tag_name == second )
        tagBoostVersion = None
        for r in q:
            tagBoostVersion = r[0]
            minIov = r[1]
            break
        currentTagBoostVersion = tagBoostVersion
        if len(iovs)>0 and destExists and currentTagBoostVersion is None:
            raise Exception('No information found about the destination tag boost version. Cannot proceed with the update.')
        logging.info('Destination tag boost version is %s' %currentTagBoostVersion )
    niovs = 0
    for k,v in iovs.items():
        logging.debug('Copying IOV %s -> %s...', k, v)
        session2.add(IOV2(tag_name=second,since=k,insertion_time=copyTime,payload_hash=v))
        niovs += 1
        if payloadSerializationVersionMap is not None:
            if v in payloadSerializationVersionMap.keys():
                tagBoostVersion = serialization_metadata.do_update_tag_boost_version(tagBoostVersion,minIov,payloadSerializationVersionMap[v], k, destTimeType, boost_run_map )
        if minIov is None or k<minIov:
            minIov = k
    if not niovs==0:
        logging.info('%s iov(s) copied.',niovs)
        merge = False
        if payloadSerializationVersionMap is not None and tagBoostVersion is not None:
            if currentTagBoostVersion is not None: 
                if currentTagBoostVersion != tagBoostVersion:
                    if serialization_metadata.cmp_boost_version( currentTagBoostVersion, tagBoostVersion )<0:
                        if destSynchro not in ['any','validation']:
                            raise Exception('Cannot update existing tag %s, since the minimum serialization version %s is not compatible with the combined boost version of the payloads to add (%s)' %(second,currentTagBoostVersion,tagBoostVersion))
            merge = True
        if merge:
            session2.merge(TagMetadata(tag_name=second,min_serialization_v=tagBoostVersion,min_since=minIov,modification_time=copyTime))
            logging.info('Destination Tag boost Version set to %s ( was %s )' %(tagBoostVersion,currentTagBoostVersion) )
    return ret, niovs

def copy(args):
    _check_same_object(args)

    connection1, connection2 = connect(args, read_only=False)
    session1, session2 = connection1.session(), connection2.session()

    args.type, args.first = _identify_object(session1, args.type, args.first)

    copyTime = datetime.datetime.utcnow()

    if args.type == 'payload':
        if args.second is None:
            args.second = args.first
        elif args.first != args.second:
            raise Exception('Cannot modify the name (hash) of a payload while copying, since the hash has to match the data.')

        if _copy_payload( args, copyTime, session1, session2, args.first ):
            _confirm_changes(args)
            session2.commit()

    elif args.type == 'tag':
        if args.second is None:
            args.second = args.first
        if args.force and args.yes:
            if args.note is None or args.note=='' or args.note==' ':
                raise Exception('Cannot run in force edit mode without to provide a non-empty editing note.')
        if args.o2oTest:
            if args.to is not None or getattr(args, 'from') is not None or args.snapshot is not None or args.override or args.nosynchro or args.toTimestamp:
                raise Exception('Cannot execute the copy for the o2o test with the options from, to, override, nosynchro, snapshot or toTimestamp.')

        try:
            ret, niovs = _copy_tag(args, copyTime, session1, session2, args.first, args.second, getattr(args, 'from'), args.to)
            if niovs!=0:
                _confirm_changes(args)
                note = args.note
                if args.force and args.note is None:
                    note = _get_user_note(args,'Force edit mode requires an editing note to be provided: ')
                if note is None or note=='' or note==' ':
                    note = '-'
                _update_tag_log(session2,args.second,copyTime,'%s iov(s) inserted.' %niovs,note)
                session2.commit()
                logging.info('Changes committed.')
        except Exception as e:
            session2.rollback()
            logging.error('Changes rolled back.')
            raise e

        return 1*( not ret )


    elif args.type == 'gt':
        if args.second is None:
            args.second = args.first

        # 'from' is a keyword!
        session = conddb.getSessionOnMasterDB( session1, session2 )
        timeMap = convertRunToTimes(session, getattr(args, 'from'), args.to)

        logging.info('Copying global tag %s to %s ...', str_db_object(args.db, args.first), str_db_object(args.destdb, args.second))

        GlobalTag1 = session1.get_dbtype(conddb.GlobalTag)
        GlobalTag2 = session2.get_dbtype(conddb.GlobalTag)
        GlobalTagMap1 = session1.get_dbtype(conddb.GlobalTagMap)
        GlobalTagMap2 = session2.get_dbtype(conddb.GlobalTagMap)
        # Prepare the copy
        global_tag = _rawdict(session1.query(GlobalTag1).get(args.first))
        global_tag['name'] = args.second
        global_tag['validity'] = 0 # XXX: SQLite does not work with long ints...
        if args.snapshot is None:
            args.snapshot = str(global_tag['snapshot_time'].strftime("%Y-%m-%d %H:%M:%S"))
        else:
            global_tag['snapshot_time'] = _parse_timestamp(args.snapshot)
        if _exists(session2, GlobalTag2.name, args.second):
            raise Exception('A GlobalTag named "%s" already exists in %s' %(args.second, args.destdb))   

        # Copy the tags of the global tag
        logging.debug('Creating query for tag %s filter %s ...', GlobalTagMap1.tag_name, args.first)
        query = session1.query(GlobalTagMap1.tag_name).filter(GlobalTagMap1.global_tag_name == args.first).distinct()
        copyTime = datetime.datetime.utcnow()
        for (tag, ) in query:
            logging.debug('Copying tag %s to %s for GT %s ...', str_db_object(args.db, tag), str_db_object(args.destdb, tag), str_db_object(args.destdb, args.second))
            Tag2 = session2.get_dbtype(conddb.Tag)
            if _exists(session2, Tag2.name, tag ):
                logging.warn('Skipping copy of tag %s to %s since it already exists... *The tags may differ in content*', str_db_object(args.db, tag), str_db_object(args.destdb, tag))
            else:
                logging.debug('going to copy tag %s to %s ... ', str_db_object(args.db, tag), str_db_object(args.destdb, tag))
                _copy_tag(args, copyTime, session1, session2, tag, tag, timeMap=timeMap)

        # Copy the global tag
        global_tag2 = GlobalTag2(**global_tag)
        copyTime = datetime.datetime.utcnow()
        global_tag2.snapshot_time = copyTime
        global_tag2.insertion_time = copyTime
        session2.add(global_tag2)
        # Copy the map of the global tag
        query = session1.query(GlobalTagMap1).filter(GlobalTagMap1.global_tag_name == args.first)
        for map_ in query:
            logging.debug('Copying global tag map %s -> %s ...', str_record(map_.record, map_.label), map_.tag_name)
            map_ = _rawdict(map_)
            map_['global_tag_name'] = args.second
            session2.add(GlobalTagMap2(**map_))

        _confirm_changes(args)
        session2.commit()
        return 0


def edit(args):

    global colors
    colors.noColors()

    connection = connect(args, read_only=False)
    session = connection.session()

    args.type, name = _identify_object(session, args.type, args.name)

    if args.editor is None:
        editor = _get_editor(args)

    with tempfile.NamedTemporaryFile(mode='r+') as tempfd:

        if args.type == 'payload':
            raise Exception('TODO')

            Payload = session1.get_dbtype(conddb.Payload)

            properties = session.query(Payload.object_type, Payload.version, Payload.insertion_time).\
                filter(Payload.hash == name).\
                one()
            columns = properties.keys()

            tempfd.write('''# Editing payload %s
#
# You can modify rows/lines after the headers. Then, save the file and
# quit the editor. The changes will be recognized and you will be asked
# for confirmation before the changes are written into the database.
#
# The order of the rows does not matter. Whitespace is not important.
# Lines starting with # are ignored.
#
# You can edit the insertion time -- however, note that if these conditions
# are to be uploaded to an official database, the times will be anyway
# replaced with the actual insertion times.

''' % payload_hash)

            table = zip(columns, properties)
            output_table(args,
                table,
                ['Property', 'Value'],
                output_file = tempfd,
            )

            _run_editor(editor, tempfd)

            new_table = []
            in_table = False
            for line in tempfd.readlines():
                line = line.strip()
                if len(line) == 0 or line.startswith('#'):
                    continue

                if not in_table:
                    if all([x == '-' for x in line.replace(' ','')]):
                        in_table = True
                    continue

                key, value = line.split(None, 1)

                if key == 'insertion_time':
                    value = _parse_timestamp(value)

                new_table.append((key, value))

            table = set(table)
            new_table = set(new_table)

            added = new_table - table
            deleted = table - new_table

            if len(added) == 0 and len(deleted) == 0:
                raise Exception('No changes found.')

            values = dict(new_table)
            if set(values.keys()) != set(columns):
                raise Exception('It is not possible to modify the name of the properties or add/remove them. Please only modify the values.')

            changes = [('+' if x in added else '-', x[0], x[1]) for x in added | deleted]
            output_table(args,
                sorted(changes, key=lambda x: (x[1], 0 if x[0] == '-' else 1)),
                ['', 'Property', 'Value'],
                no_first_header = True,
            )

            _confirm_changes(args)

            payload = session.query(Payload).\
                filter(Payload.hash == payload_hash).\
                update(dict(added | deleted))
            session.commit()


        elif args.type == 'tag':
            if args.header:
                Tag = session.get_dbtype(conddb.Tag)
                table = session.query(Tag.description,Tag.synchronization,Tag.end_of_validity).\
                    filter(Tag.name == name).\
                    all()
                table = [ (str(x[0].strip()),str(x[1]),str(x[2])) for x in table ] 

                output_table( args,
                    table,
                    ['Description','Synchronization','End of Validity'],
                    output_file = tempfd, 
                    no_max_length = True
                )

                tempfd.write('''
# Editing tag %s
#
# You can add, remove or modify rows/lines after the headers.
# Then, save the file and quit the editor.
# The changes will be recognized and you will be asked for confirmation
# before the changes are written into the database.
#
# Whitespace is not important.
# Lines starting with # are ignored.
''' % name)
                _run_editor(editor, tempfd)
                new_table = []
                editRe = re.compile(r'^(.*)\s+([a-z]+)\s+([-]?\d+)\s*$')
                for index, line in enumerate(tempfd.readlines()):
                    if index in {0, 1}:
                        continue

                    line = line.strip()

                    if len(line) == 0 or line.startswith('#'):
                        continue

                    editMatch = editRe.match(line)
                    if editMatch:
                        description,synchronization,endOfValidity = editMatch.groups()
                        if synchronization not in conddb.synch_list:
                            raise Exception('Invalid Synchronization value set: "%s"' %synchronization )
                        if int(endOfValidity)< -1:
                            raise Exception('Invalid End Of Validity set: "%s"' %endOfValidity ) 
                    else:
                        raise Exception('Each line must contain the Description, Synchronization and End Of Validity fields in the required format.')

                    new_table.append((description.strip(),synchronization,endOfValidity))

                header = table[0]
                new_header = new_table[0]

                if new_table == table:
                    logging.info('No changes found.')
                    session.rollback()
                    return

                changes = []
                changes.append(('+',new_header[0],new_header[1],new_header[2]))
                changes.append(('-',header[0],header[1],header[2]))
                output_table(args,
                             sorted(changes, key=lambda x: (0 if x[0] == '-' else 1)),
                             ['', 'Description', 'Synchronization', 'End Of Validity'],
                             no_first_header = True,
                             )

                _confirm_changes(args)
                note = '-'
                if args.force:
                    note = _get_user_note(args,'Please provide an editing note: ')
                action = ''
                if header[0] != new_header[0]:
                    action += 'Description updated'
                if header[1] != new_header[1]:
                    # validate the synchro requested( based on the tag IOV content )
                    if new_header[1] ==  'mc':
                        if not mc_validate( session, name ):
                            return
                    if len(action): action += ', '
                    action += 'Synchronization changed'
                if header[2] != new_header[2]:
                    if len(action): action += ', '
                    action += 'End Of Validity changed'
                if len(action): action += '.'

                updatedHeader = Tag(name=name,description=new_header[0],synchronization=new_header[1],end_of_validity=new_header[2],modification_time=datetime.datetime.utcnow()) 
                session.merge(updatedHeader)
                _update_tag_log(session,name,datetime.datetime.utcnow(),action,note)
                session.commit()
                logging.info('Tag header updated. Action(s): %s' %action)
                return

            IOV = session.get_dbtype(conddb.IOV)
            Payload = session.get_dbtype(conddb.Payload)
            table = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash).\
                filter(IOV.tag_name == name).\
                order_by(IOV.since, IOV.insertion_time).\
                all()

            output_table(args,
                table,
                ['Since', 'Insertion Time', 'Payload'],
                output_file = tempfd,
            )

            tempfd.write('''
# Editing tag %s
#
# You can add, remove or modify rows/lines after the headers.
# Then, save the file and quit the editor.
# The changes will be recognized and you will be asked for confirmation
# before the changes are written into the database.
#
# The order of the rows does not matter. Whitespace is not important.
# Lines starting with # are ignored.
#
# Payload hashes do not need to be full -- a prefix is enough if unique.
# The program will fill find the full hash.
#
# You can edit insertion times -- however, note that if these conditions
# are to be uploaded to an official database, the times will be anyway
# replaced with the actual insertion times. The format must be
# one of the following: '2013-01-20', '2013-01-20 10:11:12' or
# '2013-01-20 10:11:12.123123'.
# If the insertion time desired is the one of the command execution, 
# you can simply write a '-' in the corresponding column
#
# Suggestion: open another terminal to copy the payloads you need.
''' % name)

            _run_editor(editor, tempfd)

            new_table = []
            for index, line in enumerate(tempfd.readlines()):
                if index in {0, 1}:
                    continue

                line = line.strip()

                if len(line) == 0 or line.startswith('#'):
                    continue

                splitted = line.split()
                if len(splitted) == 3:
                    since, insertion_timestamp, payload = splitted
                elif len(splitted) == 4:
                    since, insertion_date, insertion_time, payload = splitted
                    insertion_timestamp = '%s %s' % (insertion_date, insertion_time)
                else:
                    raise Exception('Each line must contain the since, timestamp and payload fields in the required format.')

                # check payload hash format and existance...
                if len(payload) > conddb.hash_length:
                    raise Exception('Payload hash "%s" too long.' %payload )
                elif len(payload) < conddb.hash_length:
                    raise Exception('Payload hash "%s" too short.' %payload )
                if not _exists(session, Payload.hash, payload):
                    raise Exception('Payload hash "%s" not found in the database' %payload )

                if insertion_timestamp == '-':
                    insertion_time = datetime.datetime.utcnow()
                else:
                    insertion_time = _parse_timestamp(insertion_timestamp)
                new_table.append((int(since), insertion_time, payload))

            table = set(table)
            new_table = set(new_table)

            added = new_table - table
            deleted = table - new_table

            sizeNew = len(new_table)
            sizeUnique = len( set([(x[0],x[1]) for x in new_table]) )
            if connection.is_official:
                added = set([(x[0],'-',x[2]) for x in added])       
                sizeNew = len(added) + len(table)
                sizeUnique = len( set([x[0] for x in added]) ) + len(table)
                if len(deleted):
                    logging.info("The %s deleted entries won't be removed." %len(deleted))
                    deleted = set()

            if len(added) == 0 and len(deleted) == 0:
                logging.info('No changes found.')
                session.rollback()
                return

            changes = [('+' if x in added else '-', x[0], x[1], x[2]) for x in added | deleted]
            output_table(args,
                sorted(changes, key=lambda x: (x[1], 0 if x[0] == '-' else 1)),
                ['', 'Since', 'Insertion Time', 'Payload'],
                no_first_header = True,
            )

            logging.debug('size of modified table: %s - unique (since+timestamp) entries: %s' %(sizeNew,sizeUnique))
            if sizeNew != sizeUnique:
                raise Exception('Duplicated since.')

            _confirm_changes(args)
            note = '-'
            if args.force:
                note = _get_user_note(args,'Please provide an editing note: ')
            action = ''
            if len(added):
                action += '%s iov(s) inserted' %len(added)
            if len(deleted):
                if len(action): action += ', '
                action += '%s iov(s) deleted' %len(deleted)

            # Use session.delete() instead of bulk delete to let SQLAlchemy use UPDATE
            # (since we may disable DELETE in Oracle for the tables)
            for since, insertion_time, _ in deleted:
                session.query(IOV).filter(IOV.tag_name==name, IOV.since==since, IOV.insertion_time==insertion_time).delete()
                #session.delete(session.query(IOV).filter(IOV.tag_name==name, IOV.since==since, IOV.insertion_time==insertion_time))
            for since, insertion_time, payload in added:
                if connection.is_official:
                    insertion_time = datetime.datetime.utcnow()
                session.add(IOV(tag_name=name, since=since, insertion_time=insertion_time, payload_hash=payload))
            _update_tag_log(session,name,datetime.datetime.utcnow(),action,note)
            session.commit()

        elif args.type == 'gt':
            GlobalTagMap = session.get_dbtype(conddb.GlobalTagMap)
            table = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
                filter(GlobalTagMap.global_tag_name == name).\
                order_by(GlobalTagMap.record, GlobalTagMap.label).\
                all()

            output_table(args,
                table,
                ['Record', 'Label', 'Tag'],
                output_file = tempfd,
            )

            tempfd.write('''
# Editing global tag %s
#
# You can add, remove or modify rows/lines after the headers.
# Then, save the file and quit the editor.
# The changes will be recognized and you will be asked for confirmation
# before the changes are written into the database.
#
# To mark records without label, use a single '%s' character.
#
# The order of the rows does not matter. Whitespace is not important.
# Lines starting with # are ignored.
''' % (name, conddb.empty_label))

            _run_editor(editor, tempfd)

            new_table = []
            for index, line in enumerate(tempfd.readlines()):
                if index in {0, 1}:
                    continue

                line = line.strip()

                if len(line) == 0 or line.startswith('#'):
                    continue

                record, label, tag = line.split()

                new_table.append((record, label, tag))

            if len(new_table) != len(set([(x[0], x[1]) for x in new_table])):
                raise Exception('Duplicated (record, label) pair.')

            table = set(table)
            new_table = set(new_table)

            added = new_table - table
            deleted = table - new_table

            if len(added) == 0 and len(deleted) == 0:
                raise Exception('No changes found.')

            changes = [('+' if x in added else '-', x[0], x[1], x[2]) for x in added | deleted]
            output_table(args,
                sorted(changes, key=lambda x: (x[1], 0 if x[0] == '-' else 1)),
                ['', 'Record', 'Label', 'Tag'],
                no_first_header = True,
            )

            _confirm_changes(args)

            # Use session.delete() instead of bulk delete to let SQLAlchemy use UPDATE
            # (since we may disable DELETE in Oracle for the tables)
            for record, label, _ in deleted:
                session.delete(session.query(GlobalTagMap).get((name, record, label)))
            for record, label, tag in added:
                session.add(GlobalTagMap(global_tag_name=name, record=record, label=label, tag_name=tag))
            session.commit()


def delete(args):
    connection = connect(args, read_only=False)
    session = connection.session()

    args.type, name = _identify_object(session, args.type, args.name)

    if args.type == 'payload':
        output_table(args,
            [('-', name, )],
            ['', 'Payload'],
            no_first_header = True,
        )

        _confirm_changes(args)


        Payload = session.get_dbtype(conddb.Payload)
        session.query(Payload).\
            filter(Payload.hash == name).\
            delete()
        session.commit()

    elif args.type == 'tag':
        output_table(args,
            [('-', name, )],
            ['', 'Tag'],
            no_first_header = True,
        )

        _confirm_changes(args)

        Tag = session.get_dbtype(conddb.Tag)
        IOV = session.get_dbtype(conddb.IOV)
        TagLog = session.get_dbtype(conddb.TagLog)
        TagMetadata = session.get_dbtype(conddb.TagMetadata)
        session.query(IOV).\
            filter(IOV.tag_name == name).\
            delete()
        session.query(TagLog).\
            filter(TagLog.tag_name == name).\
            delete()
        session.query(TagMetadata).\
            filter(TagMetadata.tag_name == name).\
            delete()
        session.query(Tag).\
            filter(Tag.name == name).\
            delete()
        session.commit()

    elif args.type == 'gt':
        output_table(args,
            [('-', name, )],
            ['', 'Global Tag'],
            no_first_header = True,
        )

        _confirm_changes(args)

        GlobalTag = session.get_dbtype(conddb.GlobalTag)
        GlobalTagMap = session.get_dbtype(conddb.GlobalTagMap)
        session.query(GlobalTagMap).\
            filter(GlobalTagMap.global_tag_name == name).\
            delete()
        session.query(GlobalTag).\
            filter(GlobalTag.name == name).\
            delete()
        session.commit()


def dump(args):
    connection = connect(args)
    session = connection.session()
    IOV = session.get_dbtype(conddb.IOV)
    GlobalTag = session.get_dbtype(conddb.GlobalTag)
    GlobalTagMap = session.get_dbtype(conddb.GlobalTagMap)

    args.type, name = _identify_object(session, args.type, args.name)

    xmlProcessor = None
    if args.format == 'xml':
        xmlProcessor = cond2xml.CondXmlProcessor(conddb)

    if args.destfile != None: #The file for XML dump will be cleaned up and automatically closed after this
        with open(args.destfile, 'w'):
            pass 

    if args.type == 'payload':
        if args.format == 'xml':
            xmlProcessor.payload2xml(session, name, args.destfile)
        else:
            _dump_payload(session, name, args.loadonly)

    elif args.type == 'tag':
        for payload, in session.query(IOV.payload_hash).\
            filter(IOV.tag_name == name).\
            distinct():
            if args.format == 'xml':
                xmlProcessor.payload2xml(session, payload, args.destfile)
            else:
                _dump_payload(session, payload, args.loadonly)

    elif args.type == 'gt' and _exists(session, GlobalTag.name, name) != None:
        for payload, in session.query(IOV.payload_hash).\
            filter(GlobalTagMap.global_tag_name == name, IOV.tag_name == GlobalTagMap.tag_name).\
            distinct():
            if args.format == 'xml':
                xmlProcessor.payload2xml(session, payload, args.destfile)
            else:
                _dump_payload(session, payload, args.loadonly)

    if xmlProcessor: del xmlProcessor

def toLumi_(args):
    lumiTime = conddb_time.to_lumi_time( int(args.run), int(args.lumi_id) )
    if args.quiet:
        print('%s' %lumiTime)
    else:
        output(args,'Lumi timetype for run=%s, lumisection id=%s: %s' %(args.run,args.lumi_id, lumiTime))

def fromLumi_(args):
    run, lumisection_id = conddb_time.from_lumi_time( int(args.lumiTime) )
    if args.quiet:
        print('%s,%s' %(run, lumisection_id))
    else:
        output(args,'For Lumi timetype %s: run=%s, lumisection id=%s' %(args.lumiTime,run,lumisection_id))


def toTimestamp_( args ):
    ts = conddb_time.string_to_timestamp( args.datetime )
    if args.quiet:
        print('%s' %ts)
    else:
        output(args,"Time timetype for string '%s': %s" %(args.datetime, ts))

def fromTimestamp_( args ):
    sdt = conddb_time.string_from_timestamp( int(args.timestamp) )
    if args.quiet:
        print('%s' %sdt)
    else:
        output(args,"String date time for timestamp %s: '%s'" %(args.timestamp, sdt))

def showProtections( args ):
    connection = connect(args, False, True, True)
    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    result = session.query(Tag.name, Tag.protection_code).filter(Tag.name==args.tag).all()
    get_authorizations = False
    table = []
    for res in result:
        protection_fl = '-'
        protection_str = 'not protected'
        if res[1]!=tag_db_no_protection_code:
            get_authorizations = True
            write_flag = res[1] & tag_db_write_access_code
            lock_flag = res[1] & tag_db_lock_access_code
            protection_fl = ''
            protection_str = ''
            if write_flag != 0:
                protection_fl += db_access_code_map[tag_db_write_access_code]
                protection_str += 'write protected'
            if lock_flag != 0:
                protection_fl += db_access_code_map[tag_db_lock_access_code]
                if protection_str != '':
                    protection_str += ','
                protection_str += 'locked'
        table.append((args.tag,protection_fl,protection_str))
        break
    if len(table)==0:
        logging.error('Tag %s does not exists.'%args.tag)
        return 1
    output_table(args,table,['Tag','Flags','Protection'])
    if get_authorizations:
        TagAuthorization = session.get_dbtype(conddb.TagAuthorization)
        results = session.query(TagAuthorization.access_type, TagAuthorization.credential, TagAuthorization.credential_type).filter(TagAuthorization.tag_name==args.tag).all()
        table = []
        for r in results:
            table.append((db_access_code_map[r[0]],db_credential_type_map[r[2]][0],r[1]))
        output_table(args,table,['Access Type','Credential Type','Credential'])
    return 0

def setProtection( args ):
    if not args.remove and args.accesstype == db_access_code_map[tag_db_lock_access_code]:
        logging.error("Lock can't be set by command line tool action.")
        return 2        
    connection = connect(args, False, True, True)
    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    result = session.query(Tag.name, Tag.protection_code).filter(Tag.name==args.tag).all()
    full_protection_code = None
    for res in result:
        full_protection_code = res[1]
    if full_protection_code is None:
        logging.error('Tag %s does not exists.'%args.tag)
        return 1
    input_access_code = None
    for k in db_access_code_map.keys():
        if db_access_code_map[k] == args.accesstype:
            input_access_code = k
    new_protection_code = 0
    action = 'Access restriction altered.'
    note = ''
    if args.remove:
        TagAuthorization = session.get_dbtype(conddb.TagAuthorization)
        query = session.query(TagAuthorization).filter(TagAuthorization.tag_name == args.tag)
        query = query.filter(TagAuthorization.access_type == input_access_code)
        for code in db_access_code_map.keys():
            if code != input_access_code:
                new_protection_code |= (full_protection_code & code)
        note = '%s restrictions removed'%args.accesstype
        query = query.delete()
    else:
        new_protection_code = full_protection_code | input_access_code
        note = '%s restriction set'%args.accesstype
    session.merge(Tag(name=args.tag,protection_code=new_protection_code))
    _update_tag_log(session,args.tag,datetime.datetime.utcnow(),action,note)
    session.commit()
    logging.info(note)
    logging.info('Tag header updated. Action(s): %s' %action)
    return 0

def setPermission( args ):
    if args.credential is not None and args.credentialtype is None:
        logging.error('Specified option "credential" requires option "credentialtype')
        return 1
    if args.accesstype == db_access_code_map[tag_db_lock_access_code]:
        logging.error("Lock ownership can't be altered.")
        return 1        
    connection = connect(args, False, True, True)
    session = connection.session()
    Tag = session.get_dbtype(conddb.Tag)
    result = session.query(Tag.name, Tag.protection_code).filter(Tag.name==args.tag).all()
    full_protection_code = None
    for res in result:
        full_protection_code = res[1]
    if full_protection_code is None:
        logging.error('Tag %s does not exists.'%args.tag)
        return 2
    if full_protection_code == 0:
        logging.error('Tag %s is not protected.' %args.tag)
        return 3
    TagAuthorization = session.get_dbtype(conddb.TagAuthorization)
    input_access_code = None
    for k in db_access_code_map.keys():
        if db_access_code_map[k] == args.accesstype:
            input_access_code = k
    if full_protection_code & input_access_code==0:
        logging.error('Tag %s is not protected for access %s.'%(args.tag,args.accessType))
        return 3
    input_credential = None
    input_credential_code = None
    input_credential_type = None
    if args.credential is not None:
        input_credential = args.credential
        for k in db_credential_type_map.keys():
            if db_credential_type_map[k][1] == args.credentialtype:
                input_credential_code = k
                input_credential_type = db_credential_type_map[k][0]
    action = 'Access permission altered.'
    note = ''
    if args.remove:
        query = session.query(TagAuthorization).filter(TagAuthorization.tag_name == args.tag)
        query = query.filter(TagAuthorization.access_type == input_access_code)
        if input_credential is not None:
            query = query.filter(TagAuthorization.credential == input_credential)
            query = query.filter(TagAuthorization.credential_type == input_credential_code)
            note = '%s permission for %s "%s" removed'%(args.accesstype,input_credential_type,args.credential)
        else:
            note = '%s restrictions removed'%args.accesstype
        query = query.delete()
    else:
        if input_credential is not None:
            session.add(TagAuthorization(tag_name=args.tag, access_type=input_access_code, 
                                         credential=input_credential, credential_type=input_credential_code ))
            note = '%s permission for %s "%s" added'%(args.accesstype,input_credential_type,args.credential)
        else:
            note = '%s restriction set'%args.accesstype
    _update_tag_log(session,args.tag,datetime.datetime.utcnow(),action,note)
    session.commit()
    logging.info(note)
    logging.info('Tag access permissions updated. Action(s): %s' %action)
    return 0

def query_object(args):
    connection = connect(args)
    session = connection.session()
    if not args.tag and not args.global_tag and not args.payload:
        logging.error('The object type for the query has not been specified.')
        return -1
    if (args.tag and args.global_tag) or (args.tag and args.payload) or (args.global_tag and args.payload):
        logging.error('Only one object type for the query can be specified.')
        return -1
    found = 1
    if args.tag:
        Tag = session.get_dbtype(conddb.Tag)
        query = session.query(Tag.description,Tag.time_type,Tag.object_type,Tag.synchronization,Tag.insertion_time,Tag.modification_time).filter(Tag.name == args.unique_key).all()
        table = []
        for res in query:
            found = 0
            table.append((args.unique_key,res[0],res[1],res[2],res[3],res[4],res[5]))
        if found==0:
            output_table(args,table,['Tag name','Description','Time Type','Object Type','Synchronization','Insertion Time','Modification Time']) 
        else:
            logging.info('Tag %s has not been found.'%args.unique_key)
    if args.global_tag:
        GlobalTag = session.get_dbtype(conddb.GlobalTag)
        query =  session.query(GlobalTag.description,GlobalTag.release,GlobalTag.insertion_time,GlobalTag.snapshot_time).filter(GlobalTag.name == args.unique_key).all()
        table = []
        for res in query:
            found = 0
            table.append((args.unique_key,res[0],res[1],res[2],res[3]))
        if found==0:
            output_table(args,table,['Global Tag name','Description','Release','Insertion Time','Snapshot Time']) 
        else:
            logging.info('Global Tag %s has not been found.'%args.unique_key)
    if args.payload:
        Payload = session.get_dbtype(conddb.Payload)
        query =  session.query(Payload.object_type,Payload.streamer_info,Payload.insertion_time).filter(Payload.hash == args.unique_key).all()
        table = []
        header = None
        for res in query:
            found = 0
            streamer_info = res[1]
            row = (args.unique_key,res[0],)
            header = ['Payload hash','Object type']
            if streamer_info != b'0':
                payload_md = json.loads(streamer_info)
                for k in sorted(payload_md.keys()):
                    header.append(k)
                    row += (payload_md[k],)
            else:
                row += (' ',)
                header.append('Streamer Info')
            row += (res[2],)
            table.append(row)
            header.append('Insertion Time')
        if found==0:
            output_table(args,table,header) 
        else:
            logging.info('Payload %s has not been found.'%args.unique_key)
        
    return found

    
def main():
    '''Entry point.
    '''

    global colors

    if len(sys.argv) == 1:
        class Args(object):
            quiet = False
            nocolors = False
        colors = Colors(Args())
        help(Args())
        sys.exit(2)

    parser = argparse.ArgumentParser(description='CMS Condition DB command-line tool. For general help (manual page), use the help subcommand.', epilog='Contact help: %s' % conddb.contact_help)
    parser.add_argument('--db', '-d', default='pro', help='Database to run the command on. Run the help subcommand for more information: conddb help')
    parser.add_argument('--verbose', '-v', action='count', help='Verbosity level. -v prints debugging information of this tool, like tracebacks in case of errors. -vv prints, in addition, all SQL statements issued. -vvv prints, in addition, all results returned by queries.')
    parser.add_argument('--quiet', '-q', action='store_true', help='Quiet mode. Disables all standard output.')
    parser.add_argument('--yes', '-y', action='store_true', help='Acknowledged mode. Disables confirmation prompts before writes to the database.')
    parser.add_argument('--nocolors', action='store_true', help='Disable colors. This is automatically done when the output is connected to a pipe (e.g. " conddb ... | less" ).')
    parser.add_argument('--editor', '-e', default=None, help='Editor to use. Default: the content of the EDITOR environment variable.')
    parser.add_argument('--force', action='store_true', help='Force edit in official databases. Only meant for experts.')
    parser.add_argument('--noLimit', action='store_true', help='Ignore the limit setting for the subcommand. This may generate a _lot_ of output and put some load on the DB, so please use with care.')
    parser.add_argument('--authPath','-a', default=None, help='Path of the authentication .netrc file. Default: the content of the COND_AUTH_PATH environment variable, when specified.')
    parser_subparsers = parser.add_subparsers(title='Available subcommands')

    parser_help = parser_subparsers.add_parser('help', description='General help (manual page).')
    parser_help.set_defaults(func=help)

    parser_init = parser_subparsers.add_parser('init', description='Initializes a CMS Condition DB, i.e. creates tables, sequences, indexes, etc. if they do not exist.')
    parser_init.set_defaults(func=init)

    parser_status = parser_subparsers.add_parser('status', description='Shows a summary of the status of a database.')
    parser_status.add_argument('--limit', '-L', type=int, default=5, help='Limit on the number of results per type of object. The returned results are the latest N inserted into the database.')
    parser_status.set_defaults(func=status)

    parser_list = parser_subparsers.add_parser('list', description='Lists the contents of objects. For a tag, a list of IOVs. For a global tag, a mapping tag <-> record. If there is ambiguity, all are listed.')
    parser_list.add_argument('name', nargs='+', help="Name of the object. This can be a tag's name or a global tag's name. It must exactly match -- if needed, use the search command first to look for it.")
    parser_list.add_argument('--long', '-l', action='store_true', help='Long output. Lists the properties (e.g. description) of the objects as well (not only their content).')
    parser_list.add_argument('--snapshot', '-T', default=None, help="Snapshot time. If provided, the output will represent the state of the IOVs inserted into database up to the given time. The format of the string must be one of the following: '2013-01-20', '2013-01-20 10:11:12' or '2013-01-20 10:11:12.123123'.")
    parser_list.add_argument('--limit', '-L', type=int, default=500, help='Limit on the number of IOVs returned. The returned results are the latest N IOVs. Only applies when listing tags.')
    parser_list.set_defaults(func=list_)

    parser_listTags = parser_subparsers.add_parser('listTags', description='Lists all the Tags available in the DB.')
    parser_listTags.set_defaults(func=listTags_)

    parser_listParentTags = parser_subparsers.add_parser('listParentTags', description='Lists all the Tags available in the DB, containing the matched payload hash.')
    parser_listParentTags.add_argument('hash_name', help="Payload hash to match.")
    parser_listParentTags.set_defaults(func=listParentTags_)

    parser_diffGlobalTagsAtRun = parser_subparsers.add_parser('diffGlobalTagsAtRun', description='Diffs two global tags, but showing only the differences relevant for a given run number.')
    parser_diffGlobalTagsAtRun.add_argument('--last', '-L', dest='lastIOV', action='store_true', default=False, help='Diff the Global tags at the last open IOV.')
    parser_diffGlobalTagsAtRun.add_argument('--reference', '-R', dest='refGT', help="Reference Global Tag")
    parser_diffGlobalTagsAtRun.add_argument('--target', '-T', dest='tarGT', help="Target Global Tag")
    parser_diffGlobalTagsAtRun.add_argument('--run', '-r', dest='testRunNumber', help="target run to compare",default=-1)
    parser_diffGlobalTagsAtRun.add_argument('--verbose','-v',help='returns more info', dest='isVerbose',action='store_true',default=False)
    parser_diffGlobalTagsAtRun.add_argument('--match','-m',help='print only matching',dest='stringToMatch',action='store',default='')
    parser_diffGlobalTagsAtRun.set_defaults(func=diffGlobalTagsAtRun_)

    parser_listGTsForTag = parser_subparsers.add_parser('listGTsForTag', description='Lists the GTs which contain a given tag.')
    parser_listGTsForTag.add_argument('name', help="Name of the tag.")
    parser_listGTsForTag.set_defaults(func=listGTsForTag_)

    parser_listGTs = parser_subparsers.add_parser('listGTs', description='Lists the GTs available in the DB.')
    parser_listGTs.set_defaults(func=listGTs_)

    parser_listRuns = parser_subparsers.add_parser('listRuns', description='Lists all the Runs available in the DB, possibly applying the optional search criteria.')
    parser_listRuns.add_argument('--limit','-L',type=int,default=50,help='Limit on the number of Run entries returned. The returned results are the latest N Runs. Only applies when no selection is specified.')
    parser_listRuns.add_argument('--from', '-f', type=str, help='Select items from this "Time" onwards. Supported "Time" formats: Run, encoded TimeStamp, string TimeStamp (format: YYYY-mm-dd hh24:MM:SS)')
    parser_listRuns.add_argument('--to', '-t', type=str, help='Ignore items from this "Time" onwards. Supported "Time" formats: Run, encoded TimeStamp, string TimeStamp (format: YYYY-mm-dd hh24:MM:SS)')
    parser_listRuns.add_argument('--match', '-m', type=int, help='Search an exact match with this Run.')
    parser_listRuns.add_argument('--last','-l', action='store_true', help='Select the last available Run entry.')
    parser_listRuns.set_defaults(func=listRuns_)

    parser_diff = parser_subparsers.add_parser('diff', description='Compares the contents of two objects. For tags, their IOVs are compared to determine which ranges have different payloads. For global tags, their tag names are compared. Both objects must be of the same type. If there is more than one valid pair (ambiguity), all diffs are listed.')
    parser_diff.add_argument('first', help="Name of the first object (i.e. source, old). This can be a tag's name or a global tag's name. It must exactly match -- if needed, use the search command first to look for it.")
    parser_diff.add_argument('second', nargs='?', default=None, help='Name of the second object (i.e. destination, new). Ditto. Default: same as the first object (i.e. useful to compare the same object in different databases).')
    parser_diff.add_argument('--destdb', '-d', default=None, help='Database of the second object (destination database). Same values allowed as for --db. Default: same as the first database.')
    parser_diff.add_argument('--short', '-s', action='store_true', help='Short diff. In tag diffs, do not include the ranges where IOVs are equal (while they do not provide more information, they make the output readable).')
    parser_diff.add_argument('--long', '-l', action='store_true', help='Long output. Compares the properties (e.g. description) of the objects as well (not only their content).')
    parser_diff.add_argument('--deep', '-D', action='store_true', help='Deep diff. In global tag diffs, if two tag names are different for the same record, it compares the tags themselves with a tag diff (different tags are probably similar in a global tag, e.g. two versions of a tag).')
    parser_diff.add_argument('--payload', '-p', action='store_true', help='TODO: Payload diff. In a tag diff or a --deep global tag diff, for each range where a payload is different, the payloads are compared via a diff on the dump of both payloads.')
    parser_diff.add_argument('--snapshot', '-T', default=None, help="Snapshot time. If provided, the output will represent the state of the IOVs inserted into database up to the given time. The format of the string must be one of the following: '2013-01-20', '2013-01-20 10:11:12' or '2013-01-20 10:11:12.123123'.")
    parser_diff.set_defaults(func=diff)

    parser_search = parser_subparsers.add_parser('search', description='Searches various types of objects matching a case-insensitive string: tags (by name, object type and description), payloads (by SHA1 hash), global tags (by name, release and description) and records (by name, label and object type). The returned list is limited, by default, to 10 per type of object.')
    parser_search.add_argument('string', help='Search string. Case-insensitive.')
    parser_search.add_argument('--regexp', '-r', action='store_true', help='Regexp mode. The search string is a regular expression.')
    parser_search.add_argument('--limit', '-L', type=int, default=100, help='Limit on the number of results per type of object. The returned results are the latest N inserted into the database.')
    parser_search.set_defaults(func=search)

    parser_copy = parser_subparsers.add_parser('copy', description='Copies objects between databases. For tags, their dependent payloads are copied automatically if they do not exist in the destination database yet (or skipped if they already do). For global tags, their dependent tags are copied automatically if they do not exist in the destination database yet. However, if they exist, a warning is printed (TODO: do not print the warning if they do not differ).')
    parser_copy.add_argument('first', help="Name of the first object (i.e. source, old). This can be a tag's name, a global tag's name or a payload's SHA1 hexadecimal hash (or a prefix if unique). It must exactly match -- if needed, use the search command first to look for it.")
    parser_copy.add_argument('second', nargs='?', default=None, help='Name of the second object (i.e. destination, new). Ditto. Default: same as the first object. (i.e. useful to keep the name when copying an object between databases). Note that for payloads the names must be equal (since it is the SHA1 hash of the data) -- therefore, when copying payloads you should omit this parameter to take the default (same name).')
    parser_copy.add_argument('--destdb', '-d', default=None, help='Database of the second object (destination database). Same values allowed as for --db. Default: same as the first database.')
    parser_copy.add_argument('--from', '-f', type=int, help='From IOV: copy only from this IOV onwards. Only valid when copying tags.')
    parser_copy.add_argument('--to', '-t', type=int, help='To IOV: copy only up to this IOV. Only valid when copying tags.')
    parser_copy.add_argument('--type', default=None, choices=['tag', 'gt', 'payload'], help='Type of the objects. Use it if there is ambiguity (should be really rare).')
    parser_copy.add_argument('--note','-n', help='Editing note.')
    parser_copy.add_argument('--override','-o', action='store_true', help='Override the existing iovs for the interval covered by the new iovs.') 
    parser_copy.add_argument('--snapshot','-s', help="Timestamp of the snapshot to consider for the source iovs. The format of the string must be one of the following: '2013-01-20', '2013-01-20 10:11:12' or '2013-01-20 10:11:12.123123'.")
    parser_copy.add_argument('--o2oTest', action='store_true', help='Special copy for o2o test. Copy the second to last iov of the source tag, to allow to run the o2o procedure to add the last iov. It cannot be executed with the from, to, ovveride and snapshot options.')
    parser_copy.add_argument('--synchronize',action='store_true',help='No effect, since the synchronization is applied by default for tags. The option is kept for backward compatibility') 
    parser_copy.add_argument('--nosynchro',action='store_true',help='For tags, disable the synchronization of the destination iovs. No effect for other object type copy') 
    parser_copy.add_argument('--toTimestamp',action='store_true',help='For tags, triggers the conversion from run-based iovs to timestamp-based iovs. It will return an error for any combination with input tag non run-based, and existing destination tag non timestamp-based. Not supported with synchronization.') 
    parser_copy.add_argument('--toRun',action='store_true',help='For tags, triggers the conversion from timestamp-based iovs to run-based iovs. When multiple timestamped IOVs are found matching the same run, only the first is considered. IOVs with timestamp not matching any run are skipped. It will return an error for any combination with input tag non timestamp-based, and existing destination tag non run-based. Not supported with synchronization.') 
    parser_copy.set_defaults(func=copy)
    parser_edit = parser_subparsers.add_parser('edit', description='Edits an object. Opens up your $EDITOR with prefilled text about the object. There you can modify the data. Save the file and quit the editor. The modified data will be written into the database. e.g. for a tag, its attributes and the list of IOVs/payloads appears and are modifiable.')
    parser_edit.add_argument('name', help="Name of the object. This can be a tag's name (edits its attributes and its IOVs/payloads), a global tag's name (edits its attributes and its mapping records <-> tags) or a payload's SHA1 hexadecimal hash (or a prefix if unique; TODO: edits its attributes). It must exactly match -- if needed, use the search command first to look for it.")
    parser_edit.add_argument('--header', default=False, action='store_true', help='Edit the header attributes of the object.')
    parser_edit.add_argument('--type', default=None, choices=['tag', 'gt', 'payload'], help='Type of the object. Use it if there is ambiguity (should be really rare).')
    parser_edit.set_defaults(func=edit)

    parser_delete = parser_subparsers.add_parser('delete', description='Deletes an object. Fails if the object is referenced somewhere else in the database.')
    parser_delete.add_argument('name', help="Name of the object. This can be a tag's name, a global tag's name or a payload's SHA1 hexadecimal hash (or a prefix if unique). It must exactly match -- if needed, use the search command first to look for it.")
    parser_delete.add_argument('--deep', '-D', action='store_true', help='TODO: Deep delete. In tag deletes, deletes its payloads (fails if they are used in other tags). In global tag deletes, deletes its tags (fails if they are used by another global tag).')
    parser_delete.add_argument('--type', default=None, choices=['tag', 'gt', 'payload'], help='Type of the object. Use it if there is ambiguity (should be really rare).')
    parser_delete.set_defaults(func=delete)

    parser_dump = parser_subparsers.add_parser('dump', description='Dumps deserialized payloads, using the current CMSSW release.')
    parser_dump.add_argument('name', help="Name of the object. This can be a payload's SHA1 hexadecimal hash (or a prefix if unique), a tag's name (all payloads referenced in the tag will be dumped) or a global tag's name (all payloads referenced in the global tag will be dumped).")
    parser_dump.add_argument('--loadonly', action='store_true', help='Load only: Do not dump, only load the (deserialize) payload in memory -- useful for testing the load of an entire global tag with the current CMSSW release.')
    parser_dump.add_argument('--type', default=None, choices=['payload', 'tag', 'gt'], help='Type of the object. Use it if there is ambiguity (should be really rare).')
    parser_dump.add_argument('--format', default="xml", choices=['xml', 'raw'], help='Output format. Choice between XML and raw hexdump.')
    parser_dump.add_argument('--destfile','-d',default=None,help="Destination file for the dump.")
    parser_dump.set_defaults(func=dump)

    parser_showFcsr =  parser_subparsers.add_parser('showFCSR', description='Dumps the FCSR values for hlt and pcl')
    parser_showFcsr.set_defaults(func=showFcsr_)

    parser_toLumi = parser_subparsers.add_parser('toLumi', description='Generates the Lumi timetype from run number and lumisection id') 
    parser_toLumi.add_argument('run', help="Run id")
    parser_toLumi.add_argument('lumi_id', help="Lumisection id")
    parser_toLumi.set_defaults(func=toLumi_)

    parser_fromLumi = parser_subparsers.add_parser('fromLumi', description='Decodes the Lumi timetype extracting run number and lumisection id') 
    parser_fromLumi.add_argument('lumiTime', help="The Lumi timetype value")
    parser_fromLumi.set_defaults(func=fromLumi_)

    parser_toTimestamp = parser_subparsers.add_parser('toTimestamp', description='Generates the Time timetype from the date string') 
    parser_toTimestamp.add_argument('datetime', help="The string representing the date time, in the format 'Y-m-d H:M:S.f'" )
    parser_toTimestamp.set_defaults(func=toTimestamp_)

    parser_fromTimestamp = parser_subparsers.add_parser('fromTimestamp', description='Decodes the Time timetype extracting the date time string') 
    parser_fromTimestamp.add_argument('timestamp', help="The Time timetype value")
    parser_fromTimestamp.set_defaults(func=fromTimestamp_)

    parser_showProtections = parser_subparsers.add_parser('showProtections', description='Display the access restrictions and permissions for the specified tag')
    parser_showProtections.add_argument('tag', help="The tag name")
    parser_showProtections.set_defaults(func=showProtections)

    parser_setProtection = parser_subparsers.add_parser('setProtection', description='Set an access restriction for the specified tag')
    parser_setProtection.add_argument('tag', help="The tag name")
    parser_setProtection.add_argument('--accesstype','-a', choices=[db_access_code_map[tag_db_write_access_code],db_access_code_map[tag_db_lock_access_code]], required=True, help='The access type of the protection (flag)')
    parser_setProtection.add_argument('--remove','-r',action='store_true', help='Remove the specified protection')
    parser_setProtection.set_defaults(func=setProtection)

    parser_setPermission = parser_subparsers.add_parser('setPermission', description='Set an access permission for the specified tag')
    parser_setPermission.add_argument('tag', help="The tag name")
    parser_setPermission.add_argument('--accesstype','-a', choices=[db_access_code_map[tag_db_write_access_code],db_access_code_map[tag_db_lock_access_code]], required=True, help='The access type of the permission (flag)')
    parser_setPermission.add_argument('--credential','-c', help='The credential entitled with the permission')
    parser_setPermission.add_argument('--credentialtype','-t', choices=[db_credential_type_map[db_key_credential_type_code][1],db_credential_type_map[db_cmsuser_credential_type_code][1]],help='The type of the credential provided. Required for "credential" option')
    parser_setPermission.add_argument('--remove','-r',action='store_true', help='Remove the specified permission')
    parser_setPermission.set_defaults(func=setPermission)

    parser_query = parser_subparsers.add_parser('query', description='Query the database to get information about condition metadata')
    parser_query.add_argument('unique_key', help="The unique key for the query")
    parser_query.add_argument('--tag','-t',action='store_true', help="Query a tag object")
    parser_query.add_argument('--global_tag','-gt',action='store_true', help="Query a global tag object")
    parser_query.add_argument('--payload','-p',action='store_true', help="Query a payload object")
    parser_query.set_defaults(func=query_object)

    args = parser.parse_args()
    logging.basicConfig(
        format = '[%(asctime)s] %(levelname)s: %(message)s',
        level = logging.DEBUG if args.verbose is not None and args.verbose >= 1 else logging.INFO,
    )

    colors = Colors(args)

    if args.noLimit:
        args.limit = None
        logging.info("noLimit specified, setting limit to %s" % str(args.limit))

    if args.verbose is not None and args.verbose >= 1:
        # Include the traceback
        return args.func(args)
    else:
        # Only one error line
        try:
            sys.exit(args.func(args))
        except Exception as e:
            logging.error(e)
            sys.exit(1)


if __name__ == '__main__':
    main()

