Skip to content

Commit

Permalink
cqlsh.py: Send DESCRIBE statement to server before parsing
Browse files Browse the repository at this point in the history
Attempting to parse a DESCRIBE statement before performing
it forces us to update cqlsh whenever a new element of
the schema that could be described is introduced. We should
try to rely on the server and only in the case of a failure
(when the version of the server is old and it doesn't
recognize the query) should we try to parse it and perform
locally.

Trying to parse it first may lead to rejecting DESCRIBE
statements that would otherwise be valid -- if the grammar
used by cqlsh hasn't caught up with Scylla yet.
  • Loading branch information
dawmd authored and fruch committed Sep 2, 2024
1 parent 6f4606f commit 4b4bcfc
Showing 1 changed file with 55 additions and 37 deletions.
92 changes: 55 additions & 37 deletions bin/cqlsh.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,21 @@ def handle_statement(self, tokens, srcstr):
cmdword = tokens[0][1]
if cmdword == '?':
cmdword = 'help'

cmdword_lower = cmdword.lower()

# Describe statements get special treatment: we first want to
# send the request to the server and only when it fails will
# we attempt to perform it locally. That's why we don't want
# to follow the logic below that starts with parsing.
#
# The reason for that is changes in Scylla may need to be reflected
# in the grammar used in cqlsh. We want Scylla to be "independent"
# in that regard, so unless necessary, we don't want to do the parsing
# here.
if cmdword_lower == 'describe' or cmdword_lower == 'desc':
return self.perform_describe(cmdword, tokens, srcstr)

custom_handler = getattr(self, 'do_' + cmdword.lower(), None)
if custom_handler:
parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr,
Expand Down Expand Up @@ -1497,8 +1512,8 @@ def describe_schema_client(self, include_system=False):
self.print_recreate_keyspace(k, sys.stdout)
print('')

def do_describe(self, parsed):

# Precondition: the first token in `srcstr.lower()` is either `describe` or `desc`.
def perform_describe(self, cmdword, tokens, srcstr):
"""
DESCRIBE [cqlsh only]
Expand Down Expand Up @@ -1589,10 +1604,8 @@ def do_describe(self, parsed):
where object can be either a keyspace or a table or an index or a materialized
view (in this order).
"""
self._do_describe(parsed, force_client_side_describe=False)

def _do_describe(self, parsed, force_client_side_describe):
if force_client_side_describe:
def perform_describe_locally(parsed):
what = parsed.matched[1][1].lower()
if what == 'functions':
self.describe_functions_client(self.current_keyspace)
Expand Down Expand Up @@ -1650,40 +1663,45 @@ def _do_describe(self, parsed, force_client_side_describe):
if not name:
name = self.cql_unprotect_name(parsed.get_binding('mvname', None))
self.describe_object_client(ks, name)
else:
stmt = SimpleStatement(parsed.extract_orig(), consistency_level=cassandra.ConsistencyLevel.LOCAL_ONE,
fetch_size=self.page_size if self.use_paging else None)
future = self.session.execute_async(stmt)
try:
result = future.result()

what = parsed.matched[1][1].lower()

if what in ('columnfamilies', 'tables', 'types', 'functions', 'aggregates'):
self.describe_list(result)
elif what == 'keyspaces':
self.describe_keyspaces(result)
elif what == 'cluster':
self.describe_cluster(result)
elif what:
self.describe_element(result)

except cassandra.protocol.SyntaxException:
# Server doesn't support DESCRIBE query, retry with
# client-side DESCRIBE implementation
self._do_describe(parsed, force_client_side_describe=True)
except CQL_ERRORS as err:
err_msg = err.message if hasattr(err, 'message') else str(err)
self.printerr(err_msg.partition("message=")[2].strip('"'))
except Exception:
import traceback
self.printerr(traceback.format_exc())

if future:
if future.warnings:
self.print_warnings(future.warnings)
stmt = SimpleStatement(srcstr, consistency_level=cassandra.ConsistencyLevel.LOCAL_ONE,
fetch_size=self.page_size if self.use_paging else None)
future = self.session.execute_async(stmt)
try:
result = future.result()

# The second token in the statement indicates which
# kind of DESCRIBE we're performing.
what = srcstr.split()[1].lower().rstrip(';')

if what in ('columnfamilies', 'tables', 'types', 'functions', 'aggregates'):
self.describe_list(result)
elif what == 'keyspaces':
self.describe_keyspaces(result)
elif what == 'cluster':
self.describe_cluster(result)
elif what:
self.describe_element(result)

except cassandra.protocol.SyntaxException:
# Server doesn't support DESCRIBE query, retry with
# client-side DESCRIBE implementation
parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr,
startsymbol='cqlshCommand')
if parsed and not parsed.remainder:
return perform_describe_locally(parsed)
else:
return self.handle_parse_error(cmdword, tokens, parsed, srcstr)
except CQL_ERRORS as err:
err_msg = err.message if hasattr(err, 'message') else str(err)
self.printerr(err_msg.partition("message=")[2].strip('"'))
except Exception:
import traceback
self.printerr(traceback.format_exc())

do_desc = do_describe
if future:
if future.warnings:
self.print_warnings(future.warnings)

def describe_keyspaces(self, rows):
"""
Expand Down

0 comments on commit 4b4bcfc

Please sign in to comment.