mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-1.2' into trunk
Conflicts: pylib/cqlshlib/cql3handling.py
This commit is contained in:
commit
125a8c9c79
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
1.2.0
|
||||
* Disallow counters in collections (CASSANDRA-5082)
|
||||
* cqlsh: add unit tests (CASSANDRA-3920)
|
||||
|
||||
|
||||
1.2.0-rc2
|
||||
|
|
|
|||
86
bin/cqlsh
86
bin/cqlsh
|
|
@ -53,10 +53,16 @@ import platform
|
|||
import warnings
|
||||
import csv
|
||||
|
||||
|
||||
readline = None
|
||||
try:
|
||||
import readline
|
||||
# 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:
|
||||
readline = None
|
||||
pass
|
||||
|
||||
CQL_LIB_PREFIX = 'cql-internal-only-'
|
||||
THRIFT_LIB_PREFIX = 'thrift-python-internal-only-'
|
||||
|
|
@ -166,7 +172,7 @@ else:
|
|||
|
||||
debug_completion = bool(os.environ.get('CQLSH_DEBUG_COMPLETION', '') == 'YES')
|
||||
|
||||
SYSTEM_KEYSPACES = ('system', 'system_traces')
|
||||
SYSTEM_KEYSPACES = ('system', 'system_traces', 'system_auth')
|
||||
|
||||
# we want the cql parser to understand our cqlsh-specific commands too
|
||||
my_commands_ending_with_newline = (
|
||||
|
|
@ -368,9 +374,13 @@ class VersionNotSupported(Exception):
|
|||
pass
|
||||
|
||||
class DecodeError(Exception):
|
||||
verb = 'decode'
|
||||
|
||||
def __init__(self, thebytes, err, expectedtype, colname=None):
|
||||
self.thebytes = thebytes
|
||||
self.err = err
|
||||
if isinstance(expectedtype, type) and issubclass(expectedtype, CassandraType):
|
||||
expectedtype = expectedtype.cql_parameterized_type()
|
||||
self.expectedtype = expectedtype
|
||||
self.colname = colname
|
||||
|
||||
|
|
@ -378,14 +388,18 @@ class DecodeError(Exception):
|
|||
return str(self.thebytes)
|
||||
|
||||
def message(self):
|
||||
what = 'column name %r' % (self.thebytes,)
|
||||
what = 'value %r' % (self.thebytes,)
|
||||
if self.colname is not None:
|
||||
what = 'value %r (for column %r)' % (self.thebytes, self.colname)
|
||||
return 'Failed to decode %s as %s: %s' % (what, self.expectedtype, self.err)
|
||||
return 'Failed to %s %s as %s: %s' \
|
||||
% (self.verb, what, self.expectedtype, 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'
|
||||
|
|
@ -397,9 +411,9 @@ def format_value(val, typeclass, output_encoding, addcolor=False, time_format=No
|
|||
float_precision=None, colormap=None, nullval=None):
|
||||
if isinstance(val, DecodeError):
|
||||
if addcolor:
|
||||
return colorme(val.thebytes, colormap, 'hex')
|
||||
return colorme(repr(val.thebytes), colormap, 'error')
|
||||
else:
|
||||
return FormattedValue(val.thebytes)
|
||||
return FormattedValue(repr(val.thebytes))
|
||||
if not issubclass(typeclass, CassandraType):
|
||||
typeclass = lookup_casstype(typeclass)
|
||||
return format_by_type(typeclass, val, output_encoding, colormap=colormap,
|
||||
|
|
@ -449,7 +463,7 @@ class Shell(cmd.Cmd):
|
|||
def __init__(self, hostname, port, transport_factory, color=False,
|
||||
username=None, password=None, encoding=None, stdin=None, tty=True,
|
||||
completekey=DEFAULT_COMPLETEKEY, use_conn=None,
|
||||
cqlver=None, keyspace=None, tracing_enabled=False,
|
||||
cqlver=DEFAULT_CQLVER, keyspace=None, tracing_enabled=False,
|
||||
display_time_format=DEFAULT_TIME_FORMAT,
|
||||
display_float_precision=DEFAULT_FLOAT_PRECISION):
|
||||
cmd.Cmd.__init__(self, completekey=completekey)
|
||||
|
|
@ -530,9 +544,14 @@ class Shell(cmd.Cmd):
|
|||
def myformat_value(self, val, casstype, **kwargs):
|
||||
if isinstance(val, DecodeError):
|
||||
self.decoding_errors.append(val)
|
||||
return format_value(val, casstype, self.output_codec.name,
|
||||
addcolor=self.color, time_format=self.display_time_format,
|
||||
float_precision=self.display_float_precision, **kwargs)
|
||||
try:
|
||||
return format_value(val, casstype, self.output_codec.name,
|
||||
addcolor=self.color, time_format=self.display_time_format,
|
||||
float_precision=self.display_float_precision, **kwargs)
|
||||
except Exception, e:
|
||||
err = FormatError(val, e, casstype)
|
||||
self.decoding_errors.append(err)
|
||||
return format_value(err, None, self.output_codec.name, addcolor=self.color)
|
||||
|
||||
def myformat_colname(self, name, nametype):
|
||||
return self.myformat_value(name, nametype, colormap=COLUMN_NAME_COLORS)
|
||||
|
|
@ -644,7 +663,7 @@ class Shell(cmd.Cmd):
|
|||
raise ColumnFamilyNotFound("Unconfigured column family %r" % (cfname,))
|
||||
|
||||
def get_columnfamily_names(self, ksname=None):
|
||||
if self.cqlver_atleast(3):
|
||||
if self.cqlver_atleast(3) and not self.is_cql3_beta():
|
||||
return self.get_columnfamily_names_cql3(ksname=ksname)
|
||||
return [c.name for c in self.get_columnfamilies(ksname)]
|
||||
|
||||
|
|
@ -670,7 +689,7 @@ class Shell(cmd.Cmd):
|
|||
def get_column_names(self, ksname, cfname):
|
||||
if ksname is None:
|
||||
ksname = self.current_keyspace
|
||||
if ksname not in SYSTEM_KEYSPACES and self.cqlver_atleast(3):
|
||||
if self.cqlver_atleast(3) and not (self.is_cql3_beta() and ksname in SYSTEM_KEYSPACES):
|
||||
return self.get_column_names_from_layout(ksname, cfname)
|
||||
else:
|
||||
return self.get_column_names_from_cfdef(ksname, cfname)
|
||||
|
|
@ -898,7 +917,7 @@ class Shell(cmd.Cmd):
|
|||
print
|
||||
statement = self.statement.getvalue()
|
||||
if statement.strip():
|
||||
if not self.onecmd(statement + ';'):
|
||||
if not self.onecmd(statement):
|
||||
self.printerr('Incomplete statement at end of file')
|
||||
self.do_exit()
|
||||
|
||||
|
|
@ -960,7 +979,7 @@ class Shell(cmd.Cmd):
|
|||
print_trace_session(self, self.cursor, session_id)
|
||||
return result
|
||||
else:
|
||||
return self.perform_statement_untraced(statement, decoder=None)
|
||||
return self.perform_statement_untraced(statement, decoder=decoder)
|
||||
|
||||
def perform_statement_untraced(self, statement, decoder=None):
|
||||
if not statement:
|
||||
|
|
@ -1048,6 +1067,7 @@ class Shell(cmd.Cmd):
|
|||
last_description = None
|
||||
for row in cursor:
|
||||
if last_description is not None and cursor.description != last_description:
|
||||
cursor._reset()
|
||||
return False
|
||||
last_description = cursor.description
|
||||
cursor._reset()
|
||||
|
|
@ -1085,13 +1105,13 @@ class Shell(cmd.Cmd):
|
|||
widths[num] = max(widths[num], col.displaywidth)
|
||||
|
||||
# print header
|
||||
header = ' | '.join(hdr.color_ljust(w) for (hdr, w) in zip(formatted_names, widths))
|
||||
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))
|
||||
|
||||
# print row data
|
||||
for row in formatted_values:
|
||||
line = ' | '.join(col.color_rjust(w) for (col, w) in zip(row, widths))
|
||||
line = ' | '.join(col.rjust(w, color=self.color) for (col, w) in zip(row, widths))
|
||||
self.writeresult(' ' + line)
|
||||
|
||||
def print_dynamic_result(self, cursor):
|
||||
|
|
@ -1197,13 +1217,15 @@ class Shell(cmd.Cmd):
|
|||
|
||||
# no metainfo available from system.schema_* for system CFs, so we have
|
||||
# to use cfdef-based description for those.
|
||||
|
||||
if self.cqlver_atleast(3) and not self.is_cql3_beta():
|
||||
if self.cqlver_atleast(3) and not (self.is_cql3_beta() and ksname in SYSTEM_KEYSPACES):
|
||||
try:
|
||||
layout = self.get_columnfamily_layout(ksname, cfname)
|
||||
except CQL_ERRORS:
|
||||
# most likely a 1.1 beta where cql3 is supported, but not system.schema_*
|
||||
pass
|
||||
if self.debug:
|
||||
print 'warning: failed to use system.schema_* tables to describe cf'
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
return self.print_recreate_columnfamily_from_layout(layout, out)
|
||||
|
||||
|
|
@ -1293,14 +1315,22 @@ class Shell(cmd.Cmd):
|
|||
# work out how to determine that from a layout.
|
||||
|
||||
cf_opts = []
|
||||
compaction_strategy = trim_if_present(getattr(layout, 'compaction_strategy_class'),
|
||||
'org.apache.cassandra.db.compaction.')
|
||||
for cql3option, layoutoption in cqlruleset.columnfamily_layout_options:
|
||||
if layoutoption is None:
|
||||
layoutoption = cql3option
|
||||
optval = getattr(layout, layoutoption, None)
|
||||
if optval is None:
|
||||
continue
|
||||
if layoutoption == 'bloom_filter_fp_chance':
|
||||
if compaction_strategy == 'LeveledCompactionStrategy':
|
||||
optval = 0.1
|
||||
else:
|
||||
optval = 0.01
|
||||
else:
|
||||
continue
|
||||
elif layoutoption == 'compaction_strategy_class':
|
||||
optval = trim_if_present(optval, 'org.apache.cassandra.db.compaction.')
|
||||
optval = compaction_strategy
|
||||
cf_opts.append((cql3option, self.cql_protect_value(optval)))
|
||||
for cql3option, layoutoption, _ in cqlruleset.columnfamily_layout_map_options:
|
||||
if layoutoption is None:
|
||||
|
|
@ -1311,6 +1341,9 @@ class Shell(cmd.Cmd):
|
|||
if compclass is not None:
|
||||
optmap['sstable_compression'] = \
|
||||
trim_if_present(compclass, 'org.apache.cassandra.io.compress.')
|
||||
if layoutoption == 'compaction_strategy_options':
|
||||
optmap['class'] = compaction_strategy
|
||||
|
||||
if self.cqlver_atleast(3) and not self.is_cql3_beta():
|
||||
cf_opts.append((cql3option, optmap))
|
||||
else:
|
||||
|
|
@ -1356,8 +1389,9 @@ class Shell(cmd.Cmd):
|
|||
print
|
||||
if ksname is None:
|
||||
for k in self.get_keyspaces():
|
||||
print 'Keyspace %s' % (k.name,)
|
||||
print '---------%s' % ('-' * len(k.name))
|
||||
name = self.cql_protect_name(k.name)
|
||||
print 'Keyspace %s' % (name,)
|
||||
print '---------%s' % ('-' * len(name))
|
||||
cmd.Cmd.columnize(self, self.get_columnfamily_names(k.name))
|
||||
print
|
||||
else:
|
||||
|
|
@ -2045,7 +2079,7 @@ def raw_option_with_default(configs, section, option, default=None):
|
|||
def should_use_color():
|
||||
if not sys.stdout.isatty():
|
||||
return False
|
||||
if os.environ.get('TERM', 'dumb') == 'dumb':
|
||||
if os.environ.get('TERM', '') in ('dumb', ''):
|
||||
return False
|
||||
try:
|
||||
import subprocess
|
||||
|
|
@ -2053,7 +2087,7 @@ def should_use_color():
|
|||
stdout, _ = p.communicate()
|
||||
if int(stdout.strip()) < 8:
|
||||
return False
|
||||
except (OSError, ImportError):
|
||||
except (OSError, ImportError, ValueError):
|
||||
# oh well, we tried. at least we know there's a $TERM and it's
|
||||
# not "dumb".
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class UnexpectedTableStructure(UserWarning):
|
|||
def __str__(self):
|
||||
return 'Unexpected table structure; may not translate correctly to CQL. ' + self.msg
|
||||
|
||||
SYSTEM_KEYSPACES = ('system', 'system_traces')
|
||||
SYSTEM_KEYSPACES = ('system', 'system_auth', 'system_traces')
|
||||
|
||||
class Cql3ParsingRuleSet(CqlParsingRuleSet):
|
||||
keywords = set((
|
||||
|
|
@ -58,39 +58,37 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet):
|
|||
columnfamily_options = (
|
||||
# (CQL option name, Thrift option name (or None if same))
|
||||
('comment', None),
|
||||
('comparator', 'comparator_type'),
|
||||
('read_repair_chance', None),
|
||||
('gc_grace_seconds', None),
|
||||
('default_validation', 'default_validation_class'),
|
||||
('replicate_on_write', None),
|
||||
('compaction_strategy_class', 'compaction_strategy'),
|
||||
('comparator', 'comparator_type'),
|
||||
('default_validation', 'default_validation_class'),
|
||||
('gc_grace_seconds', None),
|
||||
('index_interval', None),
|
||||
('read_repair_chance', None),
|
||||
('replicate_on_write', None),
|
||||
)
|
||||
|
||||
old_columnfamily_layout_options = (
|
||||
# (CQL3 option name, schema_columnfamilies column name (or None if same))
|
||||
('comment', None),
|
||||
('bloom_filter_fp_chance', None),
|
||||
('caching', None),
|
||||
('read_repair_chance', None),
|
||||
('comment', None),
|
||||
('compaction_strategy_class', None),
|
||||
('dclocal_read_repair_chance', 'local_read_repair_chance'),
|
||||
('gc_grace_seconds', None),
|
||||
('replicate_on_write', None),
|
||||
('compaction_strategy_class', None),
|
||||
('index_interval', None),
|
||||
('read_repair_chance', None),
|
||||
('replicate_on_write', None),
|
||||
)
|
||||
|
||||
new_columnfamily_layout_options = (
|
||||
('comment', None),
|
||||
('bloom_filter_fp_chance', None),
|
||||
('caching', None),
|
||||
('read_repair_chance', None),
|
||||
('comment', None),
|
||||
('dclocal_read_repair_chance', 'local_read_repair_chance'),
|
||||
('gc_grace_seconds', None),
|
||||
('replicate_on_write', None),
|
||||
('default_read_consistency', None),
|
||||
('default_write_consistency', None),
|
||||
('index_interval', None),
|
||||
('read_repair_chance', None),
|
||||
('replicate_on_write', None),
|
||||
)
|
||||
|
||||
old_columnfamily_layout_map_options = (
|
||||
|
|
@ -106,18 +104,18 @@ class Cql3ParsingRuleSet(CqlParsingRuleSet):
|
|||
# (CQL3 option name, schema_columnfamilies column name (or None if same),
|
||||
# list of known map keys)
|
||||
('compaction', 'compaction_strategy_options',
|
||||
('min_threshold', 'max_threshold')),
|
||||
('class', 'min_threshold', 'max_threshold')),
|
||||
('compression', 'compression_parameters',
|
||||
('sstable_compression', 'chunk_length_kb', 'crc_check_chance')),
|
||||
)
|
||||
|
||||
new_obsolete_cf_options = (
|
||||
'compaction_parameters',
|
||||
'compaction_strategy_class',
|
||||
'compaction_strategy_options',
|
||||
'min_compaction_threshold',
|
||||
'max_compaction_threshold',
|
||||
'compaction_parameters',
|
||||
'compression_parameters',
|
||||
'max_compaction_threshold',
|
||||
'min_compaction_threshold',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1517,7 +1515,7 @@ class CqlTableDef:
|
|||
if len(self.column_aliases) == 0:
|
||||
if self.comparator is not UTF8Type:
|
||||
warn(UnexpectedTableStructure("Compact storage CF %s has no column aliases,"
|
||||
" but comparator is not UTF8Type." % (self,)))
|
||||
" but comparator is not UTF8Type." % (self.name,)))
|
||||
colalias_types = []
|
||||
elif issubclass(self.comparator, CompositeType):
|
||||
colalias_types = self.comparator.subtypes
|
||||
|
|
|
|||
|
|
@ -53,20 +53,24 @@ class FormattedValue:
|
|||
else:
|
||||
return ''
|
||||
|
||||
def ljust(self, width, fill=' '):
|
||||
def ljust(self, width, fill=' ', color=False):
|
||||
"""
|
||||
Similar to self.strval.ljust(width), but takes expected terminal
|
||||
display width into account for special characters, and does not
|
||||
take color escape codes into account.
|
||||
"""
|
||||
if color:
|
||||
return self.color_ljust(width, fill=fill)
|
||||
return self.strval + self._pad(width, fill)
|
||||
|
||||
def rjust(self, width, fill=' '):
|
||||
def rjust(self, width, fill=' ', color=False):
|
||||
"""
|
||||
Similar to self.strval.rjust(width), but takes expected terminal
|
||||
display width into account for special characters, and does not
|
||||
take color escape codes into account.
|
||||
"""
|
||||
if color:
|
||||
return self.color_rjust(width, fill=fill)
|
||||
return self._pad(width, fill) + self.strval
|
||||
|
||||
def color_rjust(self, width, fill=' '):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import re
|
||||
import time
|
||||
import binascii
|
||||
from collections import defaultdict
|
||||
from . import wcwidth
|
||||
from .displaying import colorme, FormattedValue, DEFAULT_VALUE_COLORS
|
||||
|
|
@ -66,13 +67,29 @@ def format_by_type(cqltype, val, encoding, colormap=None, addcolor=False,
|
|||
time_format=time_format, float_precision=float_precision,
|
||||
nullval=nullval)
|
||||
|
||||
def color_text(bval, colormap, displaywidth=None):
|
||||
# note that here, we render natural backslashes as just backslashes,
|
||||
# in the same color as surrounding text, when using color. When not
|
||||
# using color, we need to double up the backslashes so it's not
|
||||
# ambiguous. This introduces the unique difficulty of having different
|
||||
# display widths for the colored and non-colored versions. To avoid
|
||||
# adding the smarts to handle that in to FormattedValue, we just
|
||||
# make an explicit check to see if a null colormap is being used or
|
||||
# not.
|
||||
|
||||
if displaywidth is None:
|
||||
displaywidth = len(bval)
|
||||
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
|
||||
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
|
||||
if colormap['text']:
|
||||
displaywidth -= bval.count(r'\\')
|
||||
return FormattedValue(bval, coloredval, displaywidth)
|
||||
|
||||
def format_value_default(val, colormap, **_):
|
||||
val = str(val)
|
||||
escapedval = val.replace('\\', '\\\\')
|
||||
bval = controlchars_re.sub(_show_control_chars, escapedval)
|
||||
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
|
||||
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
|
||||
return FormattedValue(bval, coloredval)
|
||||
return color_text(bval, colormap)
|
||||
|
||||
# Mapping cql type base names ("int", "map", etc) to formatter functions,
|
||||
# making format_value a generic function
|
||||
|
|
@ -164,9 +181,7 @@ def format_value_text(val, encoding, colormap, **_):
|
|||
escapedval = unicode_controlchars_re.sub(_show_control_chars, escapedval)
|
||||
bval = escapedval.encode(encoding, 'backslashreplace')
|
||||
displaywidth = wcwidth.wcswidth(bval.decode(encoding))
|
||||
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
|
||||
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
|
||||
return FormattedValue(bval, coloredval)
|
||||
return color_text(bval, colormap, displaywidth)
|
||||
|
||||
# name alias
|
||||
formatter_for('varchar')(format_value_text)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# 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.
|
||||
|
||||
from .cassconnect import create_test_db, remove_test_db
|
||||
|
||||
setUp = create_test_db
|
||||
tearDown = remove_test_db
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
# 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.
|
||||
|
||||
import re
|
||||
|
||||
LIGHT = 010
|
||||
|
||||
ansi_CSI = '\033['
|
||||
ansi_seq = re.compile(re.escape(ansi_CSI) + r'(?P<params>[\x20-\x3f]*)(?P<final>[\x40-\x7e])')
|
||||
ansi_cmd_SGR = 'm' # set graphics rendition
|
||||
|
||||
color_defs = (
|
||||
(000, 'k', 'black'),
|
||||
(001, 'r', 'dark red'),
|
||||
(002, 'g', 'dark green'),
|
||||
(003, 'w', 'brown', 'dark yellow'),
|
||||
(004, 'b', 'dark blue'),
|
||||
(005, 'm', 'dark magenta', 'dark purple'),
|
||||
(006, 'c', 'dark cyan'),
|
||||
(007, 'n', 'light grey', 'light gray', 'neutral', 'dark white'),
|
||||
(010, 'B', 'dark grey', 'dark gray', 'light black'),
|
||||
(011, 'R', 'red', 'light red'),
|
||||
(012, 'G', 'green', 'light green'),
|
||||
(013, 'Y', 'yellow', 'light yellow'),
|
||||
(014, 'B', 'blue', 'light blue'),
|
||||
(015, 'M', 'magenta', 'purple', 'light magenta', 'light purple'),
|
||||
(016, 'C', 'cyan', 'light cyan'),
|
||||
(017, 'W', 'white', 'light white'),
|
||||
)
|
||||
|
||||
colors_by_num = {}
|
||||
colors_by_letter = {}
|
||||
colors_by_name = {}
|
||||
letters_by_num = {}
|
||||
|
||||
for colordef in color_defs:
|
||||
colorcode = colordef[0]
|
||||
colorletter = colordef[1]
|
||||
colors_by_num[colorcode] = nameset = set(colordef[2:])
|
||||
colors_by_letter[colorletter] = colorcode
|
||||
letters_by_num[colorcode] = colorletter
|
||||
for c in list(nameset):
|
||||
# equivalent names without spaces
|
||||
nameset.add(c.replace(' ', ''))
|
||||
for c in list(nameset):
|
||||
# with "bright" being an alias for "light"
|
||||
nameset.add(c.replace('light', 'bright'))
|
||||
for c in nameset:
|
||||
colors_by_name[c] = colorcode
|
||||
|
||||
class ColoredChar:
|
||||
def __init__(self, c, colorcode):
|
||||
self.c = c
|
||||
self._colorcode = colorcode
|
||||
|
||||
def colorcode(self):
|
||||
return self._colorcode
|
||||
|
||||
def plain(self):
|
||||
return self.c
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.c, name)
|
||||
|
||||
def ansi_color(self):
|
||||
clr = str(30 + (07 & self._colorcode))
|
||||
if self._colorcode & 010:
|
||||
clr = '1;' + clr
|
||||
return clr
|
||||
|
||||
def __str__(self):
|
||||
return "<%s '%r'>" % (self.__class__.__name__, self.colored_repr())
|
||||
__repr__ = __str__
|
||||
|
||||
def colored_version(self):
|
||||
return '%s0;%sm%s%s0m' % (ansi_CSI, self.ansi_color(), self.c, ansi_CSI)
|
||||
|
||||
def colored_repr(self):
|
||||
if self.c == "'":
|
||||
crepr = r"\'"
|
||||
elif self.c == '"':
|
||||
crepr = self.c
|
||||
else:
|
||||
crepr = repr(self.c)[1:-1]
|
||||
return '%s0;%sm%s%s0m' % (ansi_CSI, self.ansi_color(), crepr, ansi_CSI)
|
||||
|
||||
def colortag(self):
|
||||
return lookup_letter_from_code(self._colorcode)
|
||||
|
||||
class ColoredText:
|
||||
def __init__(self, source=''):
|
||||
if isinstance(source, basestring):
|
||||
plain, colors = self.parse_ansi_colors(source)
|
||||
self.chars = map(ColoredChar, plain, colors)
|
||||
else:
|
||||
# expected that source is an iterable of ColoredChars (or duck-typed as such)
|
||||
self.chars = tuple(source)
|
||||
|
||||
def splitlines(self):
|
||||
lines = [[]]
|
||||
for c in self.chars:
|
||||
if c.plain() == '\n':
|
||||
lines.append([])
|
||||
else:
|
||||
lines[-1].append(c)
|
||||
return [self.__class__(line) for line in lines]
|
||||
|
||||
def plain(self):
|
||||
return ''.join([c.plain() for c in self.chars])
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.chars[index]
|
||||
|
||||
@classmethod
|
||||
def parse_ansi_colors(cls, source):
|
||||
# note: strips all control sequences, even if not SGRs.
|
||||
colors = []
|
||||
plain = ''
|
||||
last = 0
|
||||
curclr = 0
|
||||
for match in ansi_seq.finditer(source):
|
||||
prevsegment = source[last:match.start()]
|
||||
plain += prevsegment
|
||||
colors.extend([curclr] * len(prevsegment))
|
||||
if match.group('final') == ansi_cmd_SGR:
|
||||
try:
|
||||
curclr = cls.parse_sgr_param(curclr, match.group('params'))
|
||||
except ValueError:
|
||||
pass
|
||||
last = match.end()
|
||||
prevsegment = source[last:]
|
||||
plain += prevsegment
|
||||
colors.extend([curclr] * len(prevsegment))
|
||||
return ''.join(plain), colors
|
||||
|
||||
@staticmethod
|
||||
def parse_sgr_param(curclr, paramstr):
|
||||
oldclr = curclr
|
||||
args = map(int, paramstr.split(';'))
|
||||
for a in args:
|
||||
if a == 0:
|
||||
curclr = lookup_colorcode('neutral')
|
||||
elif a == 1:
|
||||
curclr |= LIGHT
|
||||
elif 30 <= a <= 37:
|
||||
curclr = (curclr & LIGHT) | (a - 30)
|
||||
else:
|
||||
# not supported renditions here; ignore for now
|
||||
pass
|
||||
return curclr
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s '%s'>" % (self.__class__.__name__, ''.join([c.colored_repr() for c in self.chars]))
|
||||
__str__ = __repr__
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.chars)
|
||||
|
||||
def colored_version(self):
|
||||
return ''.join([c.colored_version() for c in self.chars])
|
||||
|
||||
def colortags(self):
|
||||
return ''.join([c.colortag() for c in self.chars])
|
||||
|
||||
def lookup_colorcode(name):
|
||||
return colors_by_name[name]
|
||||
|
||||
def lookup_colorname(code):
|
||||
return colors_by_num.get(code, 'Unknown-color-0%o' % code)
|
||||
|
||||
def lookup_colorletter(letter):
|
||||
return colors_by_letter[letter]
|
||||
|
||||
def lookup_letter_from_code(code):
|
||||
letr = letters_by_num.get(code, ' ')
|
||||
if letr == 'n':
|
||||
letr = ' '
|
||||
return letr
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from itertools import izip
|
||||
from os.path import dirname, join, normpath, islink
|
||||
|
||||
cqlshlog = logging.getLogger('test_cqlsh')
|
||||
|
||||
try:
|
||||
# a backport of python2.7 unittest features, so we can test against older
|
||||
# pythons as necessary. python2.7 users who don't care about testing older
|
||||
# versions need not install.
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
rundir = dirname(__file__)
|
||||
path_to_cqlsh = normpath(join(rundir, '..', '..', '..', 'bin', 'cqlsh'))
|
||||
|
||||
# symlink a ".py" file to cqlsh main script, so we can load it as a module
|
||||
modulepath = join(rundir, 'cqlsh.py')
|
||||
try:
|
||||
if islink(modulepath):
|
||||
os.unlink(modulepath)
|
||||
except OSError:
|
||||
pass
|
||||
os.symlink(path_to_cqlsh, modulepath)
|
||||
|
||||
sys.path.append(rundir)
|
||||
import cqlsh
|
||||
cql = cqlsh.cql
|
||||
|
||||
TEST_HOST = os.environ.get('CQL_TEST_HOST', 'localhost')
|
||||
TEST_PORT = int(os.environ.get('CQL_TEST_PORT', 9160))
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
def assertNicelyFormattedTableHeader(self, line, msg=None):
|
||||
return self.assertRegexpMatches(line, r'^ +\w+( +\| \w+)*\s*$', msg=msg)
|
||||
|
||||
def assertNicelyFormattedTableRule(self, line, msg=None):
|
||||
return self.assertRegexpMatches(line, r'^-+(\+-+)*\s*$', msg=msg)
|
||||
|
||||
def assertNicelyFormattedTableData(self, line, msg=None):
|
||||
return self.assertRegexpMatches(line, r'^ .* \| ', msg=msg)
|
||||
|
||||
def dedent(s):
|
||||
lines = [ln.rstrip() for ln in s.splitlines()]
|
||||
if lines[0] == '':
|
||||
lines = lines[1:]
|
||||
spaces = [len(line) - len(line.lstrip()) for line in lines if line]
|
||||
minspace = min(spaces if len(spaces) > 0 else (0,))
|
||||
return '\n'.join(line[minspace:] for line in lines)
|
||||
|
||||
def at_a_time(i, num):
|
||||
return izip(*([iter(i)] * num))
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
# 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.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import contextlib
|
||||
import tempfile
|
||||
import os.path
|
||||
from .basecase import cql, cqlsh, cqlshlog, TEST_HOST, TEST_PORT, rundir
|
||||
from .run_cqlsh import run_cqlsh, call_cqlsh
|
||||
|
||||
test_keyspace_init2 = os.path.join(rundir, 'test_keyspace_init2.cql')
|
||||
test_keyspace_init3 = os.path.join(rundir, 'test_keyspace_init3.cql')
|
||||
|
||||
def get_cassandra_connection(cql_version=None):
|
||||
if cql_version is None:
|
||||
cql_version = '2.0.0'
|
||||
conn = cql.connect(TEST_HOST, TEST_PORT, cql_version=cql_version)
|
||||
# until the cql lib does this for us
|
||||
conn.cql_version = cql_version
|
||||
return conn
|
||||
|
||||
def get_cassandra_cursor(cql_version=None):
|
||||
return get_cassandra_connection(cql_version=cql_version).cursor()
|
||||
|
||||
TEST_KEYSPACES_CREATED = []
|
||||
|
||||
def get_test_keyspace():
|
||||
return TEST_KEYSPACES_CREATED[-1]
|
||||
|
||||
def make_test_ks_name():
|
||||
# abuse mktemp to get a quick random-ish name
|
||||
return os.path.basename(tempfile.mktemp(prefix='CqlshTests_'))
|
||||
|
||||
def create_test_keyspace(cursor):
|
||||
ksname = make_test_ks_name()
|
||||
qksname = quote_name(cursor, ksname)
|
||||
cursor.execute('''
|
||||
CREATE KEYSPACE %s WITH strategy_class = 'SimpleStrategy'
|
||||
AND strategy_options:replication_factor = 1;
|
||||
''' % quote_name(cursor, ksname))
|
||||
cursor.execute('USE %s;' % qksname)
|
||||
TEST_KEYSPACES_CREATED.append(ksname)
|
||||
return ksname
|
||||
|
||||
def split_cql_commands(source, cqlver='2.0.0'):
|
||||
ruleset = cql_rule_set(cqlver)
|
||||
statements, in_batch = ruleset.cql_split_statements(source)
|
||||
if in_batch:
|
||||
raise ValueError("CQL source ends unexpectedly")
|
||||
|
||||
return [ruleset.cql_extract_orig(toks, source) for toks in statements if toks]
|
||||
|
||||
def execute_cql_commands(cursor, source, logprefix='INIT: '):
|
||||
for cql in split_cql_commands(source, cqlver=cursor._connection.cql_version):
|
||||
cqlshlog.debug(logprefix + cql)
|
||||
cursor.execute(cql)
|
||||
|
||||
def execute_cql_file(cursor, fname):
|
||||
with open(fname) as f:
|
||||
return execute_cql_commands(cursor, f.read())
|
||||
|
||||
def populate_test_db_cql3(cursor):
|
||||
execute_cql_file(cursor, test_keyspace_init3)
|
||||
|
||||
def populate_test_db_cql2(cursor):
|
||||
execute_cql_file(cursor, test_keyspace_init2)
|
||||
|
||||
def create_test_db():
|
||||
with cassandra_cursor(ks=None) as c:
|
||||
k = create_test_keyspace(c)
|
||||
populate_test_db_cql2(c)
|
||||
with cassandra_cursor(ks=k, cql_version='3.0.0') as c:
|
||||
populate_test_db_cql3(c)
|
||||
return k
|
||||
|
||||
def remove_test_db():
|
||||
with cassandra_cursor(ks=None) as c:
|
||||
c.execute('DROP KEYSPACE %s' % quote_name(c, TEST_KEYSPACES_CREATED.pop(-1)))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def cassandra_connection(cql_version=None):
|
||||
"""
|
||||
Make a Cassandra CQL connection with the given CQL version and get a cursor
|
||||
for it, and optionally connect to a given keyspace.
|
||||
|
||||
The connection is returned as the context manager's value, and it will be
|
||||
closed when the context exits.
|
||||
"""
|
||||
|
||||
conn = get_cassandra_connection(cql_version=cql_version)
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def cassandra_cursor(cql_version=None, ks=''):
|
||||
"""
|
||||
Make a Cassandra CQL connection with the given CQL version and get a cursor
|
||||
for it, and optionally connect to a given keyspace. If ks is the empty
|
||||
string (default), connect to the last test keyspace created. If ks is None,
|
||||
do not connect to any keyspace. Otherwise, attempt to connect to the
|
||||
keyspace named.
|
||||
|
||||
The cursor is returned as the context manager's value, and the connection
|
||||
will be closed when the context exits.
|
||||
"""
|
||||
|
||||
if ks == '':
|
||||
ks = get_test_keyspace()
|
||||
conn = get_cassandra_connection(cql_version=cql_version)
|
||||
try:
|
||||
c = conn.cursor()
|
||||
if ks is not None:
|
||||
c.execute('USE %s;' % quote_name(c, ks))
|
||||
yield c
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def cql_rule_set(cqlver):
|
||||
if str(cqlver).startswith('2'):
|
||||
return cqlsh.cqlhandling.CqlRuleSet
|
||||
else:
|
||||
return cqlsh.cql3handling.CqlRuleSet
|
||||
|
||||
def quote_name(cqlver, name):
|
||||
if isinstance(cqlver, cql.cursor.Cursor):
|
||||
cqlver = cqlver._connection
|
||||
if isinstance(cqlver, cql.connection.Connection):
|
||||
cqlver = cqlver.cql_version
|
||||
return cql_rule_set(cqlver).maybe_escape_name(name)
|
||||
|
||||
class DEFAULTVAL: pass
|
||||
|
||||
def testrun_cqlsh(keyspace=DEFAULTVAL, **kwargs):
|
||||
# use a positive default sentinel so that keyspace=None can be used
|
||||
# to override the default behavior
|
||||
if keyspace is DEFAULTVAL:
|
||||
keyspace = get_test_keyspace()
|
||||
return run_cqlsh(keyspace=keyspace, **kwargs)
|
||||
|
||||
def testcall_cqlsh(keyspace=None, **kwargs):
|
||||
if keyspace is None:
|
||||
keyspace = get_test_keyspace()
|
||||
return call_cqlsh(keyspace=keyspace, **kwargs)
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
# 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.
|
||||
|
||||
# NOTE: this testing tool is *nix specific
|
||||
|
||||
import os
|
||||
import re
|
||||
import pty
|
||||
import fcntl
|
||||
import contextlib
|
||||
import subprocess
|
||||
import signal
|
||||
import math
|
||||
from time import time
|
||||
from . import basecase
|
||||
|
||||
DEFAULT_CQLSH_PROMPT = '\ncqlsh(:\S+)?> '
|
||||
DEFAULT_CQLSH_TERM = 'xterm'
|
||||
|
||||
cqlshlog = basecase.cqlshlog
|
||||
|
||||
def set_controlling_pty(master, slave):
|
||||
os.setsid()
|
||||
os.close(master)
|
||||
for i in range(3):
|
||||
os.dup2(slave, i)
|
||||
if slave > 2:
|
||||
os.close(slave)
|
||||
os.close(os.open(os.ttyname(1), os.O_RDWR))
|
||||
|
||||
def set_nonblocking(fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raising_signal(signum, exc):
|
||||
"""
|
||||
Within the wrapped context, the given signal will interrupt signal
|
||||
calls and will raise the given exception class. The preexisting signal
|
||||
handling will be reinstated on context exit.
|
||||
"""
|
||||
def raiser(signum, frames):
|
||||
raise exc()
|
||||
oldhandlr = signal.signal(signum, raiser)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
signal.signal(signum, oldhandlr)
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
@contextlib.contextmanager
|
||||
def timing_out_itimer(seconds):
|
||||
if seconds is None:
|
||||
yield
|
||||
return
|
||||
with raising_signal(signal.SIGALRM, TimeoutError):
|
||||
oldval, oldint = signal.getitimer(signal.ITIMER_REAL)
|
||||
if oldval != 0.0:
|
||||
raise RuntimeError("ITIMER_REAL already in use")
|
||||
signal.setitimer(signal.ITIMER_REAL, seconds)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
signal.setitimer(signal.ITIMER_REAL, 0)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def timing_out_alarm(seconds):
|
||||
if seconds is None:
|
||||
yield
|
||||
return
|
||||
with raising_signal(signal.SIGALRM, TimeoutError):
|
||||
oldval = signal.alarm(int(math.ceil(seconds)))
|
||||
if oldval != 0:
|
||||
signal.alarm(oldval)
|
||||
raise RuntimeError("SIGALRM already in use")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
|
||||
# setitimer is new in 2.6, but it's still worth supporting, for potentially
|
||||
# faster tests because of sub-second resolution on timeouts.
|
||||
if hasattr(signal, 'setitimer'):
|
||||
timing_out = timing_out_itimer
|
||||
else:
|
||||
timing_out = timing_out_alarm
|
||||
|
||||
def noop(*a):
|
||||
pass
|
||||
|
||||
class ProcRunner:
|
||||
def __init__(self, path, tty=True, env=None, args=()):
|
||||
self.exe_path = path
|
||||
self.args = args
|
||||
self.tty = bool(tty)
|
||||
if env is None:
|
||||
env = {}
|
||||
self.env = env
|
||||
self.readbuf = ''
|
||||
|
||||
self.start_proc()
|
||||
|
||||
def start_proc(self):
|
||||
preexec = noop
|
||||
stdin = stdout = stderr = None
|
||||
if self.tty:
|
||||
masterfd, slavefd = pty.openpty()
|
||||
preexec = lambda: set_controlling_pty(masterfd, slavefd)
|
||||
else:
|
||||
stdin = stdout = subprocess.PIPE
|
||||
stderr = subprocess.STDOUT
|
||||
cqlshlog.info("Spawning %r subprocess with args: %r and env: %r"
|
||||
% (self.exe_path, self.args, self.env))
|
||||
self.proc = subprocess.Popen((self.exe_path,) + tuple(self.args),
|
||||
env=self.env, preexec_fn=preexec,
|
||||
stdin=stdin, stdout=stdout, stderr=stderr,
|
||||
close_fds=False)
|
||||
if self.tty:
|
||||
os.close(slavefd)
|
||||
self.childpty = masterfd
|
||||
self.send = self.send_tty
|
||||
self.read = self.read_tty
|
||||
else:
|
||||
self.send = self.send_pipe
|
||||
self.read = self.read_pipe
|
||||
|
||||
def close(self):
|
||||
cqlshlog.info("Closing %r subprocess." % (self.exe_path,))
|
||||
if self.tty:
|
||||
os.close(self.childpty)
|
||||
else:
|
||||
self.proc.stdin.close()
|
||||
cqlshlog.debug("Waiting for exit")
|
||||
return self.proc.wait()
|
||||
|
||||
def send_tty(self, data):
|
||||
os.write(self.childpty, data)
|
||||
|
||||
def send_pipe(self, data):
|
||||
self.proc.stdin.write(data)
|
||||
|
||||
def read_tty(self, blksize):
|
||||
return os.read(self.childpty, blksize)
|
||||
|
||||
def read_pipe(self, blksize):
|
||||
return self.proc.stdout.read(blksize)
|
||||
|
||||
def read_until(self, until, blksize=4096, timeout=None, flags=0):
|
||||
if not isinstance(until, re._pattern_type):
|
||||
until = re.compile(until, flags)
|
||||
got = self.readbuf
|
||||
self.readbuf = ''
|
||||
with timing_out(timeout):
|
||||
while True:
|
||||
val = self.read(blksize)
|
||||
cqlshlog.debug("read %r from subproc" % (val,))
|
||||
if val == '':
|
||||
raise EOFError("'until' pattern %r not found" % (until.pattern,))
|
||||
got += val
|
||||
m = until.search(got)
|
||||
if m is not None:
|
||||
self.readbuf = got[m.end():]
|
||||
got = got[:m.end()]
|
||||
return got
|
||||
|
||||
def read_lines(self, numlines, blksize=4096, timeout=None):
|
||||
lines = []
|
||||
with timing_out(timeout):
|
||||
for n in range(numlines):
|
||||
lines.append(self.read_until('\n', blksize=blksize))
|
||||
return lines
|
||||
|
||||
def read_up_to_timeout(self, timeout, blksize=4096):
|
||||
got = self.readbuf
|
||||
self.readbuf = ''
|
||||
curtime = time()
|
||||
stoptime = curtime + timeout
|
||||
while curtime < stoptime:
|
||||
try:
|
||||
with timing_out(stoptime - curtime):
|
||||
stuff = self.read(blksize)
|
||||
except TimeoutError:
|
||||
break
|
||||
cqlshlog.debug("read %r from subproc" % (stuff,))
|
||||
if stuff == '':
|
||||
break
|
||||
got += stuff
|
||||
curtime = time()
|
||||
return got
|
||||
|
||||
class CqlshRunner(ProcRunner):
|
||||
def __init__(self, path=None, host=None, port=None, keyspace=None, cqlver=None,
|
||||
args=(), prompt=DEFAULT_CQLSH_PROMPT, env=None, **kwargs):
|
||||
if path is None:
|
||||
path = basecase.path_to_cqlsh
|
||||
if host is None:
|
||||
host = basecase.TEST_HOST
|
||||
if port is None:
|
||||
port = basecase.TEST_PORT
|
||||
if env is None:
|
||||
env = {}
|
||||
env.setdefault('TERM', 'xterm')
|
||||
env.setdefault('CQLSH_NO_BUNDLED', os.environ.get('CQLSH_NO_BUNDLED', ''))
|
||||
env.setdefault('PYTHONPATH', os.environ.get('PYTHONPATH', ''))
|
||||
args = tuple(args) + (host, str(port))
|
||||
if cqlver is not None:
|
||||
args += ('--cqlversion', str(cqlver))
|
||||
if keyspace is not None:
|
||||
args += ('--keyspace', keyspace)
|
||||
self.keyspace = keyspace
|
||||
ProcRunner.__init__(self, path, args=args, env=env, **kwargs)
|
||||
self.prompt = prompt
|
||||
if self.prompt is None:
|
||||
self.output_header = ''
|
||||
else:
|
||||
self.output_header = self.read_to_next_prompt()
|
||||
|
||||
def read_to_next_prompt(self):
|
||||
return self.read_until(self.prompt, timeout=4.0)
|
||||
|
||||
def read_up_to_timeout(self, timeout, blksize=4096):
|
||||
output = ProcRunner.read_up_to_timeout(self, timeout, blksize=blksize)
|
||||
# readline trying to be friendly- remove these artifacts
|
||||
output = output.replace(' \r', '')
|
||||
output = output.replace('\r', '')
|
||||
return output
|
||||
|
||||
def cmd_and_response(self, cmd):
|
||||
self.send(cmd + '\n')
|
||||
output = self.read_to_next_prompt()
|
||||
# readline trying to be friendly- remove these artifacts
|
||||
output = output.replace(' \r', '')
|
||||
output = output.replace('\r', '')
|
||||
if self.tty:
|
||||
echo, output = output.split('\n', 1)
|
||||
assert echo == cmd, "unexpected echo %r instead of %r" % (echo, cmd)
|
||||
try:
|
||||
output, promptline = output.rsplit('\n', 1)
|
||||
except ValueError:
|
||||
promptline = output
|
||||
output = ''
|
||||
assert re.match(self.prompt, '\n' + promptline), \
|
||||
'last line of output %r does not match %r?' % (promptline, self.prompt)
|
||||
return output + '\n'
|
||||
|
||||
def run_cqlsh(**kwargs):
|
||||
return contextlib.closing(CqlshRunner(**kwargs))
|
||||
|
||||
def call_cqlsh(**kwargs):
|
||||
kwargs.setdefault('prompt', None)
|
||||
proginput = kwargs.pop('input', '')
|
||||
kwargs['tty'] = False
|
||||
c = CqlshRunner(**kwargs)
|
||||
output, _ = c.proc.communicate(proginput)
|
||||
result = c.close()
|
||||
return output, result
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
-- type A: single-column PK, compact storage
|
||||
-- FAILS: CREATE TABLE type_a_1 (a int PRIMARY KEY) WITH COMPACT STORAGE;
|
||||
-- Bad Request: No definition found that is not part of the PRIMARY KEY
|
||||
CREATE TABLE type_a_2 (a int PRIMARY KEY, b int) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_a_3 (a int PRIMARY KEY, b int, c int) WITH COMPACT STORAGE;
|
||||
|
||||
-- type B: single-column PK, dynamic storage
|
||||
CREATE TABLE type_b_1 (a int PRIMARY KEY);
|
||||
CREATE TABLE type_b_2 (a int PRIMARY KEY, b int);
|
||||
CREATE TABLE type_b_3 (a int PRIMARY KEY, b int, c int);
|
||||
|
||||
-- type C: compound PK, plain partition key, compact storage
|
||||
CREATE TABLE type_c_2_2 (a int, b int, PRIMARY KEY (a, b)) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_c_3_3 (a int, b int, c int, PRIMARY KEY (a, b, c)) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_c_3_2 (a int, b int, c int, PRIMARY KEY (a, b)) WITH COMPACT STORAGE;
|
||||
-- FAILS: CREATE TABLE type_c_4_2 (a int, b int, c int, d int, PRIMARY KEY (a, b)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: COMPACT STORAGE WITH composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: d, c)
|
||||
CREATE TABLE type_c_4_3 (a int, b int, c int, d int, PRIMARY KEY (a, b, c)) WITH COMPACT STORAGE;
|
||||
|
||||
-- type D: compound PK, plain partition key, dynamic storage
|
||||
CREATE TABLE type_d_2_2 (a int, b int, PRIMARY KEY (a, b));
|
||||
CREATE TABLE type_d_3_2 (a int, b int, c int, PRIMARY KEY (a, b));
|
||||
CREATE TABLE type_d_3_3 (a int, b int, c int, PRIMARY KEY (a, b, c));
|
||||
CREATE TABLE type_d_4_2 (a int, b int, c int, d int, PRIMARY KEY (a, b));
|
||||
|
||||
-- type E: compound PK, multipart partition key, all key components used in partitioning,
|
||||
-- compact storage
|
||||
-- FAILS: CREATE TABLE type_e_2_2 (a int, b int, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE;
|
||||
-- Bad Request: No definition found that is not part of the PRIMARY KEY
|
||||
CREATE TABLE type_e_3_2 (a int, b int, c int, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_e_4_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_e_4_3 (a int, b int, c int, d int, PRIMARY KEY ((a, b, c))) WITH COMPACT STORAGE;
|
||||
|
||||
-- type F: compound PK, multipart partition key, all key components used in partitioning,
|
||||
-- dynamic storage
|
||||
CREATE TABLE type_f_2_2 (a int, b int, PRIMARY KEY ((a, b)));
|
||||
CREATE TABLE type_f_3_2 (a int, b int, c int, PRIMARY KEY ((a, b)));
|
||||
CREATE TABLE type_f_4_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b)));
|
||||
CREATE TABLE type_f_4_3 (a int, b int, c int, d int, PRIMARY KEY ((a, b, c)));
|
||||
|
||||
-- type G: compound PK, multipart partition key, not all key components used in partitioning,
|
||||
-- compact storage
|
||||
CREATE TABLE type_g_3_3_2 (a int, b int, c int, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_g_4_3_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_g_4_4_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b), c, d)) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_g_4_4_3 (a int, b int, c int, d int, PRIMARY KEY ((a, b, c), d)) WITH COMPACT STORAGE;
|
||||
-- FAILS: CREATE TABLE type_g_5_3_2 (a int, b int, c int, d int, e int, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: COMPACT STORAGE with composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: d, e)
|
||||
CREATE TABLE type_g_5_4_2 (a int, b int, c int, d int, e int, PRIMARY KEY ((a, b), c, d)) WITH COMPACT STORAGE;
|
||||
|
||||
-- type H: compound PK, multipart partition key, not all key components used in partitioning,
|
||||
-- dynamic storage
|
||||
CREATE TABLE type_h_3_3_2 (a int, b int, c int, PRIMARY KEY ((a, b), c));
|
||||
CREATE TABLE type_h_4_3_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b), c));
|
||||
CREATE TABLE type_h_4_4_2 (a int, b int, c int, d int, PRIMARY KEY ((a, b), c, d));
|
||||
CREATE TABLE type_h_4_4_3 (a int, b int, c int, d int, PRIMARY KEY ((a, b, c), d));
|
||||
CREATE TABLE type_h_5_3_2 (a int, b int, c int, d int, e int, PRIMARY KEY ((a, b), c));
|
||||
CREATE TABLE type_h_5_4_2 (a int, b int, c int, d int, e int, PRIMARY KEY ((a, b), c, d));
|
||||
|
||||
-- type A with collections (these should fail, but don't)
|
||||
CREATE TABLE type_aa_2_2 (a int PRIMARY KEY, b map<float, text>) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_aa_3_2 (a int PRIMARY KEY, b map<float, text>, c int) WITH COMPACT STORAGE;
|
||||
|
||||
-- type B with collections
|
||||
CREATE TABLE type_bb_2_2 (a int PRIMARY KEY, b map<float, text>);
|
||||
CREATE TABLE type_bb_3_2 (a int PRIMARY KEY, b map<float, text>, c int);
|
||||
CREATE TABLE type_bb_3_23 (a int PRIMARY KEY, b map<float, text>, c set<int>);
|
||||
|
||||
-- type C with collections
|
||||
-- FAILS: CREATE TABLE type_cc_4_3_2 (a int, b map<float, text>, c int, d int, PRIMARY KEY (a, b, c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component b
|
||||
-- FAILS: CREATE TABLE type_cc_4_3_4 (a int, b int, c int, d map<float, text>, PRIMARY KEY (a, b, c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Collection types are not supported with COMPACT STORAGE
|
||||
|
||||
-- type D with collections
|
||||
-- FAILS: CREATE TABLE type_dd_3_2_1 (a map<float, text>, b int, c int, PRIMARY KEY (a, b));
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component a
|
||||
-- FAILS: CREATE TABLE type_dd_3_2_2 (a int, b map<float, text>, c int, PRIMARY KEY (a, b));
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component b
|
||||
CREATE TABLE type_dd_3_2_3 (a int, b int, c map<float, text>, PRIMARY KEY (a, b));
|
||||
CREATE TABLE type_dd_4_3_4 (a int, b int, c int, d map<float, text>, PRIMARY KEY (a, b, c));
|
||||
CREATE TABLE type_dd_5_3_4 (a int, b int, c int, d map<float, text>, e int, PRIMARY KEY (a, b, c));
|
||||
CREATE TABLE type_dd_5_3_45 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY (a, b, c));
|
||||
|
||||
-- type E with collections (these should all fail, but some don't)
|
||||
-- FAILS: CREATE TABLE type_ee_3_2_2 (a int, b map<float, text>, c int, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component b
|
||||
CREATE TABLE type_ee_3_2_3 (a int, b int, c map<float, text>, PRIMARY KEY ((a, b))) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_ee_4_3_4 (a int, b int, c int, d map<float, text>, PRIMARY KEY ((a, b, c))) WITH COMPACT STORAGE;
|
||||
CREATE TABLE type_ee_5_3_45 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY ((a, b, c))) WITH COMPACT STORAGE;
|
||||
|
||||
-- type F with collections
|
||||
-- FAILS: CREATE TABLE type_ff_3_2_1 (a list<int>, b int, c int, PRIMARY KEY ((a, b)));
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component a
|
||||
CREATE TABLE type_ff_3_2_3 (a int, b int, c map<float, text>, PRIMARY KEY ((a, b)));
|
||||
CREATE TABLE type_ff_4_3_4 (a int, b int, c int, d map<float, text>, PRIMARY KEY ((a, b, c)));
|
||||
CREATE TABLE type_ff_5_2_45 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY ((a, b)));
|
||||
CREATE TABLE type_ff_5_3_45 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY ((a, b, c)));
|
||||
|
||||
-- type G with collections
|
||||
-- FAILS: CREATE TABLE type_gg_4_3_2_1 (a set<int>, b int, c int, d int, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component a
|
||||
-- FAILS: CREATE TABLE type_gg_4_3_2_4 (a int, b int, c int, d list<int>, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Collection types are not supported with COMPACT STORAGE
|
||||
-- FAILS: CREATE TABLE type_gg_5_3_2_4 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY ((a, b), c)) WITH COMPACT STORAGE;
|
||||
-- Bad Request: Collection types are not supported with COMPACT STORAGE
|
||||
|
||||
-- type H with collections
|
||||
-- FAILS: CREATE TABLE type_hh_4_3_2_1 (a set<int>, b int, c int, d int, PRIMARY KEY ((a, b), c));
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component a
|
||||
-- FAILS: CREATE TABLE type_hh_4_3_2_3 (a int, b int, c list<int>, d int, PRIMARY KEY ((a, b), c));
|
||||
-- Bad Request: Invalid collection type for PRIMARY KEY component c
|
||||
CREATE TABLE type_hh_4_3_2_4 (a int, b int, c int, d list<int>, PRIMARY KEY ((a, b), c));
|
||||
CREATE TABLE type_hh_5_3_2_45 (a int, b int, c int, d map<float, text>, e list<int>, PRIMARY KEY ((a, b), c));
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# 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.
|
||||
|
||||
# to configure behavior, define $CQL_TEST_HOST to the destination address
|
||||
# for Thrift connections, and $CQL_TEST_PORT to the associated port.
|
||||
|
||||
from .basecase import BaseTestCase, cqlsh
|
||||
|
||||
class TestCqlParsing(BaseTestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_parse_string_literals(self):
|
||||
pass
|
||||
|
||||
def test_parse_numbers(self):
|
||||
pass
|
||||
|
||||
def test_parse_uuid(self):
|
||||
pass
|
||||
|
||||
def test_comments_in_string_literals(self):
|
||||
pass
|
||||
|
||||
def test_colons_in_string_literals(self):
|
||||
pass
|
||||
|
||||
def test_partial_parsing(self):
|
||||
pass
|
||||
|
||||
def test_parse_select(self):
|
||||
pass
|
||||
|
||||
def test_parse_insert(self):
|
||||
pass
|
||||
|
||||
def test_parse_update(self):
|
||||
pass
|
||||
|
||||
def test_parse_delete(self):
|
||||
pass
|
||||
|
||||
def test_parse_batch(self):
|
||||
pass
|
||||
|
||||
def test_parse_create_keyspace(self):
|
||||
pass
|
||||
|
||||
def test_parse_drop_keyspace(self):
|
||||
pass
|
||||
|
||||
def test_parse_create_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_parse_drop_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_parse_truncate(self):
|
||||
pass
|
||||
|
||||
def test_parse_alter_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_parse_use(self):
|
||||
pass
|
||||
|
||||
def test_parse_create_index(self):
|
||||
pass
|
||||
|
||||
def test_parse_drop_index(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# 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.
|
||||
|
||||
# to configure behavior, define $CQL_TEST_HOST to the destination address
|
||||
# for Thrift connections, and $CQL_TEST_PORT to the associated port.
|
||||
|
||||
from .basecase import BaseTestCase, cqlsh
|
||||
|
||||
class TestCqlshCommands(BaseTestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_assume(self):
|
||||
pass
|
||||
|
||||
def test_show(self):
|
||||
pass
|
||||
|
||||
def test_describe(self):
|
||||
pass
|
||||
|
||||
def test_exit(self):
|
||||
pass
|
||||
|
||||
def test_help(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
# 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.
|
||||
|
||||
# to configure behavior, define $CQL_TEST_HOST to the destination address
|
||||
# for Thrift connections, and $CQL_TEST_PORT to the associated port.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import re
|
||||
from .basecase import BaseTestCase, cqlsh
|
||||
from .cassconnect import testrun_cqlsh
|
||||
|
||||
BEL = '\x07' # the terminal-bell character
|
||||
CTRL_C = '\x03'
|
||||
TAB = '\t'
|
||||
|
||||
# completions not printed out in this many seconds may not be acceptable.
|
||||
# tune if needed for a slow system, etc, but be aware that the test will
|
||||
# need to wait this long for each completion test, to make sure more info
|
||||
# isn't coming
|
||||
COMPLETION_RESPONSE_TIME = 0.5
|
||||
|
||||
completion_separation_re = re.compile(r'\s\s+')
|
||||
|
||||
class CqlshCompletionCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
self.cqlsh_runner = testrun_cqlsh(cqlver=self.cqlver, env={'COLUMNS': '100000'})
|
||||
self.cqlsh = self.cqlsh_runner.__enter__()
|
||||
|
||||
def tearDown(self):
|
||||
self.cqlsh_runner.__exit__(None, None, None)
|
||||
|
||||
def _trycompletions_inner(self, inputstring, immediate='', choices=(), other_choices_ok=False):
|
||||
"""
|
||||
Test tab completion in cqlsh. Enters in the text in inputstring, then
|
||||
simulates a tab keypress to see what is immediately completed (this
|
||||
should only happen when there is only one completion possible). If
|
||||
there is an immediate completion, the new text is expected to match
|
||||
'immediate'. If there is no immediate completion, another tab keypress
|
||||
is simulated in order to get a list of choices, which are expected to
|
||||
match the items in 'choices' (order is not important, but case is).
|
||||
"""
|
||||
self.cqlsh.send(inputstring)
|
||||
self.cqlsh.send(TAB)
|
||||
completed = self.cqlsh.read_up_to_timeout(COMPLETION_RESPONSE_TIME)
|
||||
self.assertEqual(completed[:len(inputstring)], inputstring)
|
||||
completed = completed[len(inputstring):]
|
||||
completed = completed.replace(BEL, '')
|
||||
self.assertEqual(completed, immediate, 'cqlsh completed %r, but we expected %r'
|
||||
% (completed, immediate))
|
||||
if immediate:
|
||||
return
|
||||
|
||||
self.cqlsh.send(TAB)
|
||||
choice_output = self.cqlsh.read_up_to_timeout(COMPLETION_RESPONSE_TIME)
|
||||
if choice_output == BEL:
|
||||
lines = ()
|
||||
else:
|
||||
lines = choice_output.splitlines()
|
||||
self.assertRegexpMatches(lines[-1], self.cqlsh.prompt.lstrip() + re.escape(inputstring))
|
||||
choicesseen = set()
|
||||
for line in lines[:-1]:
|
||||
choicesseen.update(completion_separation_re.split(line.strip()))
|
||||
choicesseen.discard('')
|
||||
if other_choices_ok:
|
||||
self.assertEqual(set(choices), choicesseen.intersection(choices))
|
||||
else:
|
||||
self.assertEqual(set(choices), choicesseen)
|
||||
|
||||
def trycompletions(self, inputstring, immediate='', choices=(), other_choices_ok=False):
|
||||
try:
|
||||
self._trycompletions_inner(inputstring, immediate, choices, other_choices_ok)
|
||||
finally:
|
||||
self.cqlsh.send(CTRL_C) # cancel any current line
|
||||
self.cqlsh.read_to_next_prompt()
|
||||
|
||||
def strategies(self):
|
||||
return self.module.CqlRuleSet.replication_strategies
|
||||
|
||||
class TestCqlshCompletion_CQL2(CqlshCompletionCase):
|
||||
cqlver = 2
|
||||
module = cqlsh.cqlhandling
|
||||
|
||||
def test_complete_on_empty_string(self):
|
||||
self.trycompletions('', choices=('?', 'ALTER', 'ASSUME', 'BEGIN', 'CAPTURE', 'CONSISTENCY',
|
||||
'COPY', 'CREATE', 'DEBUG', 'DELETE', 'DESC', 'DESCRIBE',
|
||||
'DROP', 'HELP', 'INSERT', 'SELECT', 'SHOW', 'SOURCE',
|
||||
'TRACING', 'TRUNCATE', 'UPDATE', 'USE', 'exit', 'quit'))
|
||||
|
||||
def test_complete_command_words(self):
|
||||
self.trycompletions('alt', '\b\b\bALTER ')
|
||||
self.trycompletions('I', 'NSERT INTO ')
|
||||
self.trycompletions('exit', ' ')
|
||||
|
||||
def test_complete_in_string_literals(self):
|
||||
# would be great if we could get a space after this sort of completion,
|
||||
# but readline really wants to make things difficult for us
|
||||
self.trycompletions("insert into system.'NodeId", "Info'")
|
||||
self.trycompletions("USE '", choices=('system', self.cqlsh.keyspace), other_choices_ok=True)
|
||||
self.trycompletions("create keyspace blah with strategy_class = 'Sim",
|
||||
"pleStrategy'")
|
||||
|
||||
def test_complete_in_uuid(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_select(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_insert(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_update(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_delete(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_batch(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_create_keyspace(self):
|
||||
self.trycompletions('create keyspace ', '', choices=('<new_keyspace_name>',))
|
||||
self.trycompletions('create keyspace moo ', "WITH strategy_class = '")
|
||||
self.trycompletions("create keyspace '12SomeName' with ", "strategy_class = '")
|
||||
self.trycompletions("create keyspace moo with strategy_class", " = '")
|
||||
self.trycompletions("create keyspace moo with strategy_class='",
|
||||
choices=self.strategies())
|
||||
self.trycompletions("create keySPACE 123 with strategy_class='SimpleStrategy' A",
|
||||
"ND strategy_options:replication_factor = ")
|
||||
self.trycompletions("create keyspace fish with strategy_class='SimpleStrategy'"
|
||||
"and strategy_options:replication_factor = ", '',
|
||||
choices=('<option_value>',))
|
||||
self.trycompletions("create keyspace 'PB and J' with strategy_class="
|
||||
"'NetworkTopologyStrategy' AND", ' ')
|
||||
self.trycompletions("create keyspace 'PB and J' with strategy_class="
|
||||
"'NetworkTopologyStrategy' AND ", '',
|
||||
choices=('<strategy_option_name>',))
|
||||
|
||||
def test_complete_in_drop_keyspace(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_create_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_drop_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_truncate(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_alter_columnfamily(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_use(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_create_index(self):
|
||||
pass
|
||||
|
||||
def test_complete_in_drop_index(self):
|
||||
pass
|
||||
|
||||
class TestCqlshCompletion_CQL3final(TestCqlshCompletion_CQL2):
|
||||
cqlver = '3.0.0'
|
||||
module = cqlsh.cql3handling
|
||||
|
||||
def test_complete_on_empty_string(self):
|
||||
self.trycompletions('', choices=('?', 'ALTER', 'ASSUME', 'BEGIN', 'CAPTURE', 'CONSISTENCY',
|
||||
'COPY', 'CREATE', 'DEBUG', 'DELETE', 'DESC', 'DESCRIBE',
|
||||
'DROP', 'GRANT', 'HELP', 'INSERT', 'LIST', 'REVOKE',
|
||||
'SELECT', 'SHOW', 'SOURCE', 'TRACING', 'TRUNCATE', 'UPDATE',
|
||||
'USE', 'exit', 'quit'))
|
||||
|
||||
def test_complete_in_create_keyspace(self):
|
||||
self.trycompletions('create keyspace ', '', choices=('<identifier>', '<quotedName>'))
|
||||
self.trycompletions('create keyspace moo ',
|
||||
"WITH replication = {'class': '")
|
||||
self.trycompletions('create keyspace "12SomeName" with ',
|
||||
"replication = {'class': '")
|
||||
self.trycompletions("create keyspace fjdkljf with foo=bar ", "",
|
||||
choices=('AND', ';'))
|
||||
self.trycompletions("create keyspace fjdkljf with foo=bar AND ",
|
||||
"replication = {'class': '")
|
||||
self.trycompletions("create keyspace moo with replication", " = {'class': '")
|
||||
self.trycompletions("create keyspace moo with replication=", " {'class': '")
|
||||
self.trycompletions("create keyspace moo with replication={", "'class':'")
|
||||
self.trycompletions("create keyspace moo with replication={'class'", ":'")
|
||||
self.trycompletions("create keyspace moo with replication={'class': ", "'")
|
||||
self.trycompletions("create keyspace moo with replication={'class': '", "",
|
||||
choices=self.strategies())
|
||||
# ttl is an "unreserved keyword". should work
|
||||
self.trycompletions("create keySPACE ttl with replication ="
|
||||
"{ 'class' : 'SimpleStrategy'", ", 'replication_factor': ")
|
||||
self.trycompletions("create keyspace ttl with replication ="
|
||||
"{'class':'SimpleStrategy',", " 'replication_factor': ")
|
||||
self.trycompletions("create keyspace \"ttl\" with replication ="
|
||||
"{'class': 'SimpleStrategy', ", "'replication_factor': ")
|
||||
self.trycompletions("create keyspace \"ttl\" with replication ="
|
||||
"{'class': 'SimpleStrategy', 'repl", "ication_factor'")
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'SimpleStrategy', 'replication_factor': ", '',
|
||||
choices=('<value>',))
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'SimpleStrategy', 'replication_factor': 1", '',
|
||||
choices=('<value>',))
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'SimpleStrategy', 'replication_factor': 1 ", '}')
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'SimpleStrategy', 'replication_factor': 1, ",
|
||||
'', choices=())
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'SimpleStrategy', 'replication_factor': 1} ",
|
||||
'', choices=('AND', ';'))
|
||||
self.trycompletions("create keyspace foo with replication ="
|
||||
"{'class': 'NetworkTopologyStrategy', ", '',
|
||||
choices=('<dc_name>',))
|
||||
self.trycompletions("create keyspace \"PB and J\" with replication={"
|
||||
"'class': 'NetworkTopologyStrategy'", ', ')
|
||||
self.trycompletions("create keyspace PBJ with replication={"
|
||||
"'class': 'NetworkTopologyStrategy'} and ",
|
||||
"durable_writes = '")
|
||||
|
||||
def test_complete_in_string_literals(self):
|
||||
# would be great if we could get a space after this sort of completion,
|
||||
# but readline really wants to make things difficult for us
|
||||
self.trycompletions('insert into system."NodeId', 'Info"')
|
||||
self.trycompletions('USE "', choices=('system', self.cqlsh.keyspace),
|
||||
other_choices_ok=True)
|
||||
self.trycompletions("create keyspace blah with replication = {'class': 'Sim",
|
||||
"pleStrategy'")
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# 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.
|
||||
|
||||
# to configure behavior, define $CQL_TEST_HOST to the destination address
|
||||
# for Thrift connections, and $CQL_TEST_PORT to the associated port.
|
||||
|
||||
from .basecase import BaseTestCase
|
||||
|
||||
class TestCqlshInvocation(BaseTestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_normal_run(self):
|
||||
pass
|
||||
|
||||
def test_python_interpreter_location(self):
|
||||
pass
|
||||
|
||||
def test_color_capability_detection(self):
|
||||
pass
|
||||
|
||||
def test_colored_output(self):
|
||||
pass
|
||||
|
||||
def test_color_cmdline_option(self):
|
||||
pass
|
||||
|
||||
def test_debug_option(self):
|
||||
pass
|
||||
|
||||
def test_connection_args(self):
|
||||
pass
|
||||
|
||||
def test_connection_config(self):
|
||||
pass
|
||||
|
||||
def test_connection_envvars(self):
|
||||
pass
|
||||
|
||||
def test_command_history(self):
|
||||
pass
|
||||
|
||||
def test_missing_dependencies(self):
|
||||
pass
|
||||
|
||||
def test_completekey_config(self):
|
||||
pass
|
||||
|
||||
def test_ctrl_c(self):
|
||||
pass
|
||||
|
||||
def test_eof(self):
|
||||
pass
|
||||
|
||||
def test_output_encoding_detection(self):
|
||||
pass
|
||||
|
||||
def test_output_encoding(self):
|
||||
pass
|
||||
|
||||
def test_retries(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,965 @@
|
|||
# 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.
|
||||
|
||||
# to configure behavior, define $CQL_TEST_HOST to the destination address
|
||||
# for Thrift connections, and $CQL_TEST_PORT to the associated port.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import re
|
||||
from itertools import izip
|
||||
from .basecase import (BaseTestCase, cqlshlog, dedent, at_a_time, cql,
|
||||
TEST_HOST, TEST_PORT)
|
||||
from .cassconnect import (get_test_keyspace, testrun_cqlsh, testcall_cqlsh,
|
||||
cassandra_cursor, split_cql_commands, quote_name)
|
||||
from .ansi_colors import (ColoredText, lookup_colorcode, lookup_colorname,
|
||||
lookup_colorletter, ansi_seq)
|
||||
|
||||
CONTROL_C = '\x03'
|
||||
CONTROL_D = '\x04'
|
||||
|
||||
class TestCqlshOutput(BaseTestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def assertNoHasColors(self, text, msg=None):
|
||||
self.assertNotRegexpMatches(text, ansi_seq, msg='ANSI CSI sequence found in %r' % text)
|
||||
|
||||
def assertHasColors(self, text, msg=None):
|
||||
self.assertRegexpMatches(text, ansi_seq, msg=msg)
|
||||
|
||||
def assertColored(self, coloredtext, colorname):
|
||||
wanted_colorcode = lookup_colorcode(colorname)
|
||||
for num, c in enumerate(coloredtext):
|
||||
if not c.isspace():
|
||||
ccolor = c.colorcode()
|
||||
self.assertEqual(ccolor, wanted_colorcode,
|
||||
msg='Output text %r (char #%d) is colored %s, not %s'
|
||||
% (coloredtext, num, lookup_colorname(ccolor), colorname))
|
||||
|
||||
def assertColorFromTags(self, coloredtext, tags):
|
||||
for (char, tag) in izip(coloredtext, tags):
|
||||
if char.isspace():
|
||||
continue
|
||||
if tag.isspace():
|
||||
tag = 'n' # neutral
|
||||
self.assertEqual(char.colorcode(), lookup_colorletter(tag),
|
||||
msg='Coloring mismatch.\nExpected coloring: %s\n'
|
||||
'Actually got: %s\ncolor code: %s'
|
||||
% (tags, coloredtext.colored_version(), coloredtext.colortags()))
|
||||
|
||||
def assertCqlverQueriesGiveColoredOutput(self, queries_and_expected_outputs,
|
||||
cqlver=(), **kwargs):
|
||||
if not isinstance(cqlver, (tuple, list)):
|
||||
cqlver = (cqlver,)
|
||||
for ver in cqlver:
|
||||
self.assertQueriesGiveColoredOutput(queries_and_expected_outputs, cqlver=ver, **kwargs)
|
||||
|
||||
def assertQueriesGiveColoredOutput(self, queries_and_expected_outputs,
|
||||
sort_results=False, **kwargs):
|
||||
"""
|
||||
Allow queries and expected output to be specified in structured tuples,
|
||||
along with expected color information.
|
||||
"""
|
||||
with testrun_cqlsh(tty=True, **kwargs) as c:
|
||||
for query, expected in queries_and_expected_outputs:
|
||||
cqlshlog.debug('Testing %r' % (query,))
|
||||
output = c.cmd_and_response(query).lstrip("\r\n")
|
||||
c_output = ColoredText(output)
|
||||
pairs = at_a_time(dedent(expected).split('\n'), 2)
|
||||
outlines = c_output.splitlines()
|
||||
if sort_results:
|
||||
if outlines[1].plain().startswith('---'):
|
||||
firstpart = outlines[:2]
|
||||
sortme = outlines[2:]
|
||||
else:
|
||||
firstpart = []
|
||||
sortme = outlines
|
||||
lastpart = []
|
||||
while sortme[-1].plain() == '':
|
||||
lastpart.append(sortme.pop(-1))
|
||||
outlines = firstpart + sorted(sortme, key=lambda c:c.plain()) + lastpart
|
||||
for (plain, colorcodes), outputline in zip(pairs, outlines):
|
||||
self.assertEqual(outputline.plain().rstrip(), plain)
|
||||
self.assertColorFromTags(outputline, colorcodes)
|
||||
|
||||
def test_no_color_output(self):
|
||||
for termname in ('', 'dumb', 'vt100'):
|
||||
cqlshlog.debug('TERM=%r' % termname)
|
||||
with testrun_cqlsh(tty=True, env={'TERM': termname}) as c:
|
||||
c.send('select * from has_all_types;\n')
|
||||
self.assertNoHasColors(c.read_to_next_prompt())
|
||||
c.send('select * from has_value_encoding_errors;\n')
|
||||
self.assertNoHasColors(c.read_to_next_prompt())
|
||||
c.send('select count(*) from has_all_types;\n')
|
||||
self.assertNoHasColors(c.read_to_next_prompt())
|
||||
c.send('totally invalid cql;\n')
|
||||
self.assertNoHasColors(c.read_to_next_prompt())
|
||||
|
||||
def test_no_prompt_or_colors_output(self):
|
||||
# CQL queries and number of lines expected in output:
|
||||
queries = (('select * from has_all_types limit 1;', 5),
|
||||
('select * from has_value_encoding_errors limit 1;', 6))
|
||||
for termname in ('', 'dumb', 'vt100', 'xterm'):
|
||||
cqlshlog.debug('TERM=%r' % termname)
|
||||
for cql, lines_expected in queries:
|
||||
output, result = testcall_cqlsh(prompt=None, env={'TERM': termname},
|
||||
tty=False, input=cql + '\n')
|
||||
output = output.splitlines()
|
||||
for line in output:
|
||||
self.assertNoHasColors(line)
|
||||
self.assertNotRegexpMatches(line, r'^cqlsh\S*>')
|
||||
self.assertEqual(len(output), lines_expected,
|
||||
msg='output: %r' % '\n'.join(output))
|
||||
self.assertEqual(output[0], '')
|
||||
self.assertNicelyFormattedTableHeader(output[1])
|
||||
self.assertNicelyFormattedTableRule(output[2])
|
||||
self.assertNicelyFormattedTableData(output[3])
|
||||
self.assertEqual(output[4].strip(), '')
|
||||
|
||||
def test_color_output(self):
|
||||
for termname in ('xterm', 'unknown-garbage'):
|
||||
cqlshlog.debug('TERM=%r' % termname)
|
||||
with testrun_cqlsh(tty=True, env={'TERM': termname}) as c:
|
||||
c.send('select * from has_all_types;\n')
|
||||
self.assertHasColors(c.read_to_next_prompt())
|
||||
c.send('select * from has_value_encoding_errors;\n')
|
||||
self.assertHasColors(c.read_to_next_prompt())
|
||||
c.send('select count(*) from has_all_types;\n')
|
||||
self.assertHasColors(c.read_to_next_prompt())
|
||||
c.send('totally invalid cql;\n')
|
||||
self.assertHasColors(c.read_to_next_prompt())
|
||||
|
||||
def test_count_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
('select count(*) from has_all_types;', """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
4
|
||||
G
|
||||
|
||||
"""),
|
||||
|
||||
('select COUNT(*) FROM empty_table;', """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
0
|
||||
G
|
||||
|
||||
"""),
|
||||
|
||||
('select COUNT(*) FROM empty_composite_table;', """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
0
|
||||
G
|
||||
|
||||
"""),
|
||||
|
||||
('select COUNT(*) FROM twenty_rows_table limit 10;', """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
10
|
||||
GG
|
||||
|
||||
"""),
|
||||
|
||||
('select COUNT(*) FROM twenty_rows_table limit 1000000;', """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
20
|
||||
GG
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
# different results cql2/cql3
|
||||
q = 'select COUNT(*) FROM twenty_rows_composite_table limit 1000000;'
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
1
|
||||
GG
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
count
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
20
|
||||
GG
|
||||
|
||||
"""),
|
||||
), cqlver=3)
|
||||
|
||||
def test_dynamic_cf_output(self):
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
('select * from dynamic_columns;', """
|
||||
somekey,1 | 1.2,one point two
|
||||
nMMMMMMM G MMM YYYYYYYYYYYYY
|
||||
somekey,2 | 2.3,two point three
|
||||
MMMMMMM G MMM YYYYYYYYYYYYYYY
|
||||
somekey,3 | -0.0001,negative ten thousandth | 3.46,three point four six | 99,ninety-nine point oh
|
||||
MMMMMMM G MMMMMMM YYYYYYYYYYYYYYYYYYYYYYY MMMM YYYYYYYYYYYYYYYYYYYY n MM YYYYYYYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
|
||||
('select somekey, 2.3 from dynamic_columns;', """
|
||||
somekey | 2.3
|
||||
MMMMMMM MMM
|
||||
---------+-----------------
|
||||
|
||||
1 | null
|
||||
G RRRR
|
||||
2 | two point three
|
||||
G YYYYYYYYYYYYYYY
|
||||
3 | null
|
||||
G RRRR
|
||||
|
||||
"""),
|
||||
|
||||
('select first 1 * from dynamic_columns where somekey = 2;', """
|
||||
2.3
|
||||
MMM
|
||||
-----------------
|
||||
|
||||
two point three
|
||||
YYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
|
||||
('select * from dynamic_columns where somekey = 2;', """
|
||||
somekey | 2.3
|
||||
MMMMMMM MMM
|
||||
---------+-----------------
|
||||
|
||||
2 | two point three
|
||||
G YYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
), cqlver=2, sort_results=True)
|
||||
|
||||
def test_static_cf_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
('select a, b from twenty_rows_table where a in (1, 13, 2);', """
|
||||
a | b
|
||||
MM MM
|
||||
----+----
|
||||
|
||||
1 | 1
|
||||
YY YY
|
||||
13 | 13
|
||||
YY YY
|
||||
2 | 2
|
||||
YY YY
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
('select * from dynamic_columns;', """
|
||||
somekey | column1 | value
|
||||
MMMMMMM MMMMMMM MMMMM
|
||||
---------+---------+-------------------------
|
||||
|
||||
1 | 1.2 | one point two
|
||||
GG GGGGGGG YYYYYYYYYYYYYYYYYYYYYYY
|
||||
2 | 2.3 | two point three
|
||||
GG GGGGGGG YYYYYYYYYYYYYYYYYYYYYYY
|
||||
3 | 99 | ninety-nine point oh
|
||||
GG GGGGGGG YYYYYYYYYYYYYYYYYYYYYYY
|
||||
3 | 3.46 | three point four six
|
||||
GG GGGGGGG YYYYYYYYYYYYYYYYYYYYYYY
|
||||
3 | -0.0001 | negative ten thousandth
|
||||
GG GGGGGGG YYYYYYYYYYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
), cqlver=3, sort_results=True)
|
||||
|
||||
def test_empty_cf_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
('select * from empty_table;', """
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
q = 'select * from has_all_types where num = 999;'
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
num
|
||||
MMM
|
||||
-----
|
||||
|
||||
999
|
||||
GGG
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
# same query should show up as empty in cql 3
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
"""),
|
||||
), cqlver=3)
|
||||
|
||||
def test_columnless_key_output(self):
|
||||
q = 'select a from twenty_rows_table where a in (1, 2, -9192);'
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
a
|
||||
MMMMM
|
||||
-------
|
||||
|
||||
1
|
||||
YYYYY
|
||||
2
|
||||
YYYYY
|
||||
-9192
|
||||
YYYYY
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
(q, """
|
||||
a
|
||||
M
|
||||
---
|
||||
|
||||
1
|
||||
Y
|
||||
2
|
||||
Y
|
||||
|
||||
"""),
|
||||
), cqlver=3)
|
||||
|
||||
def test_numeric_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
('''select intcol, bigintcol, varintcol \
|
||||
from has_all_types \
|
||||
where num in (0, 1, 2, 3);''', """
|
||||
intcol | bigintcol | varintcol
|
||||
MMMMMM MMMMMMMMM MMMMMMMMM
|
||||
-------------+----------------------+-----------------------------
|
||||
|
||||
-12 | 1234567890123456789 | 10000000000000000000000000
|
||||
GGGGGGGGGGG GGGGGGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||
2147483647 | 9223372036854775807 | 9
|
||||
GGGGGGGGGGG GGGGGGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||
0 | 0 | 0
|
||||
GGGGGGGGGGG GGGGGGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||
-2147483648 | -9223372036854775808 | -10000000000000000000000000
|
||||
GGGGGGGGGGG GGGGGGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGG
|
||||
|
||||
"""),
|
||||
|
||||
('''select decimalcol, doublecol, floatcol \
|
||||
from has_all_types \
|
||||
where num in (0, 1, 2, 3);''', """
|
||||
decimalcol | doublecol | floatcol
|
||||
MMMMMMMMMM MMMMMMMMM MMMMMMMM
|
||||
------------------+-----------+----------
|
||||
|
||||
19952.11882 | 1 | -2.1
|
||||
GGGGGGGGGGGGGGGG GGGGGGG GGGGG
|
||||
1E-14 | 1e+07 | 1e+05
|
||||
GGGGGGGGGGGGGGGG GGGGGGG GGGGG
|
||||
0.0 | 0 | 0
|
||||
GGGGGGGGGGGGGGGG GGGGGGG GGGGG
|
||||
10.0000000000000 | -1004.1 | 1e+08
|
||||
GGGGGGGGGGGGGGGG GGGGGGG GGGGG
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
('''select * from dynamic_columns where somekey = 3;''', """
|
||||
somekey | -0.0001 | 3.46 | 99
|
||||
MMMMMMM MMMMMMM MMMM MM
|
||||
---------+-------------------------+----------------------+----------------------
|
||||
|
||||
3 | negative ten thousandth | three point four six | ninety-nine point oh
|
||||
G YYYYYYYYYYYYYYYYYYYYYYY YYYYYYYYYYYYYYYYYYYY YYYYYYYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
def test_timestamp_output(self):
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
('''select timestampcol from has_all_types where num = 0;''', """
|
||||
timestampcol
|
||||
MMMMMMMMMMMM
|
||||
--------------------------
|
||||
|
||||
2012-05-14 12:53:20+0000
|
||||
GGGGGGGGGGGGGGGGGGGGGGGG
|
||||
|
||||
"""),
|
||||
), env={'TZ': 'Etc/UTC'})
|
||||
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
('''select timestampcol from has_all_types where num = 0;''', """
|
||||
timestampcol
|
||||
MMMMMMMMMMMM
|
||||
--------------------------
|
||||
|
||||
2012-05-14 07:53:20-0500
|
||||
GGGGGGGGGGGGGGGGGGGGGGGG
|
||||
|
||||
"""),
|
||||
), env={'TZ': 'EST'})
|
||||
|
||||
def test_boolean_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
('select num, booleancol from has_all_types where num in (0, 1, 2, 3);', """
|
||||
num | booleancol
|
||||
MMM MMMMMMMMMM
|
||||
-----+------------
|
||||
|
||||
0 | True
|
||||
G GGGGG
|
||||
1 | True
|
||||
G GGGGG
|
||||
2 | False
|
||||
G GGGGG
|
||||
3 | False
|
||||
G GGGGG
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
def test_null_output(self):
|
||||
# column with metainfo but no values
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select k, c, notthere from undefined_values_table where k in ('k1', 'k2');", """
|
||||
k | c | notthere
|
||||
M M MMMMMMMM
|
||||
----+----+----------
|
||||
|
||||
k1 | c1 | null
|
||||
YY YY RRRR
|
||||
k2 | c2 | null
|
||||
YY YY RRRR
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
# all-columns, including a metainfo column has no values (cql3)
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
("select * from undefined_values_table where k in ('k1', 'k2');", """
|
||||
k | c | notthere
|
||||
M M MMMMMMMM
|
||||
----+----+----------
|
||||
|
||||
k1 | c1 | null
|
||||
YY YY RRRR
|
||||
k2 | c2 | null
|
||||
YY YY RRRR
|
||||
|
||||
"""),
|
||||
), cqlver=3)
|
||||
|
||||
# all-columns, including a metainfo column has no values (cql2)
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
("select * from undefined_values_table where k in ('k1', 'k2');", """
|
||||
k | c
|
||||
M M
|
||||
----+----
|
||||
|
||||
k1 | c1
|
||||
YY YY
|
||||
k2 | c2
|
||||
YY YY
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
# column not in metainfo, which has no values (invalid query in cql3)
|
||||
self.assertQueriesGiveColoredOutput((
|
||||
("select num, fakecol from has_all_types where num in (1, 2);", """
|
||||
num
|
||||
MMM
|
||||
-----
|
||||
|
||||
1
|
||||
GGG
|
||||
2
|
||||
GGG
|
||||
|
||||
"""),
|
||||
), cqlver=2)
|
||||
|
||||
def test_string_output_ascii(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select * from ascii_with_invalid_and_special_chars where k in (0, 1, 2, 3, 4);", r"""
|
||||
k | val
|
||||
M MMM
|
||||
---+-----------------------------------------------
|
||||
|
||||
0 | newline:\n
|
||||
G YYYYYYYYmm
|
||||
1 | return\rand null\x00!
|
||||
G YYYYYYmmYYYYYYYYmmmmY
|
||||
2 | \x00\x01\x02\x03\x04\x05control chars\x06\x07
|
||||
G mmmmmmmmmmmmmmmmmmmmmmmmYYYYYYYYYYYYYmmmmmmmm
|
||||
3 | \xfe\xffbyte order mark
|
||||
G mmmmmmmmYYYYYYYYYYYYYYY
|
||||
4 | fake special chars\x00\n
|
||||
G YYYYYYYYYYYYYYYYYYYYYYYY
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
def test_string_output_utf8(self):
|
||||
# many of these won't line up visually here, to keep the source code
|
||||
# here ascii-only. note that some of the special Unicode characters
|
||||
# here will render as double-width or zero-width in unicode-aware
|
||||
# terminals, but the color-checking machinery here will still treat
|
||||
# it as one character, so those won't seem to line up visually either.
|
||||
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select * from utf8_with_special_chars where k in (0, 1, 2, 3, 4, 5, 6);", u"""
|
||||
k | val
|
||||
M MMM
|
||||
---+-------------------------------
|
||||
|
||||
0 | Normal string
|
||||
G YYYYYYYYYYYYY
|
||||
1 | Text with\\nnewlines\\n
|
||||
G YYYYYYYYYmmYYYYYYYYmm
|
||||
2 | Text with embedded \\x01 char
|
||||
G YYYYYYYYYYYYYYYYYYYmmmmYYYYY
|
||||
3 | \u24c8\u24c5\u24ba\u24b8\u24be\u24b6\u24c1\u2008\u249e\u24a3\u249c\u24ad\u24ae and normal ones
|
||||
G YYYYYYYYYYYYYYYYYYYYYYYYYYYYY
|
||||
4 | double wides: \u2f91\u2fa4\u2f9a
|
||||
G YYYYYYYYYYYYYYYYY
|
||||
5 | zero width\u200bspace
|
||||
G YYYYYYYYYYYYYYYY
|
||||
6 | fake special chars\\x00\\n
|
||||
G YYYYYYYYYYYYYYYYYYYYYYYY
|
||||
|
||||
""".encode('utf-8')),
|
||||
), cqlver=(2, 3), env={'LANG': 'en_US.UTF-8'})
|
||||
|
||||
def test_blob_output(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select num, blobcol from has_all_types where num in (0, 1, 2, 3);", r"""
|
||||
num | blobcol
|
||||
MMM MMMMMMM
|
||||
-----+--------------------
|
||||
|
||||
0 | 000102030405fffefd
|
||||
G mmmmmmmmmmmmmmmmmm
|
||||
1 | ffffffffffffffffff
|
||||
G mmmmmmmmmmmmmmmmmm
|
||||
2 |
|
||||
G mmmmmmmmmmmmmmmmmm
|
||||
3 | 80
|
||||
G mmmmmmmmmmmmmmmmmm
|
||||
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
def test_colname_decoding_errors(self):
|
||||
# not clear how to achieve this situation in the first place. the
|
||||
# validator works pretty well, and we can't change the comparator
|
||||
# after insertion.
|
||||
#
|
||||
# guess we could monkey-patch cqlsh or python-cql source to
|
||||
# explicitly generate an exception on the deserialization of type X..
|
||||
pass
|
||||
|
||||
def test_colval_decoding_errors(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select * from has_value_encoding_errors;", r"""
|
||||
pkey | utf8col
|
||||
MMMM MMMMMMM
|
||||
------+--------------------
|
||||
|
||||
A | '\x00\xff\x00\xff'
|
||||
Y RRRRRRRRRRRRRRRRRR
|
||||
|
||||
|
||||
Failed to decode value '\x00\xff\x00\xff' (for column 'utf8col') as text: 'utf8' codec can't decode byte 0xff in position 1: invalid start byte
|
||||
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
def test_key_decoding_errors(self):
|
||||
self.assertCqlverQueriesGiveColoredOutput((
|
||||
("select * from has_key_encoding_errors;", r"""
|
||||
pkey | col
|
||||
MMMM MMM
|
||||
--------------------+----------
|
||||
|
||||
'\x00\xff\x02\x8f' | whatever
|
||||
RRRRRRRRRRRRRRRRRR YYYYYYYY
|
||||
|
||||
|
||||
Failed to decode value '\x00\xff\x02\x8f' (for column 'pkey') as text: 'utf8' codec can't decode byte 0xff in position 1: invalid start byte
|
||||
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
|
||||
"""),
|
||||
), cqlver=(2, 3))
|
||||
|
||||
def test_prompt(self):
|
||||
with testrun_cqlsh(tty=True, keyspace=None, cqlver=2) as c:
|
||||
self.assertEqual(c.output_header.splitlines()[-1], 'cqlsh> ')
|
||||
|
||||
c.send('\n')
|
||||
output = c.read_to_next_prompt().replace('\r\n', '\n')
|
||||
self.assertEqual(output, '\ncqlsh> ')
|
||||
|
||||
cmd = "USE '%s';\n" % get_test_keyspace().replace("'", "''")
|
||||
c.send(cmd)
|
||||
output = c.read_to_next_prompt().replace('\r\n', '\n')
|
||||
self.assertEqual(output, '%scqlsh:%s> ' % (cmd, get_test_keyspace()))
|
||||
|
||||
c.send('use system;\n')
|
||||
output = c.read_to_next_prompt().replace('\r\n', '\n')
|
||||
self.assertEqual(output, 'use system;\ncqlsh:system> ')
|
||||
|
||||
c.send('use NONEXISTENTKEYSPACE;\n')
|
||||
outputlines = c.read_to_next_prompt().splitlines()
|
||||
self.assertEqual(outputlines[0], 'use NONEXISTENTKEYSPACE;')
|
||||
self.assertEqual(outputlines[3], 'cqlsh:system> ')
|
||||
midline = ColoredText(outputlines[1])
|
||||
self.assertEqual(midline.plain(),
|
||||
"Bad Request: Keyspace 'NONEXISTENTKEYSPACE' does not exist")
|
||||
self.assertColorFromTags(midline,
|
||||
"RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR")
|
||||
|
||||
def check_describe_keyspace(self, fullcqlver):
|
||||
with testrun_cqlsh(tty=True, cqlver=fullcqlver) as c:
|
||||
ks = get_test_keyspace()
|
||||
qks = quote_name(fullcqlver, ks)
|
||||
for cmd in ('describe keyspace', 'desc keyspace'):
|
||||
for givename in ('system', '', qks):
|
||||
for semicolon in ('', ';'):
|
||||
fullcmd = cmd + (' ' if givename else '') + givename + semicolon
|
||||
desc = c.cmd_and_response(fullcmd)
|
||||
self.check_describe_keyspace_output(desc, givename or qks, fullcqlver)
|
||||
|
||||
# try to actually execute that last keyspace description, with a
|
||||
# new keyspace name
|
||||
new_ks_name = 'COPY_OF_' + ks
|
||||
copy_desc = desc.replace(ks, new_ks_name)
|
||||
statements = split_cql_commands(copy_desc, cqlver=fullcqlver)
|
||||
do_drop = True
|
||||
|
||||
with cassandra_cursor(cql_version=fullcqlver) as curs:
|
||||
try:
|
||||
for stmt in statements:
|
||||
cqlshlog.debug('TEST EXEC: %s' % stmt)
|
||||
try:
|
||||
curs.execute(stmt)
|
||||
except cql.ProgrammingError, e:
|
||||
# expected sometimes under cql3, since some existing
|
||||
# tables made under cql2 are not recreatable with cql3
|
||||
for errmsg in ('No definition found that is not part of the PRIMARY KEY',
|
||||
"mismatched input '<' expecting EOF"):
|
||||
if errmsg in str(e):
|
||||
do_drop = False
|
||||
break
|
||||
else:
|
||||
raise
|
||||
# maybe we should do some checks that the two keyspaces
|
||||
# match up as much as we expect at this point?
|
||||
finally:
|
||||
curs.execute('use system')
|
||||
if do_drop:
|
||||
curs.execute('drop keyspace %s' % quote_name(fullcqlver, new_ks_name))
|
||||
|
||||
def check_describe_keyspace_output(self, output, qksname, fullcqlver):
|
||||
expected_bits = [r'(?im)^CREATE KEYSPACE %s WITH\b' % re.escape(qksname),
|
||||
r'(?im)^USE \S+;$',
|
||||
r';\s*$']
|
||||
if fullcqlver == '3.0.0':
|
||||
expected_bits.append(r'\breplication = {\n \'class\':')
|
||||
else:
|
||||
expected_bits.append(r'\bstrategy_class =')
|
||||
for expr in expected_bits:
|
||||
self.assertRegexpMatches(output, expr)
|
||||
|
||||
def test_describe_keyspace_output(self):
|
||||
for v in ('2.0.0', '3.0.0'):
|
||||
self.check_describe_keyspace(v)
|
||||
|
||||
def test_describe_columnfamily_output(self):
|
||||
# we can change these to regular expressions if/when it makes sense
|
||||
# to do so; these will likely be subject to lots of adjustments.
|
||||
|
||||
table_desc2 = dedent("""
|
||||
|
||||
CREATE TABLE has_all_types (
|
||||
num int PRIMARY KEY,
|
||||
intcol int,
|
||||
timestampcol timestamp,
|
||||
floatcol float,
|
||||
uuidcol uuid,
|
||||
bigintcol bigint,
|
||||
doublecol double,
|
||||
booleancol boolean,
|
||||
decimalcol decimal,
|
||||
asciicol ascii,
|
||||
blobcol blob,
|
||||
varcharcol text,
|
||||
textcol text,
|
||||
varintcol varint
|
||||
) WITH
|
||||
comment='' AND
|
||||
comparator=text AND
|
||||
read_repair_chance=0.100000 AND
|
||||
gc_grace_seconds=864000 AND
|
||||
default_validation=text AND
|
||||
min_compaction_threshold=4 AND
|
||||
max_compaction_threshold=32 AND
|
||||
replicate_on_write='true' AND
|
||||
compaction_strategy_class='SizeTieredCompactionStrategy' AND
|
||||
compression_parameters:sstable_compression='SnappyCompressor';
|
||||
|
||||
""")
|
||||
|
||||
# note columns are now comparator-ordered instead of original-order.
|
||||
table_desc3 = dedent("""
|
||||
|
||||
CREATE TABLE has_all_types (
|
||||
num int PRIMARY KEY,
|
||||
asciicol ascii,
|
||||
bigintcol bigint,
|
||||
blobcol blob,
|
||||
booleancol boolean,
|
||||
decimalcol decimal,
|
||||
doublecol double,
|
||||
floatcol float,
|
||||
intcol int,
|
||||
textcol text,
|
||||
timestampcol timestamp,
|
||||
uuidcol uuid,
|
||||
varcharcol text,
|
||||
varintcol varint
|
||||
) WITH COMPACT STORAGE AND
|
||||
bloom_filter_fp_chance=0.010000 AND
|
||||
caching='KEYS_ONLY' AND
|
||||
comment='' AND
|
||||
dclocal_read_repair_chance=0.000000 AND
|
||||
gc_grace_seconds=864000 AND
|
||||
read_repair_chance=0.100000 AND
|
||||
replicate_on_write='true' AND
|
||||
compaction={'class': 'SizeTieredCompactionStrategy'} AND
|
||||
compression={'sstable_compression': 'SnappyCompressor'};
|
||||
|
||||
""")
|
||||
|
||||
for v in ('2.0.0', '3.0.0'):
|
||||
with testrun_cqlsh(tty=True, cqlver=v) as c:
|
||||
for cmdword in ('describe table', 'desc columnfamily'):
|
||||
for semicolon in (';', ''):
|
||||
output = c.cmd_and_response('%s has_all_types%s' % (cmdword, semicolon))
|
||||
self.assertNoHasColors(output)
|
||||
self.assertEqual(output, {'2.0.0': table_desc2, '3.0.0': table_desc3}[v])
|
||||
|
||||
def test_describe_columnfamilies_output(self):
|
||||
output_re = r'''
|
||||
\n
|
||||
Keyspace [ ] (?P<ksname> \S+ ) \n
|
||||
-----------* \n
|
||||
(?P<cfnames> .*? )
|
||||
\n
|
||||
'''
|
||||
|
||||
ks = get_test_keyspace()
|
||||
|
||||
with testrun_cqlsh(tty=True, keyspace=None, cqlver=3) as c:
|
||||
|
||||
# when not in a keyspace
|
||||
for cmdword in ('DESCRIBE COLUMNFAMILIES', 'desc tables'):
|
||||
for semicolon in (';', ''):
|
||||
ksnames = []
|
||||
output = c.cmd_and_response(cmdword + semicolon)
|
||||
self.assertNoHasColors(output)
|
||||
self.assertRegexpMatches(output, '(?xs) ^ ( %s )+ $' % output_re)
|
||||
|
||||
for section in re.finditer('(?xs)' + output_re, output):
|
||||
ksname = section.group('ksname')
|
||||
ksnames.append(ksname)
|
||||
cfnames = section.group('cfnames')
|
||||
self.assertNotIn('\n\n', cfnames)
|
||||
if ksname == ks:
|
||||
self.assertIn('ascii_with_invalid_and_special_chars', cfnames)
|
||||
|
||||
self.assertIn('system', ksnames)
|
||||
self.assertIn(quote_name('3.0.0', ks), ksnames)
|
||||
|
||||
# when in a keyspace
|
||||
c.send('USE %s;\n' % quote_name('3.0.0', ks))
|
||||
c.read_to_next_prompt()
|
||||
|
||||
for cmdword in ('DESCRIBE COLUMNFAMILIES', 'desc tables'):
|
||||
for semicolon in (';', ''):
|
||||
output = c.cmd_and_response(cmdword + semicolon)
|
||||
self.assertNoHasColors(output)
|
||||
self.assertEqual(output[0], '\n')
|
||||
self.assertEqual(output[-1], '\n')
|
||||
self.assertNotIn('Keyspace %s' % quote_name('3.0.0', ks), output)
|
||||
self.assertIn('has_value_encoding_errors', output)
|
||||
self.assertIn('undefined_values_table', output)
|
||||
|
||||
def test_describe_cluster_output(self):
|
||||
output_re = r'''(?x)
|
||||
^
|
||||
\n
|
||||
Cluster: [ ] (?P<clustername> .* ) \n
|
||||
Partitioner: [ ] (?P<partitionername> .* ) \n
|
||||
Snitch: [ ] (?P<snitchname> .* ) \n
|
||||
\n
|
||||
'''
|
||||
|
||||
ringinfo_re = r'''
|
||||
Range[ ]ownership: \n
|
||||
(
|
||||
[ ] .*? [ ][ ] \[ ( \d+ \. ){3} \d+ \] \n
|
||||
)+
|
||||
\n
|
||||
'''
|
||||
|
||||
with testrun_cqlsh(tty=True, keyspace=None, cqlver=3) as c:
|
||||
|
||||
# not in a keyspace
|
||||
for semicolon in ('', ';'):
|
||||
output = c.cmd_and_response('describe cluster' + semicolon)
|
||||
self.assertNoHasColors(output)
|
||||
self.assertRegexpMatches(output, output_re + '$')
|
||||
|
||||
c.send('USE %s;\n' % quote_name('3.0.0', get_test_keyspace()))
|
||||
c.read_to_next_prompt()
|
||||
|
||||
for semicolon in ('', ';'):
|
||||
output = c.cmd_and_response('describe cluster' + semicolon)
|
||||
self.assertNoHasColors(output)
|
||||
self.assertRegexpMatches(output, output_re + ringinfo_re + '$')
|
||||
|
||||
def test_describe_schema_output(self):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
for semicolon in ('', ';'):
|
||||
output = c.cmd_and_response('desc schema' + semicolon)
|
||||
self.assertNoHasColors(output)
|
||||
self.assertRegexpMatches(output, '^\nCREATE KEYSPACE')
|
||||
self.assertIn("\nCREATE KEYSPACE system WITH replication = {\n 'class': 'LocalStrategy'\n};\n",
|
||||
output)
|
||||
self.assertRegexpMatches(output, ';\s*$')
|
||||
|
||||
def test_show_output(self):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
output = c.cmd_and_response('show version;')
|
||||
self.assertRegexpMatches(output,
|
||||
'^\[cqlsh \S+ \| Cassandra \S+ \| CQL spec \S+ \| Thrift protocol \S+\]$')
|
||||
|
||||
output = c.cmd_and_response('show host;')
|
||||
self.assertHasColors(output)
|
||||
self.assertRegexpMatches(output, '^Connected to .* at %s:%d\.$'
|
||||
% (re.escape(TEST_HOST), TEST_PORT))
|
||||
|
||||
def test_show_assumptions_output(self):
|
||||
expected_output = '\nUSE %s;\n\n' % quote_name('', get_test_keyspace())
|
||||
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
output = c.cmd_and_response('show assumptions')
|
||||
self.assertEqual(output, 'No overrides.\n')
|
||||
|
||||
c.cmd_and_response('assume dynamic_values VALUES aRe uuid;')
|
||||
expected_output += 'ASSUME dynamic_values VALUES ARE uuid;\n'
|
||||
output = c.cmd_and_response('show assumptions')
|
||||
self.assertEqual(output, expected_output + '\n')
|
||||
|
||||
c.cmd_and_response('Assume has_all_types names arE float;')
|
||||
expected_output += 'ASSUME has_all_types NAMES ARE float;\n'
|
||||
output = c.cmd_and_response('show assumptions')
|
||||
self.assertEqual(output, expected_output + '\n')
|
||||
|
||||
c.cmd_and_response('assume twenty_rows_table ( b ) values are decimal;')
|
||||
expected_output += 'ASSUME twenty_rows_table(b) VALUES ARE decimal;\n'
|
||||
output = c.cmd_and_response('show assumptions')
|
||||
self.assertEqual(output, expected_output + '\n')
|
||||
|
||||
def test_eof_prints_newline(self):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
c.send(CONTROL_D)
|
||||
out = c.read_lines(1)[0].replace('\r', '')
|
||||
self.assertEqual(out, '\n')
|
||||
with self.assertRaises(BaseException) as cm:
|
||||
c.read_lines(1)
|
||||
self.assertIn(type(cm.exception), (EOFError, OSError))
|
||||
|
||||
def test_exit_prints_no_newline(self):
|
||||
for semicolon in ('', ';'):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
cmd = 'exit%s\n' % semicolon
|
||||
c.send(cmd)
|
||||
out = c.read_lines(1)[0].replace('\r', '')
|
||||
self.assertEqual(out, cmd)
|
||||
with self.assertRaises(BaseException) as cm:
|
||||
c.read_lines(1)
|
||||
self.assertIn(type(cm.exception), (EOFError, OSError))
|
||||
|
||||
def test_help_types(self):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
output = c.cmd_and_response('help types')
|
||||
|
||||
|
||||
def test_help(self):
|
||||
pass
|
||||
|
||||
def test_printing_parse_error(self):
|
||||
pass
|
||||
|
||||
def test_printing_lex_error(self):
|
||||
pass
|
||||
|
||||
def test_multiline_statements(self):
|
||||
pass
|
||||
|
||||
def test_cancel_statement(self):
|
||||
pass
|
||||
|
||||
def test_printing_integrity_error(self):
|
||||
pass
|
||||
|
||||
def test_printing_cql_error(self):
|
||||
pass
|
||||
|
||||
def test_empty_line(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
CREATE TABLE has_all_types (
|
||||
num int PRIMARY KEY,
|
||||
intcol int,
|
||||
asciicol ascii,
|
||||
bigintcol bigint,
|
||||
blobcol blob,
|
||||
booleancol boolean,
|
||||
decimalcol decimal,
|
||||
doublecol double,
|
||||
floatcol float,
|
||||
textcol text,
|
||||
timestampcol timestamp,
|
||||
uuidcol uuid,
|
||||
varcharcol varchar,
|
||||
varintcol varint
|
||||
);
|
||||
|
||||
INSERT INTO has_all_types (num, intcol, asciicol, bigintcol, blobcol, booleancol,
|
||||
decimalcol, doublecol, floatcol, textcol,
|
||||
timestampcol, uuidcol, varcharcol, varintcol)
|
||||
VALUES (0, -12, 'abcdefg', 1234567890123456789, '000102030405fffefd', 'true',
|
||||
19952.11882, 1.0, -2.1, 'Voilá!', '2012-05-14 12:53:20+0000',
|
||||
bd1924e1-6af8-44ae-b5e1-f24131dbd460, '"', 10000000000000000000000000);
|
||||
|
||||
INSERT INTO has_all_types (num, intcol, asciicol, bigintcol, blobcol, booleancol,
|
||||
decimalcol, doublecol, floatcol, textcol,
|
||||
timestampcol, uuidcol, varcharcol, varintcol)
|
||||
VALUES (1, 2147483647, '__!''$#@!~"', 9223372036854775807, 'ffffffffffffffffff', true,
|
||||
0.00000000000001, 9999999.999, 99999.99, '∭Ƕ⑮ฑ➳❏''', '1900-01-01+0000',
|
||||
ffffffff-ffff-ffff-ffff-ffffffffffff, 'newline->
|
||||
<-', 9);
|
||||
|
||||
INSERT INTO has_all_types (num, intcol, asciicol, bigintcol, blobcol, booleancol,
|
||||
decimalcol, doublecol, floatcol, textcol,
|
||||
timestampcol, uuidcol, varcharcol, varintcol)
|
||||
VALUES (2, 0, '', 0, '', false,
|
||||
0.0, 0.0, 0.0, '', 0,
|
||||
00000000-0000-0000-0000-000000000000, '', 0);
|
||||
|
||||
INSERT INTO has_all_types (num, intcol, asciicol, bigintcol, blobcol, booleancol,
|
||||
decimalcol, doublecol, floatcol, textcol,
|
||||
timestampcol, uuidcol, varcharcol, varintcol)
|
||||
VALUES (3, -2147483648, '''''''', -9223372036854775808, '80', 'false',
|
||||
10.0000000000000, -1004.10, 100000000.9, '龍馭鬱', '2038-01-19T03:14-1200',
|
||||
ffffffff-ffff-1fff-8fff-ffffffffffff, '''', -10000000000000000000000000);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE has_value_encoding_errors (
|
||||
pkey ascii PRIMARY KEY,
|
||||
utf8col blob
|
||||
);
|
||||
|
||||
INSERT INTO has_value_encoding_errors (pkey, utf8col) VALUES ('A', '00ff00ff');
|
||||
ALTER TABLE has_value_encoding_errors ALTER utf8col TYPE text;
|
||||
|
||||
CREATE TABLE has_key_encoding_errors (
|
||||
pkey blob PRIMARY KEY,
|
||||
col text
|
||||
);
|
||||
|
||||
INSERT INTO has_key_encoding_errors (pkey, col) VALUES ('00ff028f', 'whatever');
|
||||
ALTER TABLE has_key_encoding_errors ALTER pkey TYPE text;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE empty_table (
|
||||
lonelykey float primary key,
|
||||
lonelycol text
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE COLUMNFAMILY dynamic_columns (
|
||||
somekey int PRIMARY KEY
|
||||
) WITH comparator = float
|
||||
AND default_validation = text;
|
||||
|
||||
INSERT INTO dynamic_columns (somekey, 1.2) VALUES (1, 'one point two');
|
||||
INSERT INTO dynamic_columns (somekey, 2.3) VALUES (2, 'two point three');
|
||||
INSERT INTO dynamic_columns (somekey, 3.46) VALUES (3, 'three point four six');
|
||||
INSERT INTO dynamic_columns (somekey, 99.0) VALUES (3, 'ninety-nine point oh');
|
||||
INSERT INTO dynamic_columns (somekey, -0.0001) VALUES (3, 'negative ten thousandth');
|
||||
|
||||
|
||||
|
||||
CREATE TABLE twenty_rows_table (
|
||||
a text primary key,
|
||||
b text
|
||||
);
|
||||
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (1, 1);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (2, 2);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (3, 3);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (4, 4);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (5, 5);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (6, 6);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (7, 7);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (8, 8);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (9, 9);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (10, 10);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (11, 11);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (12, 12);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (13, 13);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (14, 14);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (15, 15);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (16, 16);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (17, 17);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (18, 18);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (19, 19);
|
||||
INSERT INTO twenty_rows_table (a, b) VALUES (20, 20);
|
||||
|
||||
-- one row is twenty cols wide
|
||||
INSERT INTO twenty_rows_table (a, b1) VALUES (1, 1);
|
||||
INSERT INTO twenty_rows_table (a, b2) VALUES (1, 2);
|
||||
INSERT INTO twenty_rows_table (a, b3) VALUES (1, 3);
|
||||
INSERT INTO twenty_rows_table (a, b4) VALUES (1, 4);
|
||||
INSERT INTO twenty_rows_table (a, b5) VALUES (1, 5);
|
||||
INSERT INTO twenty_rows_table (a, b6) VALUES (1, 6);
|
||||
INSERT INTO twenty_rows_table (a, b7) VALUES (1, 7);
|
||||
INSERT INTO twenty_rows_table (a, b8) VALUES (1, 8);
|
||||
INSERT INTO twenty_rows_table (a, b9) VALUES (1, 9);
|
||||
INSERT INTO twenty_rows_table (a, b10) VALUES (1, 10);
|
||||
INSERT INTO twenty_rows_table (a, b11) VALUES (1, 11);
|
||||
INSERT INTO twenty_rows_table (a, b12) VALUES (1, 12);
|
||||
INSERT INTO twenty_rows_table (a, b13) VALUES (1, 13);
|
||||
INSERT INTO twenty_rows_table (a, b14) VALUES (1, 14);
|
||||
INSERT INTO twenty_rows_table (a, b15) VALUES (1, 15);
|
||||
INSERT INTO twenty_rows_table (a, b16) VALUES (1, 16);
|
||||
INSERT INTO twenty_rows_table (a, b17) VALUES (1, 17);
|
||||
INSERT INTO twenty_rows_table (a, b18) VALUES (1, 18);
|
||||
INSERT INTO twenty_rows_table (a, b19) VALUES (1, 19);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE undefined_values_table (
|
||||
k text PRIMARY KEY,
|
||||
c text,
|
||||
notthere text
|
||||
);
|
||||
|
||||
INSERT INTO undefined_values_table (k, c) VALUES ('k1', 'c1');
|
||||
INSERT INTO undefined_values_table (k, c) VALUES ('k2', 'c2');
|
||||
|
||||
|
||||
|
||||
CREATE TABLE ascii_with_invalid_and_special_chars (
|
||||
k int PRIMARY KEY,
|
||||
val blob
|
||||
);
|
||||
|
||||
-- "newline:\n"
|
||||
INSERT INTO ascii_with_invalid_and_special_chars (k, val) VALUES (0, '6e65776c696e653a0a');
|
||||
-- "return\rand null\0!"
|
||||
INSERT INTO ascii_with_invalid_and_special_chars (k, val) VALUES (1, '72657475726e0d616e64206e756c6c0021');
|
||||
-- "\x00\x01\x02\x03\x04\x05control chars\x06\x07"
|
||||
INSERT INTO ascii_with_invalid_and_special_chars (k, val) VALUES (2, '000102030405636f6e74726f6c2063686172730607');
|
||||
-- "\xfe\xffbyte order mark"
|
||||
INSERT INTO ascii_with_invalid_and_special_chars (k, val) VALUES (3, 'feff62797465206f72646572206d61726b');
|
||||
-- "fake special chars\\x00\\n"
|
||||
INSERT INTO ascii_with_invalid_and_special_chars (k, val) VALUES (4, '66616b65207370656369616c2063686172735c7830305c6e');
|
||||
|
||||
ALTER TABLE ascii_with_invalid_and_special_chars ALTER val TYPE ascii;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE utf8_with_special_chars (
|
||||
k int PRIMARY KEY,
|
||||
val text
|
||||
);
|
||||
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (0, 'Normal string');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (1, 'Text with
|
||||
newlines
|
||||
');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (2, 'Text with embedded char');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (3, 'ⓈⓅⒺⒸⒾⒶⓁ ⒞⒣⒜⒭⒮ and normal ones');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (4, 'double wides: ⾑⾤⾚');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (5, 'zero widthspace');
|
||||
INSERT INTO utf8_with_special_chars (k, val) VALUES (6, 'fake special chars\x00\n');
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
CREATE TABLE empty_composite_table (
|
||||
lonelykey float,
|
||||
lonelycol text,
|
||||
lonelyval int,
|
||||
primary key (lonelykey, lonelycol)
|
||||
);
|
||||
|
||||
CREATE TABLE twenty_rows_composite_table (
|
||||
a text,
|
||||
b text,
|
||||
c text,
|
||||
primary key (a, b)
|
||||
);
|
||||
|
||||
-- all in the same storage engine row:
|
||||
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 1, 1);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 2, 2);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 3, 3);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 4, 4);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 5, 5);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 6, 6);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 7, 7);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 8, 8);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 9, 9);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 10, 10);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 11, 11);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 12, 12);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 13, 13);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 14, 14);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 15, 15);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 16, 16);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 17, 17);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 18, 18);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 19, 19);
|
||||
INSERT INTO twenty_rows_composite_table (a, b, c) VALUES ('A', 20, 20);
|
||||
Loading…
Reference in New Issue