From commits-return-205358-archive-asf-public=cust-asf.ponee.io@cassandra.apache.org Mon Jan 15 15:07:43 2018 Return-Path: X-Original-To: archive-asf-public@eu.ponee.io Delivered-To: archive-asf-public@eu.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by mx-eu-01.ponee.io (Postfix) with ESMTP id D4D0E18066D for ; Mon, 15 Jan 2018 15:07:43 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id C54CC160C25; Mon, 15 Jan 2018 14:07:43 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 73DEC160C31 for ; Mon, 15 Jan 2018 15:07:41 +0100 (CET) Received: (qmail 95857 invoked by uid 500); 15 Jan 2018 14:07:37 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 95554 invoked by uid 99); 15 Jan 2018 14:07:37 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 15 Jan 2018 14:07:36 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id B0652E38BA; Mon, 15 Jan 2018 14:07:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jasobrown@apache.org To: commits@cassandra.apache.org Date: Mon, 15 Jan 2018 14:07:47 -0000 Message-Id: In-Reply-To: <87fc9862066a4c84aa4e915a52bbd97d@git.apache.org> References: <87fc9862066a4c84aa4e915a52bbd97d@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [13/23] cassandra git commit: Merge branch 'cassandra-2.1' into cassandra-2.2 http://git-wip-us.apache.org/repos/asf/cassandra/blob/503aec74/bin/cqlsh.py ---------------------------------------------------------------------- diff --cc bin/cqlsh.py index 1f63826,0000000..e242d42 mode 100644,000000..100644 --- a/bin/cqlsh.py +++ b/bin/cqlsh.py @@@ -1,2588 -1,0 +1,2592 @@@ +#!/bin/sh +# -*- mode: Python -*- + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""":" +# bash code here; finds a suitable python interpreter and execs this file. +# prefer unqualified "python" if suitable: +python -c 'import sys; sys.exit(not (0x020700b0 < sys.hexversion < 0x03000000))' 2>/dev/null \ + && exec python "$0" "$@" +for pyver in 2.7; do + which python$pyver > /dev/null 2>&1 && exec python$pyver "$0" "$@" +done +echo "No appropriate python interpreter found." >&2 +exit 1 +":""" + +from __future__ import with_statement + +import cmd +import codecs +import ConfigParser +import csv +import getpass +import optparse +import os +import platform +import sys +import traceback +import warnings +import webbrowser +from StringIO import StringIO +from contextlib import contextmanager +from glob import glob +from uuid import UUID + +if sys.version_info[0] != 2 or sys.version_info[1] != 7: + sys.exit("\nCQL Shell supports only Python 2.7\n") + +UTF8 = 'utf-8' +CP65001 = 'cp65001' # Win utf-8 variant + +description = "CQL Shell for Apache Cassandra" +version = "5.0.1" + +readline = None +try: + # check if tty first, cause readline doesn't check, and only cares + # about $TERM. we don't want the funky escape code stuff to be + # output if not a tty. + if sys.stdin.isatty(): + import readline +except ImportError: + pass + +CQL_LIB_PREFIX = 'cassandra-driver-internal-only-' + +CASSANDRA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..') +CASSANDRA_CQL_HTML_FALLBACK = 'https://cassandra.apache.org/doc/cql3/CQL-2.2.html' + +if os.path.exists(CASSANDRA_PATH + '/doc/cql3/CQL.html'): + # default location of local CQL.html + CASSANDRA_CQL_HTML = 'file://' + CASSANDRA_PATH + '/doc/cql3/CQL.html' +elif os.path.exists('/usr/share/doc/cassandra/CQL.html'): + # fallback to package file + CASSANDRA_CQL_HTML = 'file:///usr/share/doc/cassandra/CQL.html' +else: + # fallback to online version + CASSANDRA_CQL_HTML = CASSANDRA_CQL_HTML_FALLBACK + +# On Linux, the Python webbrowser module uses the 'xdg-open' executable +# to open a file/URL. But that only works, if the current session has been +# opened from _within_ a desktop environment. I.e. 'xdg-open' will fail, +# if the session's been opened via ssh to a remote box. +# +# Use 'python' to get some information about the detected browsers. +# >>> import webbrowser +# >>> webbrowser._tryorder +# >>> webbrowser._browser +# +if len(webbrowser._tryorder) == 0: + CASSANDRA_CQL_HTML = CASSANDRA_CQL_HTML_FALLBACK +elif webbrowser._tryorder[0] == 'xdg-open' and os.environ.get('XDG_DATA_DIRS', '') == '': + # only on Linux (some OS with xdg-open) + webbrowser._tryorder.remove('xdg-open') + webbrowser._tryorder.append('xdg-open') + +# use bundled libs for python-cql and thrift, if available. if there +# is a ../lib dir, use bundled libs there preferentially. +ZIPLIB_DIRS = [os.path.join(CASSANDRA_PATH, 'lib')] +myplatform = platform.system() +is_win = myplatform == 'Windows' + +# Workaround for supporting CP65001 encoding on python < 3.3 (https://bugs.python.org/issue13216) +if is_win and sys.version_info < (3, 3): + codecs.register(lambda name: codecs.lookup(UTF8) if name == CP65001 else None) + +if myplatform == 'Linux': + ZIPLIB_DIRS.append('/usr/share/cassandra/lib') + +if os.environ.get('CQLSH_NO_BUNDLED', ''): + ZIPLIB_DIRS = () + + +def find_zip(libprefix): + for ziplibdir in ZIPLIB_DIRS: + zips = glob(os.path.join(ziplibdir, libprefix + '*.zip')) + if zips: + return max(zips) # probably the highest version, if multiple + ++ +cql_zip = find_zip(CQL_LIB_PREFIX) +if cql_zip: + ver = os.path.splitext(os.path.basename(cql_zip))[0][len(CQL_LIB_PREFIX):] + sys.path.insert(0, os.path.join(cql_zip, 'cassandra-driver-' + ver)) + +third_parties = ('futures-', 'six-') + +for lib in third_parties: + lib_zip = find_zip(lib) + if lib_zip: + sys.path.insert(0, lib_zip) + +warnings.filterwarnings("ignore", r".*blist.*") +try: + import cassandra +except ImportError, e: + sys.exit("\nPython Cassandra driver not installed, or not on PYTHONPATH.\n" + 'You might try "pip install cassandra-driver".\n\n' + 'Python: %s\n' + 'Module load path: %r\n\n' + 'Error: %s\n' % (sys.executable, sys.path, e)) + +from cassandra.auth import PlainTextAuthProvider +from cassandra.cluster import Cluster +from cassandra.metadata import (ColumnMetadata, KeyspaceMetadata, + TableMetadata, protect_name, protect_names) +from cassandra.policies import WhiteListRoundRobinPolicy +from cassandra.query import SimpleStatement, ordered_dict_factory, TraceUnavailable + +# cqlsh should run correctly when run out of a Cassandra source tree, +# out of an unpacked Cassandra tarball, and after a proper package install. +cqlshlibdir = os.path.join(CASSANDRA_PATH, 'pylib') +if os.path.isdir(cqlshlibdir): + sys.path.insert(0, cqlshlibdir) + +from cqlshlib import cql3handling, cqlhandling, pylexotron, sslhandling +from cqlshlib.copyutil import ExportTask, ImportTask +from cqlshlib.displaying import (ANSI_RESET, BLUE, COLUMN_NAME_COLORS, CYAN, + RED, FormattedValue, colorme) +from cqlshlib.formatting import (DEFAULT_DATE_FORMAT, DEFAULT_NANOTIME_FORMAT, + DEFAULT_TIMESTAMP_FORMAT, DateTimeFormat, + format_by_type, format_value_utype, + formatter_for) +from cqlshlib.tracing import print_trace, print_trace_session +from cqlshlib.util import get_file_encoding_bomsize, trim_if_present + +DEFAULT_HOST = '127.0.0.1' +DEFAULT_PORT = 9042 +DEFAULT_CQLVER = '3.3.1' +DEFAULT_PROTOCOL_VERSION = 4 +DEFAULT_CONNECT_TIMEOUT_SECONDS = 5 +DEFAULT_REQUEST_TIMEOUT_SECONDS = 10 + +DEFAULT_FLOAT_PRECISION = 5 +DEFAULT_MAX_TRACE_WAIT = 10 + +if readline is not None and readline.__doc__ is not None and 'libedit' in readline.__doc__: + DEFAULT_COMPLETEKEY = '\t' +else: + DEFAULT_COMPLETEKEY = 'tab' + +cqldocs = None +cqlruleset = None + +epilog = """Connects to %(DEFAULT_HOST)s:%(DEFAULT_PORT)d by default. These +defaults can be changed by setting $CQLSH_HOST and/or $CQLSH_PORT. When a +host (and optional port number) are given on the command line, they take +precedence over any defaults.""" % globals() + +parser = optparse.OptionParser(description=description, epilog=epilog, + usage="Usage: %prog [options] [host [port]]", + version='cqlsh ' + version) +parser.add_option("-C", "--color", action='store_true', dest='color', + help='Always use color output') +parser.add_option("--no-color", action='store_false', dest='color', + help='Never use color output') +parser.add_option("--browser", dest='browser', help="""The browser to use to display CQL help, where BROWSER can be: + - one of the supported browsers in https://docs.python.org/2/library/webbrowser.html. + - browser path followed by %s, example: /usr/bin/google-chrome-stable %s""") +parser.add_option('--ssl', action='store_true', help='Use SSL', default=False) +parser.add_option("-u", "--username", help="Authenticate as user.") +parser.add_option("-p", "--password", help="Authenticate using password.") +parser.add_option('-k', '--keyspace', help='Authenticate to the given keyspace.') +parser.add_option("-f", "--file", help="Execute commands from FILE, then exit") +parser.add_option('--debug', action='store_true', + help='Show additional debugging information') +parser.add_option("--encoding", help="Specify a non-default encoding for output." + + " (Default: %s)" % (UTF8,)) +parser.add_option("--cqlshrc", help="Specify an alternative cqlshrc file location.") +parser.add_option('--cqlversion', default=DEFAULT_CQLVER, + help='Specify a particular CQL version (default: %default).' + ' Examples: "3.0.3", "3.1.0"') +parser.add_option("-e", "--execute", help='Execute the statement and quit.') +parser.add_option("--connect-timeout", default=DEFAULT_CONNECT_TIMEOUT_SECONDS, dest='connect_timeout', + help='Specify the connection timeout in seconds (default: %default seconds).') +parser.add_option("--request-timeout", default=DEFAULT_REQUEST_TIMEOUT_SECONDS, dest='request_timeout', + help='Specify the default request timeout in seconds (default: %default seconds).') +parser.add_option("-t", "--tty", action='store_true', dest='tty', + help='Force tty mode (command prompt).') + +optvalues = optparse.Values() +(options, arguments) = parser.parse_args(sys.argv[1:], values=optvalues) + +# BEGIN history/config definition +HISTORY_DIR = os.path.expanduser(os.path.join('~', '.cassandra')) + +if hasattr(options, 'cqlshrc'): + CONFIG_FILE = options.cqlshrc + if not os.path.exists(CONFIG_FILE): + print '\nWarning: Specified cqlshrc location `%s` does not exist. Using `%s` instead.\n' % (CONFIG_FILE, HISTORY_DIR) + CONFIG_FILE = os.path.join(HISTORY_DIR, 'cqlshrc') +else: + CONFIG_FILE = os.path.join(HISTORY_DIR, 'cqlshrc') + +HISTORY = os.path.join(HISTORY_DIR, 'cqlsh_history') +if not os.path.exists(HISTORY_DIR): + try: + os.mkdir(HISTORY_DIR) + except OSError: + print '\nWarning: Cannot create directory at `%s`. Command history will not be saved.\n' % HISTORY_DIR + +OLD_CONFIG_FILE = os.path.expanduser(os.path.join('~', '.cqlshrc')) +if os.path.exists(OLD_CONFIG_FILE): + if os.path.exists(CONFIG_FILE): + print '\nWarning: cqlshrc config files were found at both the old location (%s) and \ + the new location (%s), the old config file will not be migrated to the new \ + location, and the new location will be used for now. You should manually \ + consolidate the config files at the new location and remove the old file.' \ + % (OLD_CONFIG_FILE, CONFIG_FILE) + else: + os.rename(OLD_CONFIG_FILE, CONFIG_FILE) +OLD_HISTORY = os.path.expanduser(os.path.join('~', '.cqlsh_history')) +if os.path.exists(OLD_HISTORY): + os.rename(OLD_HISTORY, HISTORY) +# END history/config definition + +CQL_ERRORS = ( + cassandra.AlreadyExists, cassandra.AuthenticationFailed, cassandra.InvalidRequest, + cassandra.Timeout, cassandra.Unauthorized, cassandra.OperationTimedOut, + cassandra.cluster.NoHostAvailable, + cassandra.connection.ConnectionBusy, cassandra.connection.ProtocolError, cassandra.connection.ConnectionException, + cassandra.protocol.ErrorMessage, cassandra.protocol.InternalError, cassandra.query.TraceUnavailable +) + +debug_completion = bool(os.environ.get('CQLSH_DEBUG_COMPLETION', '') == 'YES') + +# we want the cql parser to understand our cqlsh-specific commands too +my_commands_ending_with_newline = ( + 'help', + '?', + 'consistency', + 'serial', + 'describe', + 'desc', + 'show', + 'source', + 'capture', + 'login', + 'debug', + 'tracing', + 'expand', + 'paging', + 'exit', + 'quit', + 'clear', + 'cls' +) + + +cqlsh_syntax_completers = [] + + +def cqlsh_syntax_completer(rulename, termname): + def registrator(f): + cqlsh_syntax_completers.append((rulename, termname, f)) + return f + return registrator + + +cqlsh_extra_syntax_rules = r''' + ::= + | ( ";" | "\n" ) + ; + + ::= + | + | + | + | + | + | + | + | + | + | + | + | + | + | + ; + + ::= ( "DESCRIBE" | "DESC" ) + ( "FUNCTIONS" + | "FUNCTION" udf= + | "AGGREGATES" + | "AGGREGATE" uda= + | "KEYSPACES" + | "KEYSPACE" ksname=? + | ( "COLUMNFAMILY" | "TABLE" ) cf= + | "INDEX" idx= + | ( "COLUMNFAMILIES" | "TABLES" ) + | "FULL"? "SCHEMA" + | "CLUSTER" + | "TYPES" + | "TYPE" ut= + | (ksname= | cf= | idx=)) + ; + + ::= "CONSISTENCY" ( level= )? + ; + + ::= "ANY" + | "ONE" + | "TWO" + | "THREE" + | "QUORUM" + | "ALL" + | "LOCAL_QUORUM" + | "EACH_QUORUM" + | "SERIAL" + | "LOCAL_SERIAL" + | "LOCAL_ONE" + ; + + ::= "SERIAL" "CONSISTENCY" ( level= )? + ; + + ::= "SERIAL" + | "LOCAL_SERIAL" + ; + + ::= "SHOW" what=( "VERSION" | "HOST" | "SESSION" sessionid= ) + ; + + ::= "SOURCE" fname= + ; + + ::= "CAPTURE" ( fname=( | "OFF" ) )? + ; + + ::= "COPY" cf= + ( "(" [colnames]= ( "," [colnames]= )* ")" )? + ( dir="FROM" ( fname= | "STDIN" ) + | dir="TO" ( fname= | "STDOUT" ) ) + ( "WITH" ( "AND" )* )? + ; + + ::= [optnames]=(|) "=" [optvals]= + ; + + ::= + | + | + ; + +# avoiding just "DEBUG" so that this rule doesn't get treated as a terminal + ::= "DEBUG" "THINGS"? + ; + + ::= ( "HELP" | "?" ) [topic]=( /[a-z_]*/ )* + ; + + ::= "TRACING" ( switch=( "ON" | "OFF" ) )? + ; + + ::= "EXPAND" ( switch=( "ON" | "OFF" ) )? + ; + + ::= "PAGING" ( switch=( "ON" | "OFF" | /[0-9]+/) )? + ; + + ::= "LOGIN" username= (password=)? + ; + + ::= "exit" | "quit" + ; + + ::= "CLEAR" | "CLS" + ; + + ::= "?" ; +''' + + +@cqlsh_syntax_completer('helpCommand', 'topic') +def complete_help(ctxt, cqlsh): + return sorted([t.upper() for t in cqldocs.get_help_topics() + cqlsh.get_help_topics()]) + + +def complete_source_quoted_filename(ctxt, cqlsh): + partial_path = ctxt.get_binding('partial', '') + head, tail = os.path.split(partial_path) + exhead = os.path.expanduser(head) + try: + contents = os.listdir(exhead or '.') + except OSError: + return () + matches = filter(lambda f: f.startswith(tail), contents) + annotated = [] + for f in matches: + match = os.path.join(head, f) + if os.path.isdir(os.path.join(exhead, f)): + match += '/' + annotated.append(match) + return annotated + + +cqlsh_syntax_completer('sourceCommand', 'fname')(complete_source_quoted_filename) +cqlsh_syntax_completer('captureCommand', 'fname')(complete_source_quoted_filename) + + +@cqlsh_syntax_completer('copyCommand', 'fname') +def copy_fname_completer(ctxt, cqlsh): + lasttype = ctxt.get_binding('*LASTTYPE*') + if lasttype == 'unclosedString': + return complete_source_quoted_filename(ctxt, cqlsh) + partial_path = ctxt.get_binding('partial') + if partial_path == '': + return ["'"] + return () + + +@cqlsh_syntax_completer('copyCommand', 'colnames') +def complete_copy_column_names(ctxt, cqlsh): + existcols = map(cqlsh.cql_unprotect_name, ctxt.get_binding('colnames', ())) + ks = cqlsh.cql_unprotect_name(ctxt.get_binding('ksname', None)) + cf = cqlsh.cql_unprotect_name(ctxt.get_binding('cfname')) + colnames = cqlsh.get_column_names(ks, cf) + if len(existcols) == 0: + return [colnames[0]] + return set(colnames[1:]) - set(existcols) + + +COPY_COMMON_OPTIONS = ['DELIMITER', 'QUOTE', 'ESCAPE', 'HEADER', 'NULL', 'DATETIMEFORMAT', + 'MAXATTEMPTS', 'REPORTFREQUENCY', 'DECIMALSEP', 'THOUSANDSSEP', 'BOOLSTYLE', + 'NUMPROCESSES', 'CONFIGFILE', 'RATEFILE'] +COPY_FROM_OPTIONS = ['CHUNKSIZE', 'INGESTRATE', 'MAXBATCHSIZE', 'MINBATCHSIZE', 'MAXROWS', + 'SKIPROWS', 'SKIPCOLS', 'MAXPARSEERRORS', 'MAXINSERTERRORS', 'ERRFILE', 'PREPAREDSTATEMENTS'] +COPY_TO_OPTIONS = ['ENCODING', 'PAGESIZE', 'PAGETIMEOUT', 'BEGINTOKEN', 'ENDTOKEN', 'MAXOUTPUTSIZE', 'MAXREQUESTS'] + + +@cqlsh_syntax_completer('copyOption', 'optnames') +def complete_copy_options(ctxt, cqlsh): + optnames = map(str.upper, ctxt.get_binding('optnames', ())) + direction = ctxt.get_binding('dir').upper() + if direction == 'FROM': + opts = set(COPY_COMMON_OPTIONS + COPY_FROM_OPTIONS) - set(optnames) + elif direction == 'TO': + opts = set(COPY_COMMON_OPTIONS + COPY_TO_OPTIONS) - set(optnames) + return opts + + +@cqlsh_syntax_completer('copyOption', 'optvals') +def complete_copy_opt_values(ctxt, cqlsh): + optnames = ctxt.get_binding('optnames', ()) + lastopt = optnames[-1].lower() + if lastopt == 'header': + return ['true', 'false'] + return [cqlhandling.Hint('')] + + +class NoKeyspaceError(Exception): + pass + + +class KeyspaceNotFound(Exception): + pass + + +class ColumnFamilyNotFound(Exception): + pass + + +class IndexNotFound(Exception): + pass + + +class ObjectNotFound(Exception): + pass + + +class VersionNotSupported(Exception): + pass + + +class UserTypeNotFound(Exception): + pass + + +class FunctionNotFound(Exception): + pass + + +class AggregateNotFound(Exception): + pass + + +class DecodeError(Exception): + verb = 'decode' + + def __init__(self, thebytes, err, colname=None): + self.thebytes = thebytes + self.err = err + self.colname = colname + + def __str__(self): + return str(self.thebytes) + + def message(self): + what = 'value %r' % (self.thebytes,) + if self.colname is not None: + what = 'value %r (for column %r)' % (self.thebytes, self.colname) + return 'Failed to %s %s : %s' \ + % (self.verb, what, self.err) + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, self.message()) + + +class FormatError(DecodeError): + verb = 'format' + + +def full_cql_version(ver): + while ver.count('.') < 2: + ver += '.0' + ver_parts = ver.split('-', 1) + [''] + vertuple = tuple(map(int, ver_parts[0].split('.')) + [ver_parts[1]]) + return ver, vertuple + + +def format_value(val, output_encoding, addcolor=False, date_time_format=None, + float_precision=None, colormap=None, nullval=None): + if isinstance(val, DecodeError): + if addcolor: + return colorme(repr(val.thebytes), colormap, 'error') + else: + return FormattedValue(repr(val.thebytes)) + return format_by_type(type(val), val, output_encoding, colormap=colormap, + addcolor=addcolor, nullval=nullval, date_time_format=date_time_format, + float_precision=float_precision) + + +def show_warning_without_quoting_line(message, category, filename, lineno, file=None, line=None): + if file is None: + file = sys.stderr + try: + file.write(warnings.formatwarning(message, category, filename, lineno, line='')) + except IOError: + pass ++ ++ +warnings.showwarning = show_warning_without_quoting_line +warnings.filterwarnings('always', category=cql3handling.UnexpectedTableStructure) + + +def insert_driver_hooks(): + extend_cql_deserialization() + auto_format_udts() + + +def extend_cql_deserialization(): + """ + The python driver returns BLOBs as string, but we expect them as bytearrays; therefore we change + the implementation of cassandra.cqltypes.BytesType.deserialize. + + The deserializers package exists only when the driver has been compiled with cython extensions and + cassandra.deserializers.DesBytesType replaces cassandra.cqltypes.BytesType.deserialize. + + DesBytesTypeByteArray is a fast deserializer that converts blobs into bytearrays but it was + only introduced recently (3.1.0). If it is available we use it, otherwise we remove + cassandra.deserializers.DesBytesType so that we fall back onto cassandra.cqltypes.BytesType.deserialize + just like in the case where no cython extensions are present. + """ + if hasattr(cassandra, 'deserializers'): + if hasattr(cassandra.deserializers, 'DesBytesTypeByteArray'): + cassandra.deserializers.DesBytesType = cassandra.deserializers.DesBytesTypeByteArray + else: + del cassandra.deserializers.DesBytesType + + cassandra.cqltypes.BytesType.deserialize = staticmethod(lambda byts, protocol_version: bytearray(byts)) + cassandra.cqltypes.CassandraType.support_empty_values = True + + +def auto_format_udts(): + # when we see a new user defined type, set up the shell formatting for it + udt_apply_params = cassandra.cqltypes.UserType.apply_parameters + + def new_apply_params(cls, *args, **kwargs): + udt_class = udt_apply_params(*args, **kwargs) + formatter_for(udt_class.typename)(format_value_utype) + return udt_class + + cassandra.cqltypes.UserType.udt_apply_parameters = classmethod(new_apply_params) + + make_udt_class = cassandra.cqltypes.UserType.make_udt_class + + def new_make_udt_class(cls, *args, **kwargs): + udt_class = make_udt_class(*args, **kwargs) + formatter_for(udt_class.tuple_type.__name__)(format_value_utype) + return udt_class + + cassandra.cqltypes.UserType.make_udt_class = classmethod(new_make_udt_class) + + +class FrozenType(cassandra.cqltypes._ParameterizedType): + """ + Needed until the bundled python driver adds FrozenType. + """ + typename = "frozen" + num_subtypes = 1 + + @classmethod + def deserialize_safe(cls, byts, protocol_version): + subtype, = cls.subtypes + return subtype.from_binary(byts) + + @classmethod + def serialize_safe(cls, val, protocol_version): + subtype, = cls.subtypes + return subtype.to_binary(val, protocol_version) + + +class Shell(cmd.Cmd): + custom_prompt = os.getenv('CQLSH_PROMPT', '') + if custom_prompt is not '': + custom_prompt += "\n" + default_prompt = custom_prompt + "cqlsh> " + continue_prompt = " ... " + keyspace_prompt = custom_prompt + "cqlsh:%s> " + keyspace_continue_prompt = "%s ... " + show_line_nums = False + debug = False + stop = False + last_hist = None + shunted_query_out = None + use_paging = True + + default_page_size = 100 + + def __init__(self, hostname, port, color=False, + username=None, password=None, encoding=None, stdin=None, tty=True, + completekey=DEFAULT_COMPLETEKEY, browser=None, use_conn=None, + cqlver=DEFAULT_CQLVER, keyspace=None, + tracing_enabled=False, expand_enabled=False, + display_nanotime_format=DEFAULT_NANOTIME_FORMAT, + display_timestamp_format=DEFAULT_TIMESTAMP_FORMAT, + display_date_format=DEFAULT_DATE_FORMAT, + display_float_precision=DEFAULT_FLOAT_PRECISION, + display_timezone=None, + max_trace_wait=DEFAULT_MAX_TRACE_WAIT, + ssl=False, + single_statement=None, + request_timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS, + protocol_version=DEFAULT_PROTOCOL_VERSION, + connect_timeout=DEFAULT_CONNECT_TIMEOUT_SECONDS): + cmd.Cmd.__init__(self, completekey=completekey) + self.hostname = hostname + self.port = port + self.auth_provider = None + if username: + if not password: + password = getpass.getpass() + self.auth_provider = PlainTextAuthProvider(username=username, password=password) + self.username = username + self.keyspace = keyspace + self.ssl = ssl + self.tracing_enabled = tracing_enabled + self.page_size = self.default_page_size + self.expand_enabled = expand_enabled + if use_conn: + self.conn = use_conn + else: + self.conn = Cluster(contact_points=(self.hostname,), port=self.port, cql_version=cqlver, + protocol_version=protocol_version, + auth_provider=self.auth_provider, + ssl_options=sslhandling.ssl_settings(hostname, CONFIG_FILE) if ssl else None, + load_balancing_policy=WhiteListRoundRobinPolicy([self.hostname]), + control_connection_timeout=connect_timeout, + connect_timeout=connect_timeout) + self.owns_connection = not use_conn + self.set_expanded_cql_version(cqlver) + + if keyspace: + self.session = self.conn.connect(keyspace) + else: + self.session = self.conn.connect() + + if browser == "": + browser = None + self.browser = browser + self.color = color + + self.display_nanotime_format = display_nanotime_format + self.display_timestamp_format = display_timestamp_format + self.display_date_format = display_date_format + + self.display_float_precision = display_float_precision + + self.display_timezone = display_timezone + + self.session.default_timeout = request_timeout + self.session.row_factory = ordered_dict_factory + self.session.default_consistency_level = cassandra.ConsistencyLevel.ONE + self.get_connection_versions() + + self.current_keyspace = keyspace + + self.display_timestamp_format = display_timestamp_format + self.display_nanotime_format = display_nanotime_format + self.display_date_format = display_date_format + + self.max_trace_wait = max_trace_wait + self.session.max_trace_wait = max_trace_wait + + self.tty = tty + self.encoding = encoding + self.check_windows_encoding() + + self.output_codec = codecs.lookup(encoding) + + self.statement = StringIO() + self.lineno = 1 + self.in_comment = False + + self.prompt = '' + if stdin is None: + stdin = sys.stdin + + if tty: + self.reset_prompt() + self.report_connection() + print 'Use HELP for help.' + else: + self.show_line_nums = True + self.stdin = stdin + self.query_out = sys.stdout + self.consistency_level = cassandra.ConsistencyLevel.ONE + self.serial_consistency_level = cassandra.ConsistencyLevel.SERIAL + + self.empty_lines = 0 + self.statement_error = False + self.single_statement = single_statement + + @property + def is_using_utf8(self): + # utf8 encodings from https://docs.python.org/{2,3}/library/codecs.html + return self.encoding.replace('-', '_').lower() in ['utf', 'utf_8', 'u8', 'utf8', CP65001] + + def check_windows_encoding(self): + if is_win and os.name == 'nt' and self.tty and \ + self.is_using_utf8 and sys.stdout.encoding != CP65001: + self.printerr("\nWARNING: console codepage must be set to cp65001 " + "to support {} encoding on Windows platforms.\n" + "If you experience encoding problems, change your console" + " codepage with 'chcp 65001' before starting cqlsh.\n".format(self.encoding)) + + def set_expanded_cql_version(self, ver): + ver, vertuple = full_cql_version(ver) + self.cql_version = ver + self.cql_ver_tuple = vertuple + + def cqlver_atleast(self, major, minor=0, patch=0): + return self.cql_ver_tuple[:3] >= (major, minor, patch) + + def myformat_value(self, val, **kwargs): + if isinstance(val, DecodeError): + self.decoding_errors.append(val) + try: + dtformats = DateTimeFormat(timestamp_format=self.display_timestamp_format, + date_format=self.display_date_format, nanotime_format=self.display_nanotime_format, + timezone=self.display_timezone) + return format_value(val, self.output_codec.name, + addcolor=self.color, date_time_format=dtformats, + float_precision=self.display_float_precision, **kwargs) + except Exception, e: + err = FormatError(val, e) + self.decoding_errors.append(err) + return format_value(err, self.output_codec.name, addcolor=self.color) + + def myformat_colname(self, name, table_meta=None): + column_colors = COLUMN_NAME_COLORS.copy() + # check column role and color appropriately + if table_meta: + if name in [col.name for col in table_meta.partition_key]: + column_colors.default_factory = lambda: RED + elif name in [col.name for col in table_meta.clustering_key]: + column_colors.default_factory = lambda: CYAN + return self.myformat_value(name, colormap=column_colors) + + def report_connection(self): + self.show_host() + self.show_version() + + def show_host(self): + print "Connected to %s at %s:%d." % \ + (self.applycolor(self.get_cluster_name(), BLUE), + self.hostname, + self.port) + + def show_version(self): + vers = self.connection_versions.copy() + vers['shver'] = version + # system.Versions['cql'] apparently does not reflect changes with + # set_cql_version. + vers['cql'] = self.cql_version + print "[cqlsh %(shver)s | Cassandra %(build)s | CQL spec %(cql)s | Native protocol v%(protocol)s]" % vers + + def show_session(self, sessionid, partial_session=False): + print_trace_session(self, self.session, sessionid, partial_session) + + def get_connection_versions(self): + result, = self.session.execute("select * from system.local where key = 'local'") + vers = { + 'build': result['release_version'], + 'protocol': result['native_protocol_version'], + 'cql': result['cql_version'], + } + self.connection_versions = vers + + def get_keyspace_names(self): + return map(str, self.conn.metadata.keyspaces.keys()) + + def get_columnfamily_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return map(str, self.get_keyspace_meta(ksname).tables.keys()) + + def get_index_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return map(str, self.get_keyspace_meta(ksname).indexes.keys()) + + def get_column_names(self, ksname, cfname): + if ksname is None: + ksname = self.current_keyspace + layout = self.get_table_meta(ksname, cfname) + return [unicode(col) for col in layout.columns] + + def get_usertype_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return self.get_keyspace_meta(ksname).user_types.keys() + + def get_usertype_layout(self, ksname, typename): + if ksname is None: + ksname = self.current_keyspace + + ks_meta = self.get_keyspace_meta(ksname) + + try: + user_type = ks_meta.user_types[typename] + except KeyError: + raise UserTypeNotFound("User type %r not found" % typename) + + return [(field_name, field_type.cql_parameterized_type()) + for field_name, field_type in zip(user_type.field_names, user_type.field_types)] + + def get_userfunction_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return map(lambda f: f.name, self.get_keyspace_meta(ksname).functions.values()) + + def get_useraggregate_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return map(lambda f: f.name, self.get_keyspace_meta(ksname).aggregates.values()) + + def get_cluster_name(self): + return self.conn.metadata.cluster_name + + def get_partitioner(self): + return self.conn.metadata.partitioner + + def get_keyspace_meta(self, ksname): + if ksname not in self.conn.metadata.keyspaces: + raise KeyspaceNotFound('Keyspace %r not found.' % ksname) + return self.conn.metadata.keyspaces[ksname] + + def get_keyspaces(self): + return self.conn.metadata.keyspaces.values() + + def get_ring(self, ks): + self.conn.metadata.token_map.rebuild_keyspace(ks, build_if_absent=True) + return self.conn.metadata.token_map.tokens_to_hosts_by_ks[ks] + + def get_table_meta(self, ksname, tablename): + if ksname is None: + ksname = self.current_keyspace + ksmeta = self.get_keyspace_meta(ksname) + + if tablename not in ksmeta.tables: + if ksname == 'system_auth' and tablename in ['roles', 'role_permissions']: + self.get_fake_auth_table_meta(ksname, tablename) + else: + raise ColumnFamilyNotFound("Column family %r not found" % tablename) + else: + return ksmeta.tables[tablename] + + def get_fake_auth_table_meta(self, ksname, tablename): + # may be using external auth implementation so internal tables + # aren't actually defined in schema. In this case, we'll fake + # them up + if tablename == 'roles': + ks_meta = KeyspaceMetadata(ksname, True, None, None) + table_meta = TableMetadata(ks_meta, 'roles') + table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type) + table_meta.columns['is_superuser'] = ColumnMetadata(table_meta, 'is_superuser', cassandra.cqltypes.BooleanType) + table_meta.columns['can_login'] = ColumnMetadata(table_meta, 'can_login', cassandra.cqltypes.BooleanType) + elif tablename == 'role_permissions': + ks_meta = KeyspaceMetadata(ksname, True, None, None) + table_meta = TableMetadata(ks_meta, 'role_permissions') + table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type) + table_meta.columns['resource'] = ColumnMetadata(table_meta, 'resource', cassandra.cqltypes.UTF8Type) + table_meta.columns['permission'] = ColumnMetadata(table_meta, 'permission', cassandra.cqltypes.UTF8Type) + else: + raise ColumnFamilyNotFound("Column family %r not found" % tablename) + + def get_index_meta(self, ksname, idxname): + if ksname is None: + ksname = self.current_keyspace + ksmeta = self.get_keyspace_meta(ksname) + + if idxname not in ksmeta.indexes: + raise IndexNotFound("Index %r not found" % idxname) + + return ksmeta.indexes[idxname] + + def get_object_meta(self, ks, name): + if name is None: + if ks and ks in self.conn.metadata.keyspaces: + return self.conn.metadata.keyspaces[ks] + elif self.current_keyspace is None: + raise ObjectNotFound("%r not found in keyspaces" % (ks)) + else: + name = ks + ks = self.current_keyspace + + if ks is None: + ks = self.current_keyspace + + ksmeta = self.get_keyspace_meta(ks) + + if name in ksmeta.tables: + return ksmeta.tables[name] + elif name in ksmeta.indexes: + return ksmeta.indexes[name] + + raise ObjectNotFound("%r not found in keyspace %r" % (name, ks)) + + def get_usertypes_meta(self): + data = self.session.execute("select * from system.schema_usertypes") + if not data: + return cql3handling.UserTypesMeta({}) + + return cql3handling.UserTypesMeta.from_layout(data) + + def get_trigger_names(self, ksname=None): + if ksname is None: + ksname = self.current_keyspace + + return [trigger.name + for table in self.get_keyspace_meta(ksname).tables.values() + for trigger in table.triggers.values()] + + def reset_statement(self): + self.reset_prompt() + self.statement.truncate(0) + self.empty_lines = 0 + + def reset_prompt(self): + if self.current_keyspace is None: + self.set_prompt(self.default_prompt, True) + else: + self.set_prompt(self.keyspace_prompt % self.current_keyspace, True) + + def set_continue_prompt(self): + if self.empty_lines >= 3: + self.set_prompt("Statements are terminated with a ';'. You can press CTRL-C to cancel an incomplete statement.") + self.empty_lines = 0 + return + if self.current_keyspace is None: + self.set_prompt(self.continue_prompt) + else: + spaces = ' ' * len(str(self.current_keyspace)) + self.set_prompt(self.keyspace_continue_prompt % spaces) + self.empty_lines = self.empty_lines + 1 if not self.lastcmd else 0 + + @contextmanager + def prepare_loop(self): + readline = None + if self.tty and self.completekey: + try: + import readline + except ImportError: + if is_win: + print "WARNING: pyreadline dependency missing. Install to enable tab completion." + pass + else: + old_completer = readline.get_completer() + readline.set_completer(self.complete) + if readline.__doc__ is not None and 'libedit' in readline.__doc__: + readline.parse_and_bind("bind -e") + readline.parse_and_bind("bind '" + self.completekey + "' rl_complete") + readline.parse_and_bind("bind ^R em-inc-search-prev") + else: + readline.parse_and_bind(self.completekey + ": complete") + try: + yield + finally: + if readline is not None: + readline.set_completer(old_completer) + + def get_input_line(self, prompt=''): + if self.tty: + try: + self.lastcmd = raw_input(prompt).decode(self.encoding) + except UnicodeDecodeError: + self.lastcmd = '' + traceback.print_exc() + self.check_windows_encoding() + line = self.lastcmd + '\n' + else: + self.lastcmd = self.stdin.readline() + line = self.lastcmd + if not len(line): + raise EOFError + self.lineno += 1 + return line + + def use_stdin_reader(self, until='', prompt=''): + until += '\n' + while True: + try: + newline = self.get_input_line(prompt=prompt) + except EOFError: + return + if newline == until: + return + yield newline + + def cmdloop(self): + """ + Adapted from cmd.Cmd's version, because there is literally no way with + cmd.Cmd.cmdloop() to tell the difference between "EOF" showing up in + input and an actual EOF. + """ + with self.prepare_loop(): + while not self.stop: + try: + if self.single_statement: + line = self.single_statement + self.stop = True + else: + line = self.get_input_line(self.prompt) + self.statement.write(line) + if self.onecmd(self.statement.getvalue()): + self.reset_statement() + except EOFError: + self.handle_eof() + except CQL_ERRORS, cqlerr: + self.printerr(cqlerr.message.decode(encoding='utf-8')) + except KeyboardInterrupt: + self.reset_statement() + print + + def onecmd(self, statementtext): + """ + Returns true if the statement is complete and was handled (meaning it + can be reset). + """ + + try: + statements, endtoken_escaped = cqlruleset.cql_split_statements(statementtext) + except pylexotron.LexingError, e: + if self.show_line_nums: + self.printerr('Invalid syntax at char %d' % (e.charnum,)) + else: + self.printerr('Invalid syntax at line %d, char %d' + % (e.linenum, e.charnum)) + statementline = statementtext.split('\n')[e.linenum - 1] + self.printerr(' %s' % statementline) + self.printerr(' %s^' % (' ' * e.charnum)) + return True + + while statements and not statements[-1]: + statements = statements[:-1] + if not statements: + return True + if endtoken_escaped or statements[-1][-1][0] != 'endtoken': + self.set_continue_prompt() + return + for st in statements: + try: + self.handle_statement(st, statementtext) + except Exception, e: + if self.debug: + traceback.print_exc() + else: + self.printerr(e) + return True + + def handle_eof(self): + if self.tty: + print + statement = self.statement.getvalue() + if statement.strip(): + if not self.onecmd(statement): + self.printerr('Incomplete statement at end of file') + self.do_exit() + + def handle_statement(self, tokens, srcstr): + # Concat multi-line statements and insert into history + if readline is not None: + nl_count = srcstr.count("\n") + + new_hist = srcstr.replace("\n", " ").rstrip() + + if nl_count > 1 and self.last_hist != new_hist: + readline.add_history(new_hist.encode(self.encoding)) + + self.last_hist = new_hist + cmdword = tokens[0][1] + if cmdword == '?': + cmdword = 'help' + custom_handler = getattr(self, 'do_' + cmdword.lower(), None) + if custom_handler: + parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr, + startsymbol='cqlshCommand') + if parsed and not parsed.remainder: + # successful complete parse + return custom_handler(parsed) + else: + return self.handle_parse_error(cmdword, tokens, parsed, srcstr) + return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr)) + + def handle_parse_error(self, cmdword, tokens, parsed, srcstr): + if cmdword.lower() in ('select', 'insert', 'update', 'delete', 'truncate', + 'create', 'drop', 'alter', 'grant', 'revoke', + 'batch', 'list'): + # hey, maybe they know about some new syntax we don't. type + # assumptions won't work, but maybe the query will. + return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr)) + if parsed: + self.printerr('Improper %s command (problem at %r).' % (cmdword, parsed.remainder[0])) + else: + self.printerr('Improper %s command.' % cmdword) + + def do_use(self, parsed): + ksname = parsed.get_binding('ksname') + success, _ = self.perform_simple_statement(SimpleStatement(parsed.extract_orig())) + if success: + if ksname[0] == '"' and ksname[-1] == '"': + self.current_keyspace = self.cql_unprotect_name(ksname) + else: + self.current_keyspace = ksname.lower() + + def do_select(self, parsed): + tracing_was_enabled = self.tracing_enabled + ksname = parsed.get_binding('ksname') + stop_tracing = ksname == 'system_traces' or (ksname is None and self.current_keyspace == 'system_traces') + self.tracing_enabled = self.tracing_enabled and not stop_tracing + statement = parsed.extract_orig() + self.perform_statement(statement) + self.tracing_enabled = tracing_was_enabled + + def perform_statement(self, statement): + stmt = SimpleStatement(statement, consistency_level=self.consistency_level, serial_consistency_level=self.serial_consistency_level, fetch_size=self.page_size if self.use_paging else None) + success, future = self.perform_simple_statement(stmt) + + if future: + if future.warnings: + self.print_warnings(future.warnings) + + if self.tracing_enabled: + try: + for trace in future.get_all_query_traces(max_wait_per=self.max_trace_wait, query_cl=self.consistency_level): + print_trace(self, trace) + except TraceUnavailable: + msg = "Statement trace did not complete within %d seconds; trace data may be incomplete." % (self.session.max_trace_wait,) + self.writeresult(msg, color=RED) + for trace_id in future.get_query_trace_ids(): + self.show_session(trace_id, partial_session=True) + except Exception, err: + self.printerr("Unable to fetch query trace: %s" % (str(err),)) + + return success + + def parse_for_table_meta(self, query_string): + try: + parsed = cqlruleset.cql_parse(query_string)[1] + except IndexError: + return None + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + cf = self.cql_unprotect_name(parsed.get_binding('cfname')) + return self.get_table_meta(ks, cf) + + def perform_simple_statement(self, statement): + if not statement: + return False, None + + future = self.session.execute_async(statement, trace=self.tracing_enabled) + result = None + try: + result = future.result() + except CQL_ERRORS, err: + self.printerr(unicode(err.__class__.__name__) + u": " + err.message.decode(encoding='utf-8')) + except Exception: + import traceback + self.printerr(traceback.format_exc()) + + # Even if statement failed we try to refresh schema if not agreed (see CASSANDRA-9689) + if not future.is_schema_agreed: + try: + self.conn.refresh_schema_metadata(5) # will throw exception if there is a schema mismatch + except Exception: + self.printerr("Warning: schema version mismatch detected; check the schema versions of your " + "nodes in system.local and system.peers.") + self.conn.refresh_schema_metadata(-1) + + if result is None: + return False, None + + if statement.query_string[:6].lower() == 'select': + self.print_result(result, self.parse_for_table_meta(statement.query_string)) + elif statement.query_string.lower().startswith("list users") or statement.query_string.lower().startswith("list roles"): + self.print_result(result, self.get_table_meta('system_auth', 'roles')) + elif statement.query_string.lower().startswith("list"): + self.print_result(result, self.get_table_meta('system_auth', 'role_permissions')) + elif result: + # CAS INSERT/UPDATE + self.writeresult("") + self.print_static_result(result.column_names, list(result), self.parse_for_table_meta(statement.query_string)) + self.flush_output() + return True, future + + def print_result(self, result, table_meta): + self.decoding_errors = [] + + self.writeresult("") + if result.has_more_pages and self.tty: + num_rows = 0 + while True: + page = result.current_rows + if page: + num_rows += len(page) + self.print_static_result(result.column_names, page, table_meta) + if result.has_more_pages: + raw_input("---MORE---") + result.fetch_next_page() + else: + break + else: + rows = list(result) + num_rows = len(rows) + self.print_static_result(result.column_names, rows, table_meta) + self.writeresult("(%d rows)" % num_rows) + + if self.decoding_errors: + for err in self.decoding_errors[:2]: + self.writeresult(err.message(), color=RED) + if len(self.decoding_errors) > 2: + self.writeresult('%d more decoding errors suppressed.' + % (len(self.decoding_errors) - 2), color=RED) + + def print_static_result(self, column_names, rows, table_meta): + if not column_names and not table_meta: + return + + column_names = column_names or table_meta.columns.keys() + formatted_names = [self.myformat_colname(name, table_meta) for name in column_names] + if not rows: + # print header only + self.print_formatted_result(formatted_names, None) + return + formatted_values = [map(self.myformat_value, row.values()) for row in rows] + + if self.expand_enabled: + self.print_formatted_result_vertically(formatted_names, formatted_values) + else: + self.print_formatted_result(formatted_names, formatted_values) + + def print_formatted_result(self, formatted_names, formatted_values): + # determine column widths + widths = [n.displaywidth for n in formatted_names] + if formatted_values is not None: + for fmtrow in formatted_values: + for num, col in enumerate(fmtrow): + widths[num] = max(widths[num], col.displaywidth) + + # print header + header = ' | '.join(hdr.ljust(w, color=self.color) for (hdr, w) in zip(formatted_names, widths)) + self.writeresult(' ' + header.rstrip()) + self.writeresult('-%s-' % '-+-'.join('-' * w for w in widths)) + + # stop if there are no rows + if formatted_values is None: + self.writeresult("") + return + + # print row data + for row in formatted_values: + line = ' | '.join(col.rjust(w, color=self.color) for (col, w) in zip(row, widths)) + self.writeresult(' ' + line) + + self.writeresult("") + + def print_formatted_result_vertically(self, formatted_names, formatted_values): + max_col_width = max([n.displaywidth for n in formatted_names]) + max_val_width = max([n.displaywidth for row in formatted_values for n in row]) + + # for each row returned, list all the column-value pairs + for row_id, row in enumerate(formatted_values): + self.writeresult("@ Row %d" % (row_id + 1)) + self.writeresult('-%s-' % '-+-'.join(['-' * max_col_width, '-' * max_val_width])) + for field_id, field in enumerate(row): + column = formatted_names[field_id].ljust(max_col_width, color=self.color) + value = field.ljust(field.displaywidth, color=self.color) + self.writeresult(' ' + " | ".join([column, value])) + self.writeresult('') + + def print_warnings(self, warnings): + if warnings is None or len(warnings) == 0: + return + + self.writeresult('') + self.writeresult('Warnings :') + for warning in warnings: + self.writeresult(warning) + self.writeresult('') + + def emptyline(self): + pass + + def parseline(self, line): + # this shouldn't be needed + raise NotImplementedError + + def complete(self, text, state): + if readline is None: + return + if state == 0: + try: + self.completion_matches = self.find_completions(text) + except Exception: + if debug_completion: + import traceback + traceback.print_exc() + else: + raise + try: + return self.completion_matches[state] + except IndexError: + return None + + def find_completions(self, text): + curline = readline.get_line_buffer() + prevlines = self.statement.getvalue() + wholestmt = prevlines + curline + begidx = readline.get_begidx() + len(prevlines) + stuff_to_complete = wholestmt[:begidx] + return cqlruleset.cql_complete(stuff_to_complete, text, cassandra_conn=self, + debug=debug_completion, startsymbol='cqlshCommand') + + def set_prompt(self, prompt, prepend_user=False): + if prepend_user and self.username: + self.prompt = "%s@%s" % (self.username, prompt) + return + self.prompt = prompt + + def cql_unprotect_name(self, namestr): + if namestr is None: + return + return cqlruleset.dequote_name(namestr) + + def cql_unprotect_value(self, valstr): + if valstr is not None: + return cqlruleset.dequote_value(valstr) + + def print_recreate_keyspace(self, ksdef, out): + out.write(ksdef.export_as_string()) + out.write("\n") + + def print_recreate_columnfamily(self, ksname, cfname, out): + """ + Output CQL commands which should be pasteable back into a CQL session + to recreate the given table. + + Writes output to the given out stream. + """ + out.write(self.get_table_meta(ksname, cfname).export_as_string()) + out.write("\n") + + def print_recreate_index(self, ksname, idxname, out): + """ + Output CQL commands which should be pasteable back into a CQL session + to recreate the given index. + + Writes output to the given out stream. + """ + out.write(self.get_index_meta(ksname, idxname).export_as_string()) + out.write("\n") + + def print_recreate_object(self, ks, name, out): + """ + Output CQL commands which should be pasteable back into a CQL session + to recreate the given object (ks, table or index). + + Writes output to the given out stream. + """ + out.write(self.get_object_meta(ks, name).export_as_string()) + out.write("\n") + + def describe_keyspaces(self): + print + cmd.Cmd.columnize(self, protect_names(self.get_keyspace_names())) + print + + def describe_keyspace(self, ksname): + print + self.print_recreate_keyspace(self.get_keyspace_meta(ksname), sys.stdout) + print + + def describe_columnfamily(self, ksname, cfname): + if ksname is None: + ksname = self.current_keyspace + if ksname is None: + raise NoKeyspaceError("No keyspace specified and no current keyspace") + print + self.print_recreate_columnfamily(ksname, cfname, sys.stdout) + print + + def describe_index(self, ksname, idxname): + print + self.print_recreate_index(ksname, idxname, sys.stdout) + print + + def describe_object(self, ks, name): + print + self.print_recreate_object(ks, name, sys.stdout) + print + + def describe_columnfamilies(self, ksname): + print + if ksname is None: + for k in self.get_keyspaces(): + name = protect_name(k.name) + print 'Keyspace %s' % (name,) + print '---------%s' % ('-' * len(name)) + cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(k.name))) + print + else: + cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(ksname))) + print + + def describe_functions(self, ksname): + print + if ksname is None: + for ksmeta in self.get_keyspaces(): + name = protect_name(ksmeta.name) + print 'Keyspace %s' % (name,) + print '---------%s' % ('-' * len(name)) + self._columnize_unicode(ksmeta.functions.keys()) + else: + ksmeta = self.get_keyspace_meta(ksname) + self._columnize_unicode(ksmeta.functions.keys()) + + def describe_function(self, ksname, functionname): + if ksname is None: + ksname = self.current_keyspace + if ksname is None: + raise NoKeyspaceError("No keyspace specified and no current keyspace") + print + ksmeta = self.get_keyspace_meta(ksname) + functions = filter(lambda f: f.name == functionname, ksmeta.functions.values()) + if len(functions) == 0: + raise FunctionNotFound("User defined function %r not found" % functionname) + print "\n\n".join(func.as_cql_query(formatted=True) for func in functions) + print + + def describe_aggregates(self, ksname): + print + if ksname is None: + for ksmeta in self.get_keyspaces(): + name = protect_name(ksmeta.name) + print 'Keyspace %s' % (name,) + print '---------%s' % ('-' * len(name)) + self._columnize_unicode(ksmeta.aggregates.keys()) + else: + ksmeta = self.get_keyspace_meta(ksname) + self._columnize_unicode(ksmeta.aggregates.keys()) + + def describe_aggregate(self, ksname, aggregatename): + if ksname is None: + ksname = self.current_keyspace + if ksname is None: + raise NoKeyspaceError("No keyspace specified and no current keyspace") + print + ksmeta = self.get_keyspace_meta(ksname) + aggregates = filter(lambda f: f.name == aggregatename, ksmeta.aggregates.values()) + if len(aggregates) == 0: + raise FunctionNotFound("User defined aggregate %r not found" % aggregatename) + print "\n\n".join(aggr.as_cql_query(formatted=True) for aggr in aggregates) + print + + def describe_usertypes(self, ksname): + print + if ksname is None: + for ksmeta in self.get_keyspaces(): + name = protect_name(ksmeta.name) + print 'Keyspace %s' % (name,) + print '---------%s' % ('-' * len(name)) + self._columnize_unicode(ksmeta.user_types.keys(), quote=True) + else: + ksmeta = self.get_keyspace_meta(ksname) + self._columnize_unicode(ksmeta.user_types.keys(), quote=True) + + def describe_usertype(self, ksname, typename): + if ksname is None: + ksname = self.current_keyspace + if ksname is None: + raise NoKeyspaceError("No keyspace specified and no current keyspace") + print + ksmeta = self.get_keyspace_meta(ksname) + try: + usertype = ksmeta.user_types[typename] + except KeyError: + raise UserTypeNotFound("User type %r not found" % typename) + print usertype.as_cql_query(formatted=True) + print + + def _columnize_unicode(self, name_list, quote=False): + """ + Used when columnizing identifiers that may contain unicode + """ + names = [n.encode('utf-8') for n in name_list] + if quote: + names = protect_names(names) + cmd.Cmd.columnize(self, names) + print + + def describe_cluster(self): + print '\nCluster: %s' % self.get_cluster_name() + p = trim_if_present(self.get_partitioner(), 'org.apache.cassandra.dht.') + print 'Partitioner: %s\n' % p + # TODO: snitch? + # snitch = trim_if_present(self.get_snitch(), 'org.apache.cassandra.locator.') + # print 'Snitch: %s\n' % snitch + if self.current_keyspace is not None and self.current_keyspace != 'system': + print "Range ownership:" + ring = self.get_ring(self.current_keyspace) + for entry in ring.items(): + print ' %39s [%s]' % (str(entry[0].value), ', '.join([host.address for host in entry[1]])) + print + + def describe_schema(self, include_system=False): + print + for k in self.get_keyspaces(): + if include_system or k.name not in cql3handling.SYSTEM_KEYSPACES: + self.print_recreate_keyspace(k, sys.stdout) + print + + def do_describe(self, parsed): + """ + DESCRIBE [cqlsh only] + + (DESC may be used as a shorthand.) + + Outputs information about the connected Cassandra cluster, or about + the data objects stored in the cluster. Use in one of the following ways: + + DESCRIBE KEYSPACES + + Output the names of all keyspaces. + + DESCRIBE KEYSPACE [] + + Output CQL commands that could be used to recreate the given keyspace, + and the objects in it (such as tables, types, functions, etc.). + In some cases, as the CQL interface matures, there will be some metadata + about a keyspace that is not representable with CQL. That metadata will not be shown. + + The '' argument may be omitted, in which case the current + keyspace will be described. + + DESCRIBE TABLES + + Output the names of all tables in the current keyspace, or in all + keyspaces if there is no current keyspace. + + DESCRIBE TABLE [.] + + Output CQL commands that could be used to recreate the given table. + In some cases, as above, there may be table metadata which is not + representable and which will not be shown. + + DESCRIBE INDEX + + Output the CQL command that could be used to recreate the given index. + In some cases, there may be index metadata which is not representable + and which will not be shown. + + DESCRIBE CLUSTER + + Output information about the connected Cassandra cluster, such as the + cluster name, and the partitioner and snitch in use. When you are + connected to a non-system keyspace, also shows endpoint-range + ownership information for the Cassandra ring. + + DESCRIBE [FULL] SCHEMA + + Output CQL commands that could be used to recreate the entire (non-system) schema. + Works as though "DESCRIBE KEYSPACE k" was invoked for each non-system keyspace + k. Use DESCRIBE FULL SCHEMA to include the system keyspaces. + + DESCRIBE TYPES + + Output the names of all user-defined-types in the current keyspace, or in all + keyspaces if there is no current keyspace. + + DESCRIBE TYPE [.] + + Output the CQL command that could be used to recreate the given user-defined-type. + + DESCRIBE FUNCTIONS + + Output the names of all user-defined-functions in the current keyspace, or in all + keyspaces if there is no current keyspace. + + DESCRIBE FUNCTION [.] + + Output the CQL command that could be used to recreate the given user-defined-function. + + DESCRIBE AGGREGATES + + Output the names of all user-defined-aggregates in the current keyspace, or in all + keyspaces if there is no current keyspace. + + DESCRIBE AGGREGATE [.] + + Output the CQL command that could be used to recreate the given user-defined-aggregate. + + DESCRIBE + + Output CQL commands that could be used to recreate the entire object schema, + where object can be either a keyspace or a table or an index (in this order). + """ + what = parsed.matched[1][1].lower() + if what == 'functions': + self.describe_functions(self.current_keyspace) + elif what == 'function': + ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + functionname = self.cql_unprotect_name(parsed.get_binding('udfname')) + self.describe_function(ksname, functionname) + elif what == 'aggregates': + self.describe_aggregates(self.current_keyspace) + elif what == 'aggregate': + ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + aggregatename = self.cql_unprotect_name(parsed.get_binding('udaname')) + self.describe_aggregate(ksname, aggregatename) + elif what == 'keyspaces': + self.describe_keyspaces() + elif what == 'keyspace': + ksname = self.cql_unprotect_name(parsed.get_binding('ksname', '')) + if not ksname: + ksname = self.current_keyspace + if ksname is None: + self.printerr('Not in any keyspace.') + return + self.describe_keyspace(ksname) + elif what in ('columnfamily', 'table'): + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + cf = self.cql_unprotect_name(parsed.get_binding('cfname')) + self.describe_columnfamily(ks, cf) + elif what == 'index': + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + idx = self.cql_unprotect_name(parsed.get_binding('idxname', None)) + self.describe_index(ks, idx) + elif what in ('columnfamilies', 'tables'): + self.describe_columnfamilies(self.current_keyspace) + elif what == 'types': + self.describe_usertypes(self.current_keyspace) + elif what == 'type': + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + ut = self.cql_unprotect_name(parsed.get_binding('utname')) + self.describe_usertype(ks, ut) + elif what == 'cluster': + self.describe_cluster() + elif what == 'schema': + self.describe_schema(False) + elif what == 'full' and parsed.matched[2][1].lower() == 'schema': + self.describe_schema(True) + elif what: + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + name = self.cql_unprotect_name(parsed.get_binding('cfname')) + if not name: + name = self.cql_unprotect_name(parsed.get_binding('idxname', None)) + self.describe_object(ks, name) + do_desc = do_describe + + def do_copy(self, parsed): + r""" + COPY [cqlsh only] + + COPY x FROM: Imports CSV data into a Cassandra table + COPY x TO: Exports data from a Cassandra table in CSV format. + + COPY [ ( column [, ...] ) ] + FROM ( '' | STDIN ) + [ WITH