From d586ea89800ddbba38eea3bd08e2d107bf765344 Mon Sep 17 00:00:00 2001 From: Tyler Hobbs Date: Thu, 21 Aug 2014 11:54:47 -0500 Subject: [PATCH] cqlsh: Fix handling of CAS statement results Patch by Tyler Hobbs; reviewed by Aleksey Yeschenko for CASSANDRA-7671 --- CHANGES.txt | 1 + bin/cqlsh | 34 ++++++++++++++++++---------------- pylib/cqlshlib/cql3handling.py | 13 +++++++++++-- pylib/cqlshlib/pylexotron.py | 4 +++- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2320653ce5..11ec7ff1f8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.1 + * (cqlsh) Fix handling of CAS statement results (CASSANDRA-7671) * (cqlsh) COPY TO/FROM improvements (CASSANDRA-7405) * Support list index operations with conditions (CASSANDRA-7499) * Add max live/tombstoned cells to nodetool cfstats output (CASSANDRA-7731) diff --git a/bin/cqlsh b/bin/cqlsh index 340566d7eb..b633e93b5d 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -585,13 +585,13 @@ class Shell(cmd.Cmd): self.decoding_errors.append(err) return format_value(err, self.output_codec.name, addcolor=self.color) - def myformat_colname(self, name, cfMetaData = None): + def myformat_colname(self, name, table_meta=None): column_colors = COLUMN_NAME_COLORS.copy() # check column role and color appropriately - if cfMetaData: - if name in [col.name for col in cfMetaData.partition_key]: + if table_meta: + if name in [col.name for col in table_meta.partition_key]: column_colors.default_factory = lambda : RED - elif name in [col.name for col in cfMetaData.clustering_key]: + elif name in [col.name for col in table_meta.clustering_key]: column_colors.default_factory = lambda : CYAN return self.myformat_value(name, colormap=column_colors) @@ -913,6 +913,12 @@ class Shell(cmd.Cmd): return result + def parse_for_table_meta(self, query_string): + parsed = cqlruleset.cql_parse(query_string)[1] + ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) + cf = self.cql_unprotect_name(parsed.get_binding('cfname')) + return self.get_table_meta(ks, cf) + def perform_simple_statement(self, statement, with_default_limit=False): if not statement: return False @@ -930,25 +936,21 @@ class Shell(cmd.Cmd): return False if statement.query_string[:6].lower() == 'select' or statement.query_string.lower().startswith("list"): - parsed = cqlruleset.cql_parse(statement.query_string)[1] - ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) - cf = self.cql_unprotect_name(parsed.get_binding('cfname')) - cfMetaData = self.get_table_meta(ks, cf) - self.print_result(rows, with_default_limit, cfMetaData) + self.print_result(rows, with_default_limit, self.parse_for_table_meta(statement.query_string)) elif rows: # CAS INSERT/UPDATE self.writeresult("") - self.print_static_result(rows) + self.print_static_result(rows, self.parse_for_table_meta(statement.query_string)) self.flush_output() return True - def print_result(self, rows, with_default_limit, cfMetaData): + def print_result(self, rows, with_default_limit, table_meta): self.decoding_errors = [] self.writeresult("") if rows: rows = list(rows) # this may be an iterator if the result is large enough to page - self.print_static_result(rows, cfMetaData) + self.print_static_result(rows, table_meta) self.writeresult("(%d rows)" % len(rows or [])) if self.decoding_errors: @@ -965,16 +967,16 @@ class Shell(cmd.Cmd): % DEFAULT_SELECT_LIMIT, color=RED) self.writeresult("") - def print_static_result(self, rows, cfMetaData): + def print_static_result(self, rows, table_meta): if not rows: # print header only - colnames = cfMetaData.columns.keys() # full header - formatted_names = [self.myformat_colname(name, cfMetaData) for name in colnames] + colnames = table_meta.columns.keys() # full header + formatted_names = [self.myformat_colname(name, table_meta) for name in colnames] self.print_formatted_result(formatted_names, None) return colnames = rows[0].keys() - formatted_names = [self.myformat_colname(name, cfMetaData) for name in colnames] + formatted_names = [self.myformat_colname(name, table_meta) for name in colnames] formatted_values = [map(self.myformat_value, row.values()) for row in rows] if self.expand_enabled: diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 0888d64fd7..e4ef67ba66 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -185,6 +185,7 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | | | + | "NULL" ; ::= "(" ( ( "," )* )? ")" @@ -673,6 +674,7 @@ syntax_rules += r''' ( "," [colname]= )* ")" "VALUES" "(" [newval]= valcomma="," [newval]= ( valcomma="," [newval]= )* valcomma=")" + ( "IF" "NOT" "EXISTS")? ( "USING" [insertopt]= ( "AND" [insertopt]= )* )? ; @@ -741,13 +743,19 @@ syntax_rules += r''' ( "AND" [updateopt]= )* )? "SET" ( "," )* "WHERE" + ( "IF" )? ; ::= updatecol= - ( "=" update_rhs=( | ) + ( "=" update_rhs=( | ) ( counterop=( "+" | "-" ) inc= - | listadder="+" listcol= ) + | listadder="+" listcol= )? | indexbracket="[" "]" "=" ) ; + ::= ( "AND" )* + ; + ::= ( "[" "]" )? ( ( "=" | "<" | ">" | "<=" | ">=" | "!=" ) + | "IN" "(" ( "," )* ")") + ; ''' @completer_for('updateStatement', 'updateopt') @@ -819,6 +827,7 @@ syntax_rules += r''' "FROM" cf= ( "USING" [delopt]= )? "WHERE" + ( "IF" ( "EXISTS" | ) )? ; ::= delcol= ( memberbracket="[" memberselector= "]" )? ; diff --git a/pylib/cqlshlib/pylexotron.py b/pylib/cqlshlib/pylexotron.py index ad283dfaba..b4ac36f599 100644 --- a/pylib/cqlshlib/pylexotron.py +++ b/pylib/cqlshlib/pylexotron.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from functools import partial import re from .saferscanner import SaferScanner @@ -24,6 +23,9 @@ class LexingError(Exception): bad_char = len(rulestr) - len(unmatched) linenum = rulestr[:bad_char].count('\n') + 1 charnum = len(rulestr[:bad_char].rsplit('\n', 1)[-1]) + 1 + snippet_start = max(0, min(len(rulestr), bad_char - 10)) + snippet_end = max(0, min(len(rulestr), bad_char + 10)) + msg += " (Error at: '...%s...')" % (rulestr[snippet_start:snippet_end],) raise cls(linenum, charnum, msg) def __init__(self, linenum, charnum, msg='Lexing error'):