You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by al...@apache.org on 2012/12/28 00:37:37 UTC
[1/3] cqlsh: add unit tests;
patch by Paul Cannon with minor changes by Aleksey Yeschenko,
reviewed by Aleksey Yeschenko for CASSANDRA-3920
http://git-wip-us.apache.org/repos/asf/cassandra/blob/14d62ab1/pylib/cqlshlib/test/test_cqlsh_output.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/test/test_cqlsh_output.py b/pylib/cqlshlib/test/test_cqlsh_output.py
new file mode 100644
index 0000000..dbce527
--- /dev/null
+++ b/pylib/cqlshlib/test/test_cqlsh_output.py
@@ -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
http://git-wip-us.apache.org/repos/asf/cassandra/blob/14d62ab1/pylib/cqlshlib/test/test_keyspace_init2.cql
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/test/test_keyspace_init2.cql b/pylib/cqlshlib/test/test_keyspace_init2.cql
new file mode 100644
index 0000000..415becc
--- /dev/null
+++ b/pylib/cqlshlib/test/test_keyspace_init2.cql
@@ -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');
http://git-wip-us.apache.org/repos/asf/cassandra/blob/14d62ab1/pylib/cqlshlib/test/test_keyspace_init3.cql
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/test/test_keyspace_init3.cql b/pylib/cqlshlib/test/test_keyspace_init3.cql
new file mode 100644
index 0000000..8c5e4be
--- /dev/null
+++ b/pylib/cqlshlib/test/test_keyspace_init3.cql
@@ -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);