Run a query on a Cassandra cluster and return a dictionary.

    :param query:          The query to execute.
    :type  query:          str
    :param contact_points: The Cassandra cluster addresses, can either be a string or a list of IPs.
    :type  contact_points: str | list[str]
    :param cql_user:       The Cassandra user if authentication is turned on.
    :type  cql_user:       str
    :param cql_pass:       The Cassandra user password if authentication is turned on.
    :type  cql_pass:       str
    :param port:           The Cassandra cluster port, defaults to None.
    :type  port:           int
    :param params:         The parameters for the query, optional.
    :type  params:         str
    :return:               A dictionary from the return values of the query
    :rtype:                list[dict]

    CLI Example:

    .. code-block:: bash

         salt 'cassandra-server' cassandra_cql.cql_query "SELECT * FROM users_by_name WHERE first_name = 'jane'"

def cql_query(query, contact_points=None, port=None, cql_user=None, cql_pass=None):
    '''
    Run a query on a Cassandra cluster and return a dictionary.

    :param query:          The query to execute.
    :type  query:          str
    :param contact_points: The Cassandra cluster addresses, can either be a string or a list of IPs.
    :type  contact_points: str | list[str]
    :param cql_user:       The Cassandra user if authentication is turned on.
    :type  cql_user:       str
    :param cql_pass:       The Cassandra user password if authentication is turned on.
    :type  cql_pass:       str
    :param port:           The Cassandra cluster port, defaults to None.
    :type  port:           int
    :param params:         The parameters for the query, optional.
    :type  params:         str
    :return:               A dictionary from the return values of the query
    :rtype:                list[dict]

    CLI Example:

    .. code-block:: bash

         salt 'cassandra-server' cassandra_cql.cql_query "SELECT * FROM users_by_name WHERE first_name = 'jane'"
    '''
    try:
        cluster, session = _connect(contact_points=contact_points, port=port, cql_user=cql_user, cql_pass=cql_pass)
    except CommandExecutionError:
        log.critical('Could not get Cassandra cluster session.')
        raise
    except BaseException as e:
        log.critical('Unexpected error while getting Cassandra cluster session: %s', e)
        raise

    session.row_factory = dict_factory
    ret = []

    # Cassandra changed their internal schema from v2 to v3
    # If the query contains a dictionary sorted by versions
    # Find the query for the current cluster version.
    # https://issues.apache.org/jira/browse/CASSANDRA-6717
    if isinstance(query, dict):
        cluster_version = version(contact_points=contact_points,
                                  port=port,
                                  cql_user=cql_user,
                                  cql_pass=cql_pass)
        match = re.match(r'^(\d+)\.(\d+)(?:\.(\d+))?', cluster_version)
        major, minor, point = match.groups()
        # try to find the specific version in the query dictionary
        # then try the major version
        # otherwise default to the highest version number
        try:
            query = query[cluster_version]
        except KeyError:
            query = query.get(major, max(query))
        log.debug('New query is: %s', query)

    try:
        results = session.execute(query)
    except BaseException as e:
        log.error('Failed to execute query: %s\n reason: %s', query, e)
        msg = "ERROR: Cassandra query failed: {0} reason: {1}".format(query, e)
        raise CommandExecutionError(msg)

    if results:
        for result in results:
            values = {}
            for key, value in six.iteritems(result):
                # Salt won't return dictionaries with odd types like uuid.UUID
                if not isinstance(value, six.text_type):
                    # Must support Cassandra collection types.
                    # Namely, Cassandras set, list, and map collections.
                    if not isinstance(value, (set, list, dict)):
                        value = six.text_type(value)
                values[key] = value
            ret.append(values)

    return ret