Merge branch 'cassandra-1.2' into trunk

Conflicts:
	pylib/cqlshlib/cql3handling.py
This commit is contained in:
Aleksey Yeschenko 2012-12-28 02:45:32 +03:00
commit 125a8c9c79
18 changed files with 2563 additions and 54 deletions

View File

@ -23,6 +23,7 @@
1.2.0
* Disallow counters in collections (CASSANDRA-5082)
* cqlsh: add unit tests (CASSANDRA-3920)
1.2.0-rc2

View File

@ -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

View File

@ -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

View File

@ -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=' '):

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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));

View File

@ -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

View File

@ -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

View File

@ -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'")

View File

@ -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

View File

@ -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

View File

@ -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');

View File

@ -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);