scylla-cqlsh 6.0.30__cp314-cp314-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copyutil.cpython-314-aarch64-linux-musl.so +0 -0
- cqlsh/__init__.py +1 -0
- cqlsh/__main__.py +11 -0
- cqlsh/cqlsh.py +2751 -0
- cqlshlib/__init__.py +90 -0
- cqlshlib/_version.py +34 -0
- cqlshlib/authproviderhandling.py +176 -0
- cqlshlib/copyutil.py +2762 -0
- cqlshlib/cql3handling.py +1670 -0
- cqlshlib/cqlhandling.py +333 -0
- cqlshlib/cqlshhandling.py +314 -0
- cqlshlib/displaying.py +128 -0
- cqlshlib/formatting.py +601 -0
- cqlshlib/helptopics.py +190 -0
- cqlshlib/pylexotron.py +562 -0
- cqlshlib/saferscanner.py +91 -0
- cqlshlib/sslhandling.py +109 -0
- cqlshlib/tracing.py +90 -0
- cqlshlib/util.py +183 -0
- cqlshlib/wcwidth.py +379 -0
- scylla_cqlsh-6.0.30.dist-info/METADATA +108 -0
- scylla_cqlsh-6.0.30.dist-info/RECORD +26 -0
- scylla_cqlsh-6.0.30.dist-info/WHEEL +5 -0
- scylla_cqlsh-6.0.30.dist-info/entry_points.txt +2 -0
- scylla_cqlsh-6.0.30.dist-info/licenses/LICENSE.txt +204 -0
- scylla_cqlsh-6.0.30.dist-info/top_level.txt +3 -0
cqlsh/cqlsh.py
ADDED
|
@@ -0,0 +1,2751 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
import cmd
|
|
20
|
+
import codecs
|
|
21
|
+
import configparser
|
|
22
|
+
import csv
|
|
23
|
+
import errno
|
|
24
|
+
import getpass
|
|
25
|
+
import optparse
|
|
26
|
+
import os
|
|
27
|
+
import platform
|
|
28
|
+
import re
|
|
29
|
+
import stat
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
import traceback
|
|
33
|
+
import warnings
|
|
34
|
+
import webbrowser
|
|
35
|
+
import logging
|
|
36
|
+
from contextlib import contextmanager
|
|
37
|
+
from glob import glob
|
|
38
|
+
from io import StringIO
|
|
39
|
+
from uuid import UUID
|
|
40
|
+
|
|
41
|
+
if sys.version_info < (3, 6):
|
|
42
|
+
sys.exit("\ncqlsh requires Python 3.6+\n")
|
|
43
|
+
|
|
44
|
+
# see CASSANDRA-10428
|
|
45
|
+
if platform.python_implementation().startswith('Jython'):
|
|
46
|
+
sys.exit("\nCQL Shell does not run on Jython\n")
|
|
47
|
+
|
|
48
|
+
UTF8 = 'utf-8'
|
|
49
|
+
|
|
50
|
+
description = "CQL Shell for Apache Cassandra"
|
|
51
|
+
|
|
52
|
+
readline = None
|
|
53
|
+
try:
|
|
54
|
+
# check if tty first, cause readline doesn't check, and only cares
|
|
55
|
+
# about $TERM. we don't want the funky escape code stuff to be
|
|
56
|
+
# output if not a tty.
|
|
57
|
+
if sys.stdin.isatty():
|
|
58
|
+
import readline
|
|
59
|
+
except ImportError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
CQL_LIB_PREFIX = 'scylla-driver-'
|
|
63
|
+
|
|
64
|
+
CASSANDRA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
|
|
65
|
+
CASSANDRA_CQL_HTML_FALLBACK = 'https://cassandra.apache.org/doc/latest/cql/index.html'
|
|
66
|
+
|
|
67
|
+
# default location of local CQL.html
|
|
68
|
+
if os.path.exists(CASSANDRA_PATH + '/doc/cql3/CQL.html'):
|
|
69
|
+
# default location of local CQL.html
|
|
70
|
+
CASSANDRA_CQL_HTML = 'file://' + CASSANDRA_PATH + '/doc/cql3/CQL.html'
|
|
71
|
+
elif os.path.exists('/usr/share/doc/cassandra/CQL.html'):
|
|
72
|
+
# fallback to package file
|
|
73
|
+
CASSANDRA_CQL_HTML = 'file:///usr/share/doc/cassandra/CQL.html'
|
|
74
|
+
else:
|
|
75
|
+
# fallback to online version
|
|
76
|
+
CASSANDRA_CQL_HTML = CASSANDRA_CQL_HTML_FALLBACK
|
|
77
|
+
|
|
78
|
+
# On Linux, the Python webbrowser module uses the 'xdg-open' executable
|
|
79
|
+
# to open a file/URL. But that only works, if the current session has been
|
|
80
|
+
# opened from _within_ a desktop environment. I.e. 'xdg-open' will fail,
|
|
81
|
+
# if the session's been opened via ssh to a remote box.
|
|
82
|
+
#
|
|
83
|
+
try:
|
|
84
|
+
webbrowser.register_standard_browsers() # registration is otherwise lazy in Python3
|
|
85
|
+
except AttributeError:
|
|
86
|
+
pass
|
|
87
|
+
if webbrowser._tryorder and webbrowser._tryorder[0] == 'xdg-open' and os.environ.get('XDG_DATA_DIRS', '') == '':
|
|
88
|
+
# only on Linux (some OS with xdg-open)
|
|
89
|
+
webbrowser._tryorder.remove('xdg-open')
|
|
90
|
+
webbrowser._tryorder.append('xdg-open')
|
|
91
|
+
|
|
92
|
+
# use bundled lib for python-cql if available. if there
|
|
93
|
+
# is a ../lib dir, use bundled libs there preferentially.
|
|
94
|
+
ZIPLIB_DIRS = [os.path.join(CASSANDRA_PATH, 'lib')]
|
|
95
|
+
|
|
96
|
+
if platform.system() == 'Linux':
|
|
97
|
+
ZIPLIB_DIRS.append('/usr/share/cassandra/lib')
|
|
98
|
+
|
|
99
|
+
if os.environ.get('CQLSH_NO_BUNDLED', ''):
|
|
100
|
+
ZIPLIB_DIRS = ()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def find_zip(libprefix):
|
|
104
|
+
for ziplibdir in ZIPLIB_DIRS:
|
|
105
|
+
zips = glob(os.path.join(ziplibdir, libprefix + '*.zip'))
|
|
106
|
+
if zips:
|
|
107
|
+
return max(zips) # probably the highest version, if multiple
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
cql_zip = find_zip(CQL_LIB_PREFIX)
|
|
111
|
+
if cql_zip:
|
|
112
|
+
sys.path.insert(0, cql_zip)
|
|
113
|
+
|
|
114
|
+
# the driver needs dependencies
|
|
115
|
+
third_parties = ('six-', 'pure_sasl-', 'PyYAML-')
|
|
116
|
+
|
|
117
|
+
for lib in third_parties:
|
|
118
|
+
lib_zip = find_zip(lib)
|
|
119
|
+
if lib_zip:
|
|
120
|
+
sys.path.insert(0, lib_zip)
|
|
121
|
+
|
|
122
|
+
warnings.filterwarnings("ignore", r".*blist.*")
|
|
123
|
+
try:
|
|
124
|
+
import cassandra
|
|
125
|
+
except ImportError as e:
|
|
126
|
+
sys.exit("\nPython Cassandra driver not installed, or not on PYTHONPATH.\n"
|
|
127
|
+
'You might try "pip install cassandra-driver".\n\n'
|
|
128
|
+
'Python: %s\n'
|
|
129
|
+
'Module load path: %r\n\n'
|
|
130
|
+
'Error: %s\n' % (sys.executable, sys.path, e))
|
|
131
|
+
|
|
132
|
+
from cassandra.auth import PlainTextAuthProvider
|
|
133
|
+
from cassandra.cluster import Cluster, EXEC_PROFILE_DEFAULT, ExecutionProfile
|
|
134
|
+
from cassandra.connection import UnixSocketEndPoint
|
|
135
|
+
from cassandra.cqltypes import cql_typename
|
|
136
|
+
from cassandra.marshal import int64_unpack
|
|
137
|
+
from cassandra.metadata import (ColumnMetadata, KeyspaceMetadata, TableMetadata, protect_name, protect_names, protect_value)
|
|
138
|
+
from cassandra.policies import WhiteListRoundRobinPolicy
|
|
139
|
+
from cassandra.query import SimpleStatement, ordered_dict_factory, TraceUnavailable
|
|
140
|
+
from cassandra.util import datetime_from_timestamp
|
|
141
|
+
from cassandra.connection import DRIVER_NAME, DRIVER_VERSION
|
|
142
|
+
|
|
143
|
+
# cqlsh should run correctly when run out of a Cassandra source tree,
|
|
144
|
+
# out of an unpacked Cassandra tarball, and after a proper package install.
|
|
145
|
+
cqlshlibdir = os.path.join(CASSANDRA_PATH, 'pylib')
|
|
146
|
+
if os.path.isdir(cqlshlibdir):
|
|
147
|
+
sys.path.insert(0, cqlshlibdir)
|
|
148
|
+
|
|
149
|
+
from cqlshlib import cql3handling, pylexotron, sslhandling, cqlshhandling, authproviderhandling
|
|
150
|
+
from cqlshlib.copyutil import ExportTask, ImportTask
|
|
151
|
+
from cqlshlib.displaying import (ANSI_RESET, BLUE, COLUMN_NAME_COLORS, CYAN,
|
|
152
|
+
RED, YELLOW, WHITE, FormattedValue, colorme)
|
|
153
|
+
from cqlshlib.formatting import (DEFAULT_DATE_FORMAT, DEFAULT_NANOTIME_FORMAT,
|
|
154
|
+
DEFAULT_TIMESTAMP_FORMAT, CqlType, DateTimeFormat,
|
|
155
|
+
format_by_type)
|
|
156
|
+
from cqlshlib.tracing import print_trace, print_trace_session
|
|
157
|
+
from cqlshlib.util import get_file_encoding_bomsize
|
|
158
|
+
from cqlshlib.util import is_file_secure, trim_if_present
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
from cqlshlib._version import __version__ as version
|
|
162
|
+
except ImportError:
|
|
163
|
+
# Get the version relative to the current file, suppressing UserWarnings from setuptools_scm
|
|
164
|
+
from setuptools_scm import get_version
|
|
165
|
+
|
|
166
|
+
with warnings.catch_warnings():
|
|
167
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
168
|
+
version = get_version(root='..', relative_to=__file__, tag_regex=r'^(?P<prefix>v)?(?P<version>[^\+-]+)(?P<suffix>-scylla)?$')
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
DEFAULT_HOST = '127.0.0.1'
|
|
172
|
+
DEFAULT_PORT = 9042
|
|
173
|
+
DEFAULT_SSL = False
|
|
174
|
+
DEFAULT_CONNECT_TIMEOUT_SECONDS = 5
|
|
175
|
+
DEFAULT_REQUEST_TIMEOUT_SECONDS = 10
|
|
176
|
+
|
|
177
|
+
DEFAULT_FLOAT_PRECISION = 5
|
|
178
|
+
DEFAULT_DOUBLE_PRECISION = 5
|
|
179
|
+
DEFAULT_MAX_TRACE_WAIT = 10
|
|
180
|
+
|
|
181
|
+
if readline is not None and readline.__doc__ is not None and 'libedit' in readline.__doc__:
|
|
182
|
+
DEFAULT_COMPLETEKEY = '\t'
|
|
183
|
+
else:
|
|
184
|
+
DEFAULT_COMPLETEKEY = 'tab'
|
|
185
|
+
|
|
186
|
+
cqldocs = None
|
|
187
|
+
cqlruleset = None
|
|
188
|
+
|
|
189
|
+
epilog = """Connects to %(DEFAULT_HOST)s:%(DEFAULT_PORT)d by default. These
|
|
190
|
+
defaults can be changed by setting $CQLSH_HOST and/or $CQLSH_PORT. When a
|
|
191
|
+
host (and optional port number) are given on the command line, they take
|
|
192
|
+
precedence over any defaults.""" % globals()
|
|
193
|
+
|
|
194
|
+
parser = optparse.OptionParser(description=description, epilog=epilog,
|
|
195
|
+
usage="Usage: %prog [options] [host [port]]",
|
|
196
|
+
version='cqlsh ' + version)
|
|
197
|
+
parser.add_option("-C", "--color", action='store_true', dest='color',
|
|
198
|
+
help='Always use color output')
|
|
199
|
+
parser.add_option("--no-color", action='store_false', dest='color',
|
|
200
|
+
help='Never use color output')
|
|
201
|
+
parser.add_option("--browser", dest='browser', help="""The browser to use to display CQL help, where BROWSER can be:
|
|
202
|
+
- one of the supported browsers in https://docs.python.org/3/library/webbrowser.html.
|
|
203
|
+
- browser path followed by %s, example: /usr/bin/google-chrome-stable %s""")
|
|
204
|
+
parser.add_option('--ssl', action='store_true', help='Use SSL', default=False)
|
|
205
|
+
parser.add_option('--no-compression', action='store_true', help='Disable compression', default=False)
|
|
206
|
+
parser.add_option("-u", "--username", help="Authenticate as user.")
|
|
207
|
+
parser.add_option("-p", "--password", help="Authenticate using password.")
|
|
208
|
+
parser.add_option('-k', '--keyspace', help='Authenticate to the given keyspace.')
|
|
209
|
+
parser.add_option("-f", "--file", help="Execute commands from FILE, then exit")
|
|
210
|
+
parser.add_option('--debug', action='store_true',
|
|
211
|
+
help='Show additional debugging information')
|
|
212
|
+
parser.add_option('--driver-debug', action='store_true',
|
|
213
|
+
help='Show additional driver debugging information')
|
|
214
|
+
parser.add_option('--coverage', action='store_true',
|
|
215
|
+
help='Collect coverage data')
|
|
216
|
+
parser.add_option("--encoding", help="Specify a non-default encoding for output."
|
|
217
|
+
+ " (Default: %s)" % (UTF8,))
|
|
218
|
+
parser.add_option("--cqlshrc", help="Specify an alternative cqlshrc file location.")
|
|
219
|
+
parser.add_option("--credentials", help="Specify an alternative credentials file location.")
|
|
220
|
+
parser.add_option('--cqlversion', default=None,
|
|
221
|
+
help='Specify a particular CQL version, '
|
|
222
|
+
'by default the highest version supported by the server will be used.'
|
|
223
|
+
' Examples: "3.0.3", "3.1.0"')
|
|
224
|
+
parser.add_option("--protocol-version", type="int", default=None,
|
|
225
|
+
help='Specify a specific protocol version otherwise the client will default and downgrade as necessary')
|
|
226
|
+
|
|
227
|
+
parser.add_option("-e", "--execute", help='Execute the statement and quit.')
|
|
228
|
+
parser.add_option("--connect-timeout", default=DEFAULT_CONNECT_TIMEOUT_SECONDS, dest='connect_timeout',
|
|
229
|
+
help='Specify the connection timeout in seconds (default: %default seconds).')
|
|
230
|
+
parser.add_option("--request-timeout", default=DEFAULT_REQUEST_TIMEOUT_SECONDS, dest='request_timeout',
|
|
231
|
+
help='Specify the default request timeout in seconds (default: %default seconds).')
|
|
232
|
+
parser.add_option("-t", "--tty", action='store_true', dest='tty',
|
|
233
|
+
help='Force tty mode (command prompt).')
|
|
234
|
+
parser.add_option('-v', action="version", help='Print the current version of cqlsh.')
|
|
235
|
+
|
|
236
|
+
# This is a hidden option to suppress the warning when the -p/--password command line option is used.
|
|
237
|
+
# Power users may use this option if they know no other people has access to the system where cqlsh is run or don't care about security.
|
|
238
|
+
# Use of this option in scripting is discouraged. Please use a (temporary) credentials file where possible.
|
|
239
|
+
# The Cassandra distributed tests (dtests) also use this option in some tests when a well-known password is supplied via the command line.
|
|
240
|
+
parser.add_option("--insecure-password-without-warning", action='store_true', dest='insecure_password_without_warning',
|
|
241
|
+
help=optparse.SUPPRESS_HELP)
|
|
242
|
+
|
|
243
|
+
opt_values = optparse.Values()
|
|
244
|
+
(options, arguments) = parser.parse_args(sys.argv[1:], values=opt_values)
|
|
245
|
+
|
|
246
|
+
# BEGIN history/config definition
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def mkdirp(path):
|
|
250
|
+
"""Creates all parent directories up to path parameter or fails when path exists, but it is not a directory."""
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
os.makedirs(path)
|
|
254
|
+
except OSError:
|
|
255
|
+
if not os.path.isdir(path):
|
|
256
|
+
raise
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def resolve_cql_history_file():
|
|
260
|
+
default_cql_history = os.path.expanduser(os.path.join('~', '.cassandra', 'cqlsh_history'))
|
|
261
|
+
if 'CQL_HISTORY' in os.environ:
|
|
262
|
+
return os.environ['CQL_HISTORY']
|
|
263
|
+
else:
|
|
264
|
+
return default_cql_history
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
HISTORY = resolve_cql_history_file()
|
|
268
|
+
HISTORY_DIR = os.path.dirname(HISTORY)
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
mkdirp(HISTORY_DIR)
|
|
272
|
+
except OSError:
|
|
273
|
+
print('\nWarning: Cannot create directory at `%s`. Command history will not be saved. Please check what was the environment property CQL_HISTORY set to.\n' % HISTORY_DIR)
|
|
274
|
+
|
|
275
|
+
DEFAULT_CQLSHRC = os.path.expanduser(os.path.join('~', '.cassandra', 'cqlshrc'))
|
|
276
|
+
|
|
277
|
+
if hasattr(options, 'cqlshrc'):
|
|
278
|
+
CONFIG_FILE = os.path.expanduser(options.cqlshrc)
|
|
279
|
+
if not os.path.exists(CONFIG_FILE):
|
|
280
|
+
print('\nWarning: Specified cqlshrc location `%s` does not exist. Using `%s` instead.\n' % (CONFIG_FILE, DEFAULT_CQLSHRC))
|
|
281
|
+
CONFIG_FILE = DEFAULT_CQLSHRC
|
|
282
|
+
else:
|
|
283
|
+
CONFIG_FILE = DEFAULT_CQLSHRC
|
|
284
|
+
|
|
285
|
+
CQL_DIR = os.path.dirname(CONFIG_FILE)
|
|
286
|
+
|
|
287
|
+
CQL_ERRORS = (
|
|
288
|
+
cassandra.AlreadyExists, cassandra.AuthenticationFailed, cassandra.CoordinationFailure,
|
|
289
|
+
cassandra.InvalidRequest, cassandra.Timeout, cassandra.Unauthorized, cassandra.OperationTimedOut,
|
|
290
|
+
cassandra.cluster.NoHostAvailable,
|
|
291
|
+
cassandra.connection.ConnectionBusy, cassandra.connection.ProtocolError, cassandra.connection.ConnectionException,
|
|
292
|
+
cassandra.protocol.ErrorMessage, cassandra.protocol.InternalError, cassandra.query.TraceUnavailable
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
debug_completion = bool(os.environ.get('CQLSH_DEBUG_COMPLETION', '') == 'YES')
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class NoKeyspaceError(Exception):
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class KeyspaceNotFound(Exception):
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class ColumnFamilyNotFound(Exception):
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class IndexNotFound(Exception):
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class MaterializedViewNotFound(Exception):
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class ObjectNotFound(Exception):
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class VersionNotSupported(Exception):
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class UserTypeNotFound(Exception):
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class FunctionNotFound(Exception):
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class AggregateNotFound(Exception):
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class DecodeError(Exception):
|
|
339
|
+
verb = 'decode'
|
|
340
|
+
|
|
341
|
+
def __init__(self, thebytes, err, colname=None):
|
|
342
|
+
self.thebytes = thebytes
|
|
343
|
+
self.err = err
|
|
344
|
+
self.colname = colname
|
|
345
|
+
|
|
346
|
+
def __str__(self):
|
|
347
|
+
return str(self.thebytes)
|
|
348
|
+
|
|
349
|
+
def message(self):
|
|
350
|
+
what = 'value %r' % (self.thebytes,)
|
|
351
|
+
if self.colname is not None:
|
|
352
|
+
what = 'value %r (for column %r)' % (self.thebytes, self.colname)
|
|
353
|
+
return 'Failed to %s %s : %s' \
|
|
354
|
+
% (self.verb, what, self.err)
|
|
355
|
+
|
|
356
|
+
def __repr__(self):
|
|
357
|
+
return '<%s %s>' % (self.__class__.__name__, self.message())
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def maybe_ensure_text(val):
|
|
361
|
+
return str(val) if val else val
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class FormatError(DecodeError):
|
|
365
|
+
verb = 'format'
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def full_cql_version(ver):
|
|
369
|
+
while ver.count('.') < 2:
|
|
370
|
+
ver += '.0'
|
|
371
|
+
ver_parts = ver.split('-', 1) + ['']
|
|
372
|
+
vertuple = tuple(list(map(int, ver_parts[0].split('.'))) + [ver_parts[1]])
|
|
373
|
+
return ver, vertuple
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def format_value(val, cqltype, encoding, addcolor=False, date_time_format=None,
|
|
377
|
+
float_precision=None, colormap=None, nullval=None):
|
|
378
|
+
if isinstance(val, DecodeError):
|
|
379
|
+
if addcolor:
|
|
380
|
+
return colorme(repr(val.thebytes), colormap, 'error')
|
|
381
|
+
else:
|
|
382
|
+
return FormattedValue(repr(val.thebytes))
|
|
383
|
+
return format_by_type(val, cqltype=cqltype, encoding=encoding, colormap=colormap,
|
|
384
|
+
addcolor=addcolor, nullval=nullval, date_time_format=date_time_format,
|
|
385
|
+
float_precision=float_precision)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def show_warning_without_quoting_line(message, category, filename, lineno, file=None, line=None):
|
|
389
|
+
if file is None:
|
|
390
|
+
file = sys.stderr
|
|
391
|
+
try:
|
|
392
|
+
file.write(warnings.formatwarning(message, category, filename, lineno, line=''))
|
|
393
|
+
except IOError:
|
|
394
|
+
pass
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
warnings.showwarning = show_warning_without_quoting_line
|
|
398
|
+
warnings.filterwarnings('always', category=cql3handling.UnexpectedTableStructure)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def insert_driver_hooks():
|
|
402
|
+
|
|
403
|
+
class DateOverFlowWarning(RuntimeWarning):
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
# Native datetime types blow up outside of datetime.[MIN|MAX]_YEAR. We will fall back to an int timestamp
|
|
407
|
+
def deserialize_date_fallback_int(byts, protocol_version):
|
|
408
|
+
timestamp_ms = int64_unpack(byts)
|
|
409
|
+
try:
|
|
410
|
+
return datetime_from_timestamp(timestamp_ms / 1000.0)
|
|
411
|
+
except OverflowError:
|
|
412
|
+
warnings.warn(DateOverFlowWarning("Some timestamps are larger than Python datetime can represent. "
|
|
413
|
+
"Timestamps are displayed in milliseconds from epoch."))
|
|
414
|
+
return timestamp_ms
|
|
415
|
+
|
|
416
|
+
cassandra.cqltypes.DateType.deserialize = staticmethod(deserialize_date_fallback_int)
|
|
417
|
+
|
|
418
|
+
if hasattr(cassandra, 'deserializers'):
|
|
419
|
+
del cassandra.deserializers.DesDateType
|
|
420
|
+
|
|
421
|
+
# Return cassandra.cqltypes.EMPTY instead of None for empty values
|
|
422
|
+
cassandra.cqltypes.CassandraType.support_empty_values = True
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class Shell(cmd.Cmd):
|
|
426
|
+
custom_prompt = os.getenv('CQLSH_PROMPT', '')
|
|
427
|
+
if custom_prompt != '':
|
|
428
|
+
custom_prompt += "\n"
|
|
429
|
+
default_prompt = custom_prompt + "cqlsh> "
|
|
430
|
+
continue_prompt = " ... "
|
|
431
|
+
keyspace_prompt = custom_prompt + "cqlsh:{}> "
|
|
432
|
+
keyspace_continue_prompt = "{} ... "
|
|
433
|
+
show_line_nums = False
|
|
434
|
+
debug = False
|
|
435
|
+
coverage = False
|
|
436
|
+
coveragerc_path = None
|
|
437
|
+
stop = False
|
|
438
|
+
last_hist = None
|
|
439
|
+
shunted_query_out = None
|
|
440
|
+
use_paging = True
|
|
441
|
+
|
|
442
|
+
default_page_size = 100
|
|
443
|
+
|
|
444
|
+
def __init__(self, hostname, port, color=False,
|
|
445
|
+
username=None, encoding=None, stdin=None, tty=True,
|
|
446
|
+
completekey=DEFAULT_COMPLETEKEY, browser=None, use_conn=None,
|
|
447
|
+
cqlver=None, keyspace=None,
|
|
448
|
+
tracing_enabled=False, expand_enabled=False,
|
|
449
|
+
display_nanotime_format=DEFAULT_NANOTIME_FORMAT,
|
|
450
|
+
display_timestamp_format=DEFAULT_TIMESTAMP_FORMAT,
|
|
451
|
+
display_date_format=DEFAULT_DATE_FORMAT,
|
|
452
|
+
display_float_precision=DEFAULT_FLOAT_PRECISION,
|
|
453
|
+
display_double_precision=DEFAULT_DOUBLE_PRECISION,
|
|
454
|
+
display_timezone=None,
|
|
455
|
+
max_trace_wait=DEFAULT_MAX_TRACE_WAIT,
|
|
456
|
+
ssl=False,
|
|
457
|
+
single_statement=None,
|
|
458
|
+
request_timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
|
459
|
+
protocol_version=None,
|
|
460
|
+
connect_timeout=DEFAULT_CONNECT_TIMEOUT_SECONDS,
|
|
461
|
+
is_subshell=False,
|
|
462
|
+
auth_provider=None,
|
|
463
|
+
no_compression=False,
|
|
464
|
+
):
|
|
465
|
+
cmd.Cmd.__init__(self, completekey=completekey)
|
|
466
|
+
self.hostname = hostname
|
|
467
|
+
self.port = port
|
|
468
|
+
self.auth_provider = auth_provider
|
|
469
|
+
self.username = username
|
|
470
|
+
self.no_compression = no_compression
|
|
471
|
+
|
|
472
|
+
if isinstance(auth_provider, PlainTextAuthProvider):
|
|
473
|
+
self.username = auth_provider.username
|
|
474
|
+
if not auth_provider.password:
|
|
475
|
+
# if no password is provided, we need to query the user to get one.
|
|
476
|
+
password = getpass.getpass()
|
|
477
|
+
self.auth_provider = PlainTextAuthProvider(username=auth_provider.username, password=password)
|
|
478
|
+
|
|
479
|
+
self.keyspace = keyspace
|
|
480
|
+
self.ssl = ssl
|
|
481
|
+
self.tracing_enabled = tracing_enabled
|
|
482
|
+
self.page_size = self.default_page_size
|
|
483
|
+
self.expand_enabled = expand_enabled
|
|
484
|
+
if use_conn:
|
|
485
|
+
self.conn = use_conn
|
|
486
|
+
else:
|
|
487
|
+
kwargs = {}
|
|
488
|
+
if protocol_version is not None:
|
|
489
|
+
kwargs['protocol_version'] = protocol_version
|
|
490
|
+
|
|
491
|
+
self.profiles = {
|
|
492
|
+
EXEC_PROFILE_DEFAULT: ExecutionProfile(consistency_level=cassandra.ConsistencyLevel.ONE,
|
|
493
|
+
request_timeout=request_timeout,
|
|
494
|
+
row_factory=ordered_dict_factory)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if os.path.exists(self.hostname) and stat.S_ISSOCK(os.stat(self.hostname).st_mode):
|
|
498
|
+
kwargs['contact_points'] = (UnixSocketEndPoint(self.hostname),)
|
|
499
|
+
self.profiles[EXEC_PROFILE_DEFAULT].load_balancing_policy = WhiteListRoundRobinPolicy([UnixSocketEndPoint(self.hostname)])
|
|
500
|
+
else:
|
|
501
|
+
kwargs['contact_points'] = (self.hostname,)
|
|
502
|
+
self.profiles[EXEC_PROFILE_DEFAULT].load_balancing_policy = WhiteListRoundRobinPolicy([self.hostname])
|
|
503
|
+
kwargs['port'] = self.port
|
|
504
|
+
kwargs['ssl_context'] = sslhandling.ssl_settings(hostname, CONFIG_FILE) if ssl else None
|
|
505
|
+
# workaround until driver would know not to lose the DNS names for `server_hostname`
|
|
506
|
+
kwargs['ssl_options'] = {'server_hostname': self.hostname} if ssl else None
|
|
507
|
+
|
|
508
|
+
# Disable compression if requested. When not set, the driver will use its default
|
|
509
|
+
# compression behavior (which is to enable compression if lz4 is available)
|
|
510
|
+
if no_compression:
|
|
511
|
+
kwargs['compression'] = False
|
|
512
|
+
|
|
513
|
+
self.conn = Cluster(cql_version=cqlver,
|
|
514
|
+
auth_provider=self.auth_provider,
|
|
515
|
+
control_connection_timeout=connect_timeout,
|
|
516
|
+
connect_timeout=connect_timeout,
|
|
517
|
+
execution_profiles=self.profiles,
|
|
518
|
+
**kwargs)
|
|
519
|
+
self.owns_connection = not use_conn
|
|
520
|
+
|
|
521
|
+
if keyspace:
|
|
522
|
+
self.session = self.conn.connect(keyspace)
|
|
523
|
+
else:
|
|
524
|
+
self.session = self.conn.connect()
|
|
525
|
+
|
|
526
|
+
if browser == "":
|
|
527
|
+
browser = None
|
|
528
|
+
self.browser = browser
|
|
529
|
+
self.color = color
|
|
530
|
+
|
|
531
|
+
self.display_nanotime_format = display_nanotime_format
|
|
532
|
+
self.display_timestamp_format = display_timestamp_format
|
|
533
|
+
self.display_date_format = display_date_format
|
|
534
|
+
|
|
535
|
+
self.display_float_precision = display_float_precision
|
|
536
|
+
self.display_double_precision = display_double_precision
|
|
537
|
+
|
|
538
|
+
self.display_timezone = display_timezone
|
|
539
|
+
|
|
540
|
+
self.scylla_version = None
|
|
541
|
+
|
|
542
|
+
self.get_connection_versions()
|
|
543
|
+
self.get_scylla_version()
|
|
544
|
+
self.set_expanded_cql_version(self.connection_versions['cql'])
|
|
545
|
+
|
|
546
|
+
self.current_keyspace = keyspace
|
|
547
|
+
|
|
548
|
+
self.max_trace_wait = max_trace_wait
|
|
549
|
+
self.session.max_trace_wait = max_trace_wait
|
|
550
|
+
|
|
551
|
+
self.tty = tty
|
|
552
|
+
self.encoding = encoding
|
|
553
|
+
|
|
554
|
+
self.output_codec = codecs.lookup(encoding)
|
|
555
|
+
|
|
556
|
+
self.statement = StringIO()
|
|
557
|
+
self.lineno = 1
|
|
558
|
+
self.in_comment = False
|
|
559
|
+
|
|
560
|
+
self.prompt = ''
|
|
561
|
+
if stdin is None:
|
|
562
|
+
stdin = sys.stdin
|
|
563
|
+
|
|
564
|
+
if tty:
|
|
565
|
+
self.reset_prompt()
|
|
566
|
+
self.report_connection()
|
|
567
|
+
print('Use HELP for help.')
|
|
568
|
+
else:
|
|
569
|
+
self.show_line_nums = True
|
|
570
|
+
self.stdin = stdin
|
|
571
|
+
self.query_out = sys.stdout
|
|
572
|
+
self.consistency_level = cassandra.ConsistencyLevel.ONE
|
|
573
|
+
self.serial_consistency_level = cassandra.ConsistencyLevel.SERIAL
|
|
574
|
+
|
|
575
|
+
self.empty_lines = 0
|
|
576
|
+
self.statement_error = False
|
|
577
|
+
self.single_statement = single_statement
|
|
578
|
+
self.is_subshell = is_subshell
|
|
579
|
+
|
|
580
|
+
@property
|
|
581
|
+
def batch_mode(self):
|
|
582
|
+
return not self.tty
|
|
583
|
+
|
|
584
|
+
def set_expanded_cql_version(self, ver):
|
|
585
|
+
ver, vertuple = full_cql_version(ver)
|
|
586
|
+
self.cql_version = ver
|
|
587
|
+
self.cql_ver_tuple = vertuple
|
|
588
|
+
|
|
589
|
+
def cqlver_atleast(self, major, minor=0, patch=0):
|
|
590
|
+
return self.cql_ver_tuple[:3] >= (major, minor, patch)
|
|
591
|
+
|
|
592
|
+
def myformat_value(self, val, cqltype=None, **kwargs):
|
|
593
|
+
if isinstance(val, DecodeError):
|
|
594
|
+
self.decoding_errors.append(val)
|
|
595
|
+
try:
|
|
596
|
+
dtformats = DateTimeFormat(timestamp_format=self.display_timestamp_format,
|
|
597
|
+
date_format=self.display_date_format, nanotime_format=self.display_nanotime_format,
|
|
598
|
+
timezone=self.display_timezone)
|
|
599
|
+
precision = self.display_double_precision if cqltype is not None and cqltype.type_name == 'double' \
|
|
600
|
+
else self.display_float_precision
|
|
601
|
+
return format_value(val, cqltype=cqltype, encoding=self.output_codec.name,
|
|
602
|
+
addcolor=self.color, date_time_format=dtformats,
|
|
603
|
+
float_precision=precision, **kwargs)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
err = FormatError(val, e)
|
|
606
|
+
self.decoding_errors.append(err)
|
|
607
|
+
return format_value(err, cqltype=cqltype, encoding=self.output_codec.name, addcolor=self.color)
|
|
608
|
+
|
|
609
|
+
def myformat_colname(self, name, table_meta=None):
|
|
610
|
+
column_colors = COLUMN_NAME_COLORS.copy()
|
|
611
|
+
# check column role and color appropriately
|
|
612
|
+
if table_meta:
|
|
613
|
+
if name in [col.name for col in table_meta.partition_key]:
|
|
614
|
+
column_colors.default_factory = lambda: RED
|
|
615
|
+
elif name in [col.name for col in table_meta.clustering_key]:
|
|
616
|
+
column_colors.default_factory = lambda: CYAN
|
|
617
|
+
elif name in table_meta.columns and table_meta.columns[name].is_static:
|
|
618
|
+
column_colors.default_factory = lambda: WHITE
|
|
619
|
+
return self.myformat_value(name, colormap=column_colors)
|
|
620
|
+
|
|
621
|
+
def report_connection(self):
|
|
622
|
+
self.show_host()
|
|
623
|
+
self.show_version()
|
|
624
|
+
|
|
625
|
+
def show_host(self):
|
|
626
|
+
print("Connected to {0} at {1}:{2}"
|
|
627
|
+
.format(self.applycolor(self.get_cluster_name(), BLUE),
|
|
628
|
+
self.hostname,
|
|
629
|
+
self.port))
|
|
630
|
+
|
|
631
|
+
def show_version(self):
|
|
632
|
+
vers = self.connection_versions.copy()
|
|
633
|
+
vers['shver'] = version
|
|
634
|
+
# system.Versions['cql'] apparently does not reflect changes with
|
|
635
|
+
# set_cql_version.
|
|
636
|
+
vers['cql'] = self.cql_version
|
|
637
|
+
if self.scylla_version:
|
|
638
|
+
vers['build'] = "Scylla {0}".format(self.scylla_version)
|
|
639
|
+
else:
|
|
640
|
+
vers['build'] = "Cassandra %(build)s" % vers
|
|
641
|
+
print("[cqlsh %(shver)s | %(build)s | CQL spec %(cql)s | Native protocol v%(protocol)s]" % vers)
|
|
642
|
+
|
|
643
|
+
def show_session(self, sessionid, partial_session=False):
|
|
644
|
+
print_trace_session(self, self.session, sessionid, partial_session)
|
|
645
|
+
|
|
646
|
+
def show_replicas(self, token_value, keyspace=None):
|
|
647
|
+
ks = self.current_keyspace if keyspace is None else keyspace
|
|
648
|
+
token_map = self.conn.metadata.token_map
|
|
649
|
+
nodes = token_map.get_replicas(ks, token_map.token_class(token_value))
|
|
650
|
+
addresses = [x.address for x in nodes]
|
|
651
|
+
print(f"{addresses}")
|
|
652
|
+
|
|
653
|
+
def get_connection_versions(self):
|
|
654
|
+
result, = self.session.execute("select * from system.local where key = 'local'")
|
|
655
|
+
vers = {
|
|
656
|
+
'build': result['release_version'],
|
|
657
|
+
'protocol': self.conn.protocol_version,
|
|
658
|
+
'cql': result['cql_version'],
|
|
659
|
+
}
|
|
660
|
+
self.connection_versions = vers
|
|
661
|
+
|
|
662
|
+
def get_scylla_version(self):
|
|
663
|
+
try:
|
|
664
|
+
result, = self.session.execute("SELECT * FROM system.versions WHERE key = 'local'")
|
|
665
|
+
self.scylla_version = result['version']
|
|
666
|
+
except CQL_ERRORS:
|
|
667
|
+
pass
|
|
668
|
+
|
|
669
|
+
def get_keyspace_names(self):
|
|
670
|
+
return list(self.conn.metadata.keyspaces)
|
|
671
|
+
|
|
672
|
+
def get_columnfamily_names(self, ksname=None):
|
|
673
|
+
if ksname is None:
|
|
674
|
+
ksname = self.current_keyspace
|
|
675
|
+
|
|
676
|
+
return list(self.get_keyspace_meta(ksname).tables)
|
|
677
|
+
|
|
678
|
+
def get_materialized_view_names(self, ksname=None):
|
|
679
|
+
if ksname is None:
|
|
680
|
+
ksname = self.current_keyspace
|
|
681
|
+
|
|
682
|
+
return list(self.get_keyspace_meta(ksname).views)
|
|
683
|
+
|
|
684
|
+
def get_index_names(self, ksname=None):
|
|
685
|
+
if ksname is None:
|
|
686
|
+
ksname = self.current_keyspace
|
|
687
|
+
|
|
688
|
+
return list(self.get_keyspace_meta(ksname).indexes)
|
|
689
|
+
|
|
690
|
+
def get_column_names(self, ksname, cfname):
|
|
691
|
+
if ksname is None:
|
|
692
|
+
ksname = self.current_keyspace
|
|
693
|
+
layout = self.get_table_meta(ksname, cfname)
|
|
694
|
+
return list(layout.columns)
|
|
695
|
+
|
|
696
|
+
def get_usertype_names(self, ksname=None):
|
|
697
|
+
if ksname is None:
|
|
698
|
+
ksname = self.current_keyspace
|
|
699
|
+
|
|
700
|
+
return list(self.get_keyspace_meta(ksname).user_types)
|
|
701
|
+
|
|
702
|
+
def get_usertype_layout(self, ksname, typename):
|
|
703
|
+
if ksname is None:
|
|
704
|
+
ksname = self.current_keyspace
|
|
705
|
+
|
|
706
|
+
ks_meta = self.get_keyspace_meta(ksname)
|
|
707
|
+
|
|
708
|
+
try:
|
|
709
|
+
user_type = ks_meta.user_types[typename]
|
|
710
|
+
except KeyError:
|
|
711
|
+
raise UserTypeNotFound("User type {!r} not found".format(typename))
|
|
712
|
+
|
|
713
|
+
return list(zip(user_type.field_names, user_type.field_types))
|
|
714
|
+
|
|
715
|
+
def get_userfunction_names(self, ksname=None):
|
|
716
|
+
if ksname is None:
|
|
717
|
+
ksname = self.current_keyspace
|
|
718
|
+
|
|
719
|
+
return [f.name for f in list(self.get_keyspace_meta(ksname).functions.values())]
|
|
720
|
+
|
|
721
|
+
def get_useraggregate_names(self, ksname=None):
|
|
722
|
+
if ksname is None:
|
|
723
|
+
ksname = self.current_keyspace
|
|
724
|
+
|
|
725
|
+
return [f.name for f in list(self.get_keyspace_meta(ksname).aggregates.values())]
|
|
726
|
+
|
|
727
|
+
def get_cluster_name(self):
|
|
728
|
+
return self.conn.metadata.cluster_name
|
|
729
|
+
|
|
730
|
+
def get_partitioner(self):
|
|
731
|
+
return self.conn.metadata.partitioner
|
|
732
|
+
|
|
733
|
+
def get_keyspace_meta(self, ksname):
|
|
734
|
+
if ksname in self.conn.metadata.keyspaces:
|
|
735
|
+
return self.conn.metadata.keyspaces[ksname]
|
|
736
|
+
|
|
737
|
+
raise KeyspaceNotFound('Keyspace %r not found.' % ksname)
|
|
738
|
+
|
|
739
|
+
def get_keyspaces(self):
|
|
740
|
+
return list(self.conn.metadata.keyspaces.values())
|
|
741
|
+
|
|
742
|
+
def get_ring(self, ks):
|
|
743
|
+
self.conn.metadata.token_map.rebuild_keyspace(ks, build_if_absent=True)
|
|
744
|
+
return self.conn.metadata.token_map.tokens_to_hosts_by_ks[ks]
|
|
745
|
+
|
|
746
|
+
def get_table_meta(self, ksname, tablename):
|
|
747
|
+
if ksname is None:
|
|
748
|
+
ksname = self.current_keyspace
|
|
749
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
750
|
+
if tablename not in ksmeta.tables:
|
|
751
|
+
if ksname == 'system_auth' and tablename in ['roles', 'role_permissions']:
|
|
752
|
+
self.get_fake_auth_table_meta(ksname, tablename)
|
|
753
|
+
else:
|
|
754
|
+
raise ColumnFamilyNotFound("Column family {} not found".format(tablename))
|
|
755
|
+
else:
|
|
756
|
+
return ksmeta.tables[tablename]
|
|
757
|
+
|
|
758
|
+
def get_fake_auth_table_meta(self, ksname, tablename):
|
|
759
|
+
# may be using external auth implementation so internal tables
|
|
760
|
+
# aren't actually defined in schema. In this case, we'll fake
|
|
761
|
+
# them up
|
|
762
|
+
if tablename == 'roles':
|
|
763
|
+
ks_meta = KeyspaceMetadata(ksname, True, None, None)
|
|
764
|
+
table_meta = TableMetadata(ks_meta, 'roles')
|
|
765
|
+
table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type)
|
|
766
|
+
table_meta.columns['is_superuser'] = ColumnMetadata(table_meta, 'is_superuser', cassandra.cqltypes.BooleanType)
|
|
767
|
+
table_meta.columns['can_login'] = ColumnMetadata(table_meta, 'can_login', cassandra.cqltypes.BooleanType)
|
|
768
|
+
elif tablename == 'role_permissions':
|
|
769
|
+
ks_meta = KeyspaceMetadata(ksname, True, None, None)
|
|
770
|
+
table_meta = TableMetadata(ks_meta, 'role_permissions')
|
|
771
|
+
table_meta.columns['role'] = ColumnMetadata(table_meta, 'role', cassandra.cqltypes.UTF8Type)
|
|
772
|
+
table_meta.columns['resource'] = ColumnMetadata(table_meta, 'resource', cassandra.cqltypes.UTF8Type)
|
|
773
|
+
table_meta.columns['permission'] = ColumnMetadata(table_meta, 'permission', cassandra.cqltypes.UTF8Type)
|
|
774
|
+
else:
|
|
775
|
+
raise ColumnFamilyNotFound("Column family {} not found".format(tablename))
|
|
776
|
+
|
|
777
|
+
def get_index_meta(self, ksname, idxname):
|
|
778
|
+
if ksname is None:
|
|
779
|
+
ksname = self.current_keyspace
|
|
780
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
781
|
+
|
|
782
|
+
if idxname not in ksmeta.indexes:
|
|
783
|
+
raise IndexNotFound("Index {} not found".format(idxname))
|
|
784
|
+
|
|
785
|
+
return ksmeta.indexes[idxname]
|
|
786
|
+
|
|
787
|
+
def get_view_meta(self, ksname, viewname):
|
|
788
|
+
if ksname is None:
|
|
789
|
+
ksname = self.current_keyspace
|
|
790
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
791
|
+
|
|
792
|
+
if viewname not in ksmeta.views:
|
|
793
|
+
raise MaterializedViewNotFound("Materialized view '{}' not found".format(viewname))
|
|
794
|
+
return ksmeta.views[viewname]
|
|
795
|
+
|
|
796
|
+
def get_object_meta(self, ks, name):
|
|
797
|
+
if name is None:
|
|
798
|
+
if ks and ks in self.conn.metadata.keyspaces:
|
|
799
|
+
return self.conn.metadata.keyspaces[ks]
|
|
800
|
+
elif self.current_keyspace is None:
|
|
801
|
+
raise ObjectNotFound("'{}' not found in keyspaces".format(ks))
|
|
802
|
+
else:
|
|
803
|
+
name = ks
|
|
804
|
+
ks = self.current_keyspace
|
|
805
|
+
|
|
806
|
+
if ks is None:
|
|
807
|
+
ks = self.current_keyspace
|
|
808
|
+
|
|
809
|
+
ksmeta = self.get_keyspace_meta(ks)
|
|
810
|
+
|
|
811
|
+
if name in ksmeta.tables:
|
|
812
|
+
return ksmeta.tables[name]
|
|
813
|
+
elif name in ksmeta.indexes:
|
|
814
|
+
return ksmeta.indexes[name]
|
|
815
|
+
elif name in ksmeta.views:
|
|
816
|
+
return ksmeta.views[name]
|
|
817
|
+
|
|
818
|
+
raise ObjectNotFound("'{}' not found in keyspace '{}'".format(name, ks))
|
|
819
|
+
|
|
820
|
+
def get_trigger_names(self, ksname=None):
|
|
821
|
+
if ksname is None:
|
|
822
|
+
ksname = self.current_keyspace
|
|
823
|
+
|
|
824
|
+
return [trigger.name
|
|
825
|
+
for table in list(self.get_keyspace_meta(ksname).tables.values())
|
|
826
|
+
for trigger in list(table.triggers.values())]
|
|
827
|
+
|
|
828
|
+
def reset_statement(self):
|
|
829
|
+
self.reset_prompt()
|
|
830
|
+
self.statement.truncate(0)
|
|
831
|
+
self.statement.seek(0)
|
|
832
|
+
self.empty_lines = 0
|
|
833
|
+
|
|
834
|
+
def reset_prompt(self):
|
|
835
|
+
if self.current_keyspace is None:
|
|
836
|
+
self.set_prompt(self.default_prompt, True)
|
|
837
|
+
else:
|
|
838
|
+
self.set_prompt(self.keyspace_prompt.format(self.current_keyspace), True)
|
|
839
|
+
|
|
840
|
+
def set_continue_prompt(self):
|
|
841
|
+
if self.empty_lines >= 3:
|
|
842
|
+
self.set_prompt("Statements are terminated with a ';'. You can press CTRL-C to cancel an incomplete statement.")
|
|
843
|
+
self.empty_lines = 0
|
|
844
|
+
return
|
|
845
|
+
if self.current_keyspace is None:
|
|
846
|
+
self.set_prompt(self.continue_prompt)
|
|
847
|
+
else:
|
|
848
|
+
spaces = ' ' * len(str(self.current_keyspace))
|
|
849
|
+
self.set_prompt(self.keyspace_continue_prompt.format(spaces))
|
|
850
|
+
self.empty_lines = self.empty_lines + 1 if not self.lastcmd else 0
|
|
851
|
+
|
|
852
|
+
@contextmanager
|
|
853
|
+
def prepare_loop(self):
|
|
854
|
+
readline = None
|
|
855
|
+
if self.tty and self.completekey:
|
|
856
|
+
try:
|
|
857
|
+
import readline
|
|
858
|
+
except ImportError:
|
|
859
|
+
pass
|
|
860
|
+
else:
|
|
861
|
+
old_completer = readline.get_completer()
|
|
862
|
+
readline.set_completer(self.complete)
|
|
863
|
+
if readline.__doc__ is not None and 'libedit' in readline.__doc__:
|
|
864
|
+
readline.parse_and_bind("bind -e")
|
|
865
|
+
readline.parse_and_bind("bind '" + self.completekey + "' rl_complete")
|
|
866
|
+
readline.parse_and_bind("bind ^R em-inc-search-prev")
|
|
867
|
+
else:
|
|
868
|
+
readline.parse_and_bind(self.completekey + ": complete")
|
|
869
|
+
# start coverage collection if requested, unless in subshell
|
|
870
|
+
if self.coverage and not self.is_subshell:
|
|
871
|
+
# check for coveragerc file, write it if missing
|
|
872
|
+
if os.path.exists(CQL_DIR):
|
|
873
|
+
self.coveragerc_path = os.path.join(CQL_DIR, '.coveragerc')
|
|
874
|
+
covdata_path = os.path.join(CQL_DIR, '.coverage')
|
|
875
|
+
if not os.path.isfile(self.coveragerc_path):
|
|
876
|
+
with open(self.coveragerc_path, 'w') as f:
|
|
877
|
+
f.writelines(["[run]\n",
|
|
878
|
+
"concurrency = multiprocessing\n",
|
|
879
|
+
"data_file = {}\n".format(covdata_path),
|
|
880
|
+
"parallel = true\n"]
|
|
881
|
+
)
|
|
882
|
+
# start coverage
|
|
883
|
+
import coverage
|
|
884
|
+
self.cov = coverage.Coverage(config_file=self.coveragerc_path)
|
|
885
|
+
self.cov.start()
|
|
886
|
+
try:
|
|
887
|
+
yield
|
|
888
|
+
finally:
|
|
889
|
+
if readline is not None:
|
|
890
|
+
readline.set_completer(old_completer)
|
|
891
|
+
if self.coverage and not self.is_subshell:
|
|
892
|
+
self.stop_coverage()
|
|
893
|
+
|
|
894
|
+
def get_input_line(self, prompt=''):
|
|
895
|
+
if self.tty:
|
|
896
|
+
self.lastcmd = input(str(prompt))
|
|
897
|
+
line = self.lastcmd + '\n'
|
|
898
|
+
else:
|
|
899
|
+
self.lastcmd = self.stdin.readline()
|
|
900
|
+
line = self.lastcmd
|
|
901
|
+
if not len(line):
|
|
902
|
+
raise EOFError
|
|
903
|
+
self.lineno += 1
|
|
904
|
+
return line
|
|
905
|
+
|
|
906
|
+
def use_stdin_reader(self, until='', prompt=''):
|
|
907
|
+
until += '\n'
|
|
908
|
+
while True:
|
|
909
|
+
try:
|
|
910
|
+
newline = self.get_input_line(prompt=prompt)
|
|
911
|
+
except EOFError:
|
|
912
|
+
return
|
|
913
|
+
if newline == until:
|
|
914
|
+
return
|
|
915
|
+
yield newline
|
|
916
|
+
|
|
917
|
+
def cmdloop(self, intro=None):
|
|
918
|
+
"""
|
|
919
|
+
Adapted from cmd.Cmd's version, because there is literally no way with
|
|
920
|
+
cmd.Cmd.cmdloop() to tell the difference between "EOF" showing up in
|
|
921
|
+
input and an actual EOF.
|
|
922
|
+
"""
|
|
923
|
+
with self.prepare_loop():
|
|
924
|
+
while not self.stop:
|
|
925
|
+
try:
|
|
926
|
+
if self.single_statement:
|
|
927
|
+
line = self.single_statement
|
|
928
|
+
self.stop = True
|
|
929
|
+
else:
|
|
930
|
+
line = self.get_input_line(self.prompt)
|
|
931
|
+
self.statement.write(line)
|
|
932
|
+
if self.onecmd(self.statement.getvalue()):
|
|
933
|
+
self.reset_statement()
|
|
934
|
+
except EOFError:
|
|
935
|
+
self.handle_eof()
|
|
936
|
+
except CQL_ERRORS as cqlerr:
|
|
937
|
+
self.printerr(cqlerr.message)
|
|
938
|
+
except KeyboardInterrupt:
|
|
939
|
+
self.reset_statement()
|
|
940
|
+
print('')
|
|
941
|
+
|
|
942
|
+
def strip_comment_blocks(self, statementtext):
|
|
943
|
+
comment_block_in_literal_string = re.search('["].*[/][*].*[*][/].*["]', statementtext)
|
|
944
|
+
if not comment_block_in_literal_string:
|
|
945
|
+
result = re.sub('[/][*].*[*][/]', "", statementtext)
|
|
946
|
+
if '*/' in result and '/*' not in result and not self.in_comment:
|
|
947
|
+
raise SyntaxError("Encountered comment block terminator without being in comment block")
|
|
948
|
+
if '/*' in result:
|
|
949
|
+
result = re.sub('[/][*].*', "", result)
|
|
950
|
+
self.in_comment = True
|
|
951
|
+
if '*/' in result:
|
|
952
|
+
result = re.sub('.*[*][/]', "", result)
|
|
953
|
+
self.in_comment = False
|
|
954
|
+
if self.in_comment and not re.findall('[/][*]|[*][/]', statementtext):
|
|
955
|
+
result = ''
|
|
956
|
+
return result
|
|
957
|
+
return statementtext
|
|
958
|
+
|
|
959
|
+
def onecmd(self, statementtext):
|
|
960
|
+
"""
|
|
961
|
+
Returns true if the statement is complete and was handled (meaning it
|
|
962
|
+
can be reset).
|
|
963
|
+
"""
|
|
964
|
+
statementtext = self.strip_comment_blocks(statementtext)
|
|
965
|
+
try:
|
|
966
|
+
statements, endtoken_escaped = cqlruleset.cql_split_statements(statementtext)
|
|
967
|
+
except pylexotron.LexingError as e:
|
|
968
|
+
if self.show_line_nums:
|
|
969
|
+
self.printerr('Invalid syntax at line {0}, char {1}'
|
|
970
|
+
.format(e.linenum, e.charnum))
|
|
971
|
+
else:
|
|
972
|
+
self.printerr('Invalid syntax at char {0}'.format(e.charnum))
|
|
973
|
+
statementline = statementtext.split('\n')[e.linenum - 1]
|
|
974
|
+
self.printerr(' {0}'.format(statementline))
|
|
975
|
+
self.printerr(' {0}^'.format(' ' * e.charnum))
|
|
976
|
+
return True
|
|
977
|
+
|
|
978
|
+
while statements and not statements[-1]:
|
|
979
|
+
statements = statements[:-1]
|
|
980
|
+
if not statements:
|
|
981
|
+
return True
|
|
982
|
+
if endtoken_escaped or statements[-1][-1][0] != 'endtoken':
|
|
983
|
+
self.set_continue_prompt()
|
|
984
|
+
return
|
|
985
|
+
for st in statements:
|
|
986
|
+
try:
|
|
987
|
+
self.handle_statement(st, statementtext)
|
|
988
|
+
except Exception as e:
|
|
989
|
+
if self.debug:
|
|
990
|
+
traceback.print_exc()
|
|
991
|
+
else:
|
|
992
|
+
self.printerr(e)
|
|
993
|
+
return True
|
|
994
|
+
|
|
995
|
+
def handle_eof(self):
|
|
996
|
+
if self.tty:
|
|
997
|
+
print('')
|
|
998
|
+
statement = self.statement.getvalue()
|
|
999
|
+
if statement.strip():
|
|
1000
|
+
if not self.onecmd(statement):
|
|
1001
|
+
self.printerr('Incomplete statement at end of file')
|
|
1002
|
+
self.do_exit()
|
|
1003
|
+
|
|
1004
|
+
def handle_statement(self, tokens, srcstr):
|
|
1005
|
+
# Concat multi-line statements and insert into history
|
|
1006
|
+
if readline is not None:
|
|
1007
|
+
nl_count = srcstr.count("\n")
|
|
1008
|
+
|
|
1009
|
+
new_hist = srcstr.replace("\n", " ").rstrip()
|
|
1010
|
+
|
|
1011
|
+
if nl_count > 1 and self.last_hist != new_hist:
|
|
1012
|
+
readline.add_history(new_hist)
|
|
1013
|
+
|
|
1014
|
+
self.last_hist = new_hist
|
|
1015
|
+
cmdword = tokens[0][1]
|
|
1016
|
+
if cmdword == '?':
|
|
1017
|
+
cmdword = 'help'
|
|
1018
|
+
|
|
1019
|
+
cmdword_lower = cmdword.lower()
|
|
1020
|
+
|
|
1021
|
+
# Describe statements get special treatment: we first want to
|
|
1022
|
+
# send the request to the server and only when it fails will
|
|
1023
|
+
# we attempt to perform it locally. That's why we don't want
|
|
1024
|
+
# to follow the logic below that starts with parsing.
|
|
1025
|
+
#
|
|
1026
|
+
# The reason for that is changes in Scylla may need to be reflected
|
|
1027
|
+
# in the grammar used in cqlsh. We want Scylla to be "independent"
|
|
1028
|
+
# in that regard, so unless necessary, we don't want to do the parsing
|
|
1029
|
+
# here.
|
|
1030
|
+
if cmdword_lower == 'describe' or cmdword_lower == 'desc':
|
|
1031
|
+
return self.perform_describe(cmdword, tokens, srcstr)
|
|
1032
|
+
|
|
1033
|
+
custom_handler = getattr(self, 'do_' + cmdword.lower(), None)
|
|
1034
|
+
if custom_handler:
|
|
1035
|
+
parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr,
|
|
1036
|
+
startsymbol='cqlshCommand')
|
|
1037
|
+
if parsed and not parsed.remainder:
|
|
1038
|
+
# successful complete parse
|
|
1039
|
+
return custom_handler(parsed)
|
|
1040
|
+
else:
|
|
1041
|
+
return self.handle_parse_error(cmdword, tokens, parsed, srcstr)
|
|
1042
|
+
return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr))
|
|
1043
|
+
|
|
1044
|
+
def handle_parse_error(self, cmdword, tokens, parsed, srcstr):
|
|
1045
|
+
if cmdword.lower() in ('select', 'insert', 'update', 'delete', 'truncate',
|
|
1046
|
+
'create', 'drop', 'alter', 'grant', 'revoke',
|
|
1047
|
+
'batch', 'list'):
|
|
1048
|
+
# hey, maybe they know about some new syntax we don't. type
|
|
1049
|
+
# assumptions won't work, but maybe the query will.
|
|
1050
|
+
return self.perform_statement(cqlruleset.cql_extract_orig(tokens, srcstr))
|
|
1051
|
+
if parsed:
|
|
1052
|
+
self.printerr('Improper %s command (problem at %r).' % (cmdword, parsed.remainder[0]))
|
|
1053
|
+
else:
|
|
1054
|
+
self.printerr(f'Improper {cmdword} command.')
|
|
1055
|
+
|
|
1056
|
+
def do_use(self, parsed):
|
|
1057
|
+
ksname = parsed.get_binding('ksname')
|
|
1058
|
+
success, _ = self.perform_simple_statement(SimpleStatement(parsed.extract_orig()))
|
|
1059
|
+
if success:
|
|
1060
|
+
if ksname[0] == '"' and ksname[-1] == '"':
|
|
1061
|
+
self.current_keyspace = self.cql_unprotect_name(ksname)
|
|
1062
|
+
else:
|
|
1063
|
+
self.current_keyspace = ksname.lower()
|
|
1064
|
+
|
|
1065
|
+
def do_select(self, parsed):
|
|
1066
|
+
tracing_was_enabled = self.tracing_enabled
|
|
1067
|
+
ksname = parsed.get_binding('ksname')
|
|
1068
|
+
stop_tracing = ksname == 'system_traces' or (ksname is None and self.current_keyspace == 'system_traces')
|
|
1069
|
+
self.tracing_enabled = self.tracing_enabled and not stop_tracing
|
|
1070
|
+
statement = parsed.extract_orig()
|
|
1071
|
+
self.perform_statement(statement)
|
|
1072
|
+
self.tracing_enabled = tracing_was_enabled
|
|
1073
|
+
|
|
1074
|
+
def perform_statement(self, statement):
|
|
1075
|
+
|
|
1076
|
+
stmt = SimpleStatement(statement, consistency_level=self.consistency_level, serial_consistency_level=self.serial_consistency_level, fetch_size=self.page_size if self.use_paging else None)
|
|
1077
|
+
success, future = self.perform_simple_statement(stmt)
|
|
1078
|
+
|
|
1079
|
+
if future:
|
|
1080
|
+
if future.warnings:
|
|
1081
|
+
self.print_warnings(future.warnings)
|
|
1082
|
+
|
|
1083
|
+
if self.tracing_enabled:
|
|
1084
|
+
try:
|
|
1085
|
+
for trace in future.get_all_query_traces(max_wait_per=self.max_trace_wait, query_cl=self.consistency_level):
|
|
1086
|
+
print_trace(self, trace)
|
|
1087
|
+
except TraceUnavailable:
|
|
1088
|
+
msg = "Statement trace did not complete within %d seconds; trace data may be incomplete." % (self.session.max_trace_wait,)
|
|
1089
|
+
self.writeresult(msg, color=RED)
|
|
1090
|
+
for trace_id in future.get_query_trace_ids():
|
|
1091
|
+
self.show_session(trace_id, partial_session=True)
|
|
1092
|
+
except Exception as err:
|
|
1093
|
+
self.printerr("Unable to fetch query trace: %s" % (str(err),))
|
|
1094
|
+
|
|
1095
|
+
return success
|
|
1096
|
+
|
|
1097
|
+
def parse_for_select_meta(self, query_string):
|
|
1098
|
+
try:
|
|
1099
|
+
parsed = cqlruleset.cql_parse(query_string)[1]
|
|
1100
|
+
except IndexError:
|
|
1101
|
+
return None
|
|
1102
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1103
|
+
name = self.cql_unprotect_name(parsed.get_binding('cfname', None))
|
|
1104
|
+
try:
|
|
1105
|
+
return self.get_table_meta(ks, name)
|
|
1106
|
+
except ColumnFamilyNotFound:
|
|
1107
|
+
try:
|
|
1108
|
+
return self.get_view_meta(ks, name)
|
|
1109
|
+
except MaterializedViewNotFound:
|
|
1110
|
+
raise ObjectNotFound("'{}' not found in keyspace '{}'".format(name, ks))
|
|
1111
|
+
|
|
1112
|
+
def parse_for_update_meta(self, query_string):
|
|
1113
|
+
try:
|
|
1114
|
+
parsed = cqlruleset.cql_parse(query_string)[1]
|
|
1115
|
+
except IndexError:
|
|
1116
|
+
return None
|
|
1117
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1118
|
+
cf = self.cql_unprotect_name(parsed.get_binding('cfname'))
|
|
1119
|
+
return self.get_table_meta(ks, cf)
|
|
1120
|
+
|
|
1121
|
+
def perform_simple_statement(self, statement):
|
|
1122
|
+
if not statement:
|
|
1123
|
+
return False, None
|
|
1124
|
+
|
|
1125
|
+
future = self.session.execute_async(statement, trace=self.tracing_enabled)
|
|
1126
|
+
result = None
|
|
1127
|
+
try:
|
|
1128
|
+
result = future.result()
|
|
1129
|
+
except CQL_ERRORS as err:
|
|
1130
|
+
err_msg = err.message if hasattr(err, 'message') else str(err)
|
|
1131
|
+
self.printerr(str(err.__class__.__name__) + ": " + err_msg)
|
|
1132
|
+
except Exception:
|
|
1133
|
+
import traceback
|
|
1134
|
+
self.printerr(traceback.format_exc())
|
|
1135
|
+
|
|
1136
|
+
# Even if statement failed we try to refresh schema if not agreed (see CASSANDRA-9689)
|
|
1137
|
+
if not future.is_schema_agreed:
|
|
1138
|
+
try:
|
|
1139
|
+
self.conn.refresh_schema_metadata(5) # will throw exception if there is a schema mismatch
|
|
1140
|
+
except Exception:
|
|
1141
|
+
self.printwarn("Warning: schema version mismatch detected; check the schema versions of your "
|
|
1142
|
+
"nodes in system.local and system.peers.")
|
|
1143
|
+
self.conn.refresh_schema_metadata(-1)
|
|
1144
|
+
|
|
1145
|
+
if result is None:
|
|
1146
|
+
return False, None
|
|
1147
|
+
|
|
1148
|
+
if statement.query_string[:6].lower() == 'select':
|
|
1149
|
+
self.print_result(result, self.parse_for_select_meta(statement.query_string))
|
|
1150
|
+
elif statement.query_string.lower().startswith("list users") or statement.query_string.lower().startswith("list roles"):
|
|
1151
|
+
self.print_result(result, self.get_table_meta('system_auth', 'roles'))
|
|
1152
|
+
elif statement.query_string.lower().startswith("list"):
|
|
1153
|
+
self.print_result(result, self.get_table_meta('system_auth', 'role_permissions'))
|
|
1154
|
+
elif result:
|
|
1155
|
+
# CAS INSERT/UPDATE
|
|
1156
|
+
self.writeresult("")
|
|
1157
|
+
self.print_static_result(result, self.parse_for_update_meta(statement.query_string), with_header=True, tty=self.tty)
|
|
1158
|
+
self.flush_output()
|
|
1159
|
+
return True, future
|
|
1160
|
+
|
|
1161
|
+
def print_result(self, result, table_meta):
|
|
1162
|
+
self.decoding_errors = []
|
|
1163
|
+
|
|
1164
|
+
self.writeresult("")
|
|
1165
|
+
|
|
1166
|
+
def print_all(result, table_meta, tty):
|
|
1167
|
+
# Return the number of rows in total
|
|
1168
|
+
num_rows = 0
|
|
1169
|
+
is_first = True
|
|
1170
|
+
while True:
|
|
1171
|
+
# Always print for the first page even it is empty
|
|
1172
|
+
if result.current_rows or is_first:
|
|
1173
|
+
with_header = is_first or tty
|
|
1174
|
+
self.print_static_result(result, table_meta, with_header, tty, num_rows)
|
|
1175
|
+
num_rows += len(result.current_rows)
|
|
1176
|
+
if result.has_more_pages:
|
|
1177
|
+
if self.shunted_query_out is None and tty:
|
|
1178
|
+
# Only pause when not capturing.
|
|
1179
|
+
input("---MORE---")
|
|
1180
|
+
result.fetch_next_page()
|
|
1181
|
+
else:
|
|
1182
|
+
if not tty:
|
|
1183
|
+
self.writeresult("")
|
|
1184
|
+
break
|
|
1185
|
+
is_first = False
|
|
1186
|
+
return num_rows
|
|
1187
|
+
|
|
1188
|
+
num_rows = print_all(result, table_meta, self.tty)
|
|
1189
|
+
self.writeresult("(%d rows)" % num_rows)
|
|
1190
|
+
|
|
1191
|
+
if self.decoding_errors:
|
|
1192
|
+
for err in self.decoding_errors[:2]:
|
|
1193
|
+
self.writeresult(err.message(), color=RED)
|
|
1194
|
+
if len(self.decoding_errors) > 2:
|
|
1195
|
+
self.writeresult('%d more decoding errors suppressed.'
|
|
1196
|
+
% (len(self.decoding_errors) - 2), color=RED)
|
|
1197
|
+
|
|
1198
|
+
def print_static_result(self, result, table_meta, with_header, tty, row_count_offset=0):
|
|
1199
|
+
if not result.column_names and not table_meta:
|
|
1200
|
+
return
|
|
1201
|
+
|
|
1202
|
+
column_names = result.column_names or list(table_meta.columns.keys())
|
|
1203
|
+
formatted_names = [self.myformat_colname(name, table_meta) for name in column_names]
|
|
1204
|
+
if not result.current_rows:
|
|
1205
|
+
# print header only
|
|
1206
|
+
self.print_formatted_result(formatted_names, None, with_header=True, tty=tty)
|
|
1207
|
+
return
|
|
1208
|
+
|
|
1209
|
+
cql_types = []
|
|
1210
|
+
if result.column_types:
|
|
1211
|
+
ks_name = table_meta.keyspace_name if table_meta else self.current_keyspace
|
|
1212
|
+
ks_meta = self.conn.metadata.keyspaces.get(ks_name, None)
|
|
1213
|
+
cql_types = [CqlType(cql_typename(t), ks_meta) for t in result.column_types]
|
|
1214
|
+
|
|
1215
|
+
formatted_values = [list(map(self.myformat_value, [row[c] for c in column_names], cql_types)) for row in result.current_rows]
|
|
1216
|
+
|
|
1217
|
+
if self.expand_enabled:
|
|
1218
|
+
self.print_formatted_result_vertically(formatted_names, formatted_values, row_count_offset)
|
|
1219
|
+
else:
|
|
1220
|
+
self.print_formatted_result(formatted_names, formatted_values, with_header, tty)
|
|
1221
|
+
|
|
1222
|
+
def print_formatted_result(self, formatted_names, formatted_values, with_header, tty):
|
|
1223
|
+
# determine column widths
|
|
1224
|
+
widths = [n.displaywidth for n in formatted_names]
|
|
1225
|
+
if formatted_values is not None:
|
|
1226
|
+
for fmtrow in formatted_values:
|
|
1227
|
+
for num, col in enumerate(fmtrow):
|
|
1228
|
+
widths[num] = max(widths[num], col.displaywidth)
|
|
1229
|
+
|
|
1230
|
+
# print header
|
|
1231
|
+
if with_header:
|
|
1232
|
+
header = ' | '.join(hdr.ljust(w, color=self.color) for (hdr, w) in zip(formatted_names, widths))
|
|
1233
|
+
self.writeresult(' ' + header.rstrip())
|
|
1234
|
+
self.writeresult('-%s-' % '-+-'.join('-' * w for w in widths))
|
|
1235
|
+
|
|
1236
|
+
# stop if there are no rows
|
|
1237
|
+
if formatted_values is None:
|
|
1238
|
+
self.writeresult("")
|
|
1239
|
+
return
|
|
1240
|
+
|
|
1241
|
+
# print row data
|
|
1242
|
+
for row in formatted_values:
|
|
1243
|
+
line = ' | '.join(col.rjust(w, color=self.color) for (col, w) in zip(row, widths))
|
|
1244
|
+
self.writeresult(' ' + line)
|
|
1245
|
+
|
|
1246
|
+
if tty:
|
|
1247
|
+
self.writeresult("")
|
|
1248
|
+
|
|
1249
|
+
def print_formatted_result_vertically(self, formatted_names, formatted_values, row_count_offset):
|
|
1250
|
+
max_col_width = max([n.displaywidth for n in formatted_names])
|
|
1251
|
+
max_val_width = max([n.displaywidth for row in formatted_values for n in row])
|
|
1252
|
+
|
|
1253
|
+
# for each row returned, list all the column-value pairs
|
|
1254
|
+
for i, row in enumerate(formatted_values):
|
|
1255
|
+
self.writeresult("@ Row %d" % (row_count_offset + i + 1))
|
|
1256
|
+
self.writeresult('-%s-' % '-+-'.join(['-' * max_col_width, '-' * max_val_width]))
|
|
1257
|
+
for field_id, field in enumerate(row):
|
|
1258
|
+
column = formatted_names[field_id].ljust(max_col_width, color=self.color)
|
|
1259
|
+
value = field.ljust(field.displaywidth, color=self.color)
|
|
1260
|
+
self.writeresult(' ' + " | ".join([column, value]))
|
|
1261
|
+
self.writeresult('')
|
|
1262
|
+
|
|
1263
|
+
def print_warnings(self, warnings):
|
|
1264
|
+
if warnings is None or len(warnings) == 0:
|
|
1265
|
+
return
|
|
1266
|
+
|
|
1267
|
+
self.writeresult('')
|
|
1268
|
+
self.writeresult('Warnings :')
|
|
1269
|
+
for warning in warnings:
|
|
1270
|
+
self.writeresult(warning)
|
|
1271
|
+
self.writeresult('')
|
|
1272
|
+
|
|
1273
|
+
def emptyline(self):
|
|
1274
|
+
pass
|
|
1275
|
+
|
|
1276
|
+
def parseline(self, line):
|
|
1277
|
+
# this shouldn't be needed
|
|
1278
|
+
raise NotImplementedError
|
|
1279
|
+
|
|
1280
|
+
def complete(self, text, state):
|
|
1281
|
+
if readline is None:
|
|
1282
|
+
return
|
|
1283
|
+
if state == 0:
|
|
1284
|
+
try:
|
|
1285
|
+
self.completion_matches = self.find_completions(text)
|
|
1286
|
+
except Exception:
|
|
1287
|
+
if debug_completion:
|
|
1288
|
+
import traceback
|
|
1289
|
+
traceback.print_exc()
|
|
1290
|
+
else:
|
|
1291
|
+
raise
|
|
1292
|
+
try:
|
|
1293
|
+
return self.completion_matches[state]
|
|
1294
|
+
except IndexError:
|
|
1295
|
+
return None
|
|
1296
|
+
|
|
1297
|
+
def find_completions(self, text):
|
|
1298
|
+
curline = readline.get_line_buffer()
|
|
1299
|
+
prevlines = self.statement.getvalue()
|
|
1300
|
+
wholestmt = prevlines + curline
|
|
1301
|
+
begidx = readline.get_begidx() + len(prevlines)
|
|
1302
|
+
stuff_to_complete = wholestmt[:begidx]
|
|
1303
|
+
return cqlruleset.cql_complete(stuff_to_complete, text, cassandra_conn=self,
|
|
1304
|
+
debug=debug_completion, startsymbol='cqlshCommand')
|
|
1305
|
+
|
|
1306
|
+
def set_prompt(self, prompt, prepend_user=False):
|
|
1307
|
+
if prepend_user and self.username:
|
|
1308
|
+
self.prompt = "{0}@{1}".format(self.username, prompt)
|
|
1309
|
+
return
|
|
1310
|
+
self.prompt = prompt
|
|
1311
|
+
|
|
1312
|
+
def cql_unprotect_name(self, namestr):
|
|
1313
|
+
if namestr is None:
|
|
1314
|
+
return
|
|
1315
|
+
return cqlruleset.dequote_name(namestr)
|
|
1316
|
+
|
|
1317
|
+
def cql_unprotect_value(self, valstr):
|
|
1318
|
+
if valstr is not None:
|
|
1319
|
+
return cqlruleset.dequote_value(valstr)
|
|
1320
|
+
|
|
1321
|
+
def print_recreate_keyspace(self, ksdef, out):
|
|
1322
|
+
out.write(ksdef.export_as_string())
|
|
1323
|
+
out.write("\n")
|
|
1324
|
+
|
|
1325
|
+
def print_recreate_columnfamily(self, ksname, cfname, out):
|
|
1326
|
+
"""
|
|
1327
|
+
Output CQL commands which should be pasteable back into a CQL session
|
|
1328
|
+
to recreate the given table.
|
|
1329
|
+
|
|
1330
|
+
Writes output to the given out stream.
|
|
1331
|
+
"""
|
|
1332
|
+
out.write(self.get_table_meta(ksname, cfname).export_as_string())
|
|
1333
|
+
out.write("\n")
|
|
1334
|
+
|
|
1335
|
+
def print_recreate_index(self, ksname, idxname, out):
|
|
1336
|
+
"""
|
|
1337
|
+
Output CQL commands which should be pasteable back into a CQL session
|
|
1338
|
+
to recreate the given index.
|
|
1339
|
+
|
|
1340
|
+
Writes output to the given out stream.
|
|
1341
|
+
"""
|
|
1342
|
+
out.write(self.get_index_meta(ksname, idxname).export_as_string())
|
|
1343
|
+
out.write("\n")
|
|
1344
|
+
|
|
1345
|
+
def print_recreate_materialized_view(self, ksname, viewname, out):
|
|
1346
|
+
"""
|
|
1347
|
+
Output CQL commands which should be pasteable back into a CQL session
|
|
1348
|
+
to recreate the given materialized view.
|
|
1349
|
+
|
|
1350
|
+
Writes output to the given out stream.
|
|
1351
|
+
"""
|
|
1352
|
+
out.write(self.get_view_meta(ksname, viewname).export_as_string())
|
|
1353
|
+
out.write("\n")
|
|
1354
|
+
|
|
1355
|
+
def print_recreate_object(self, ks, name, out):
|
|
1356
|
+
"""
|
|
1357
|
+
Output CQL commands which should be pasteable back into a CQL session
|
|
1358
|
+
to recreate the given object (ks, table or index).
|
|
1359
|
+
|
|
1360
|
+
Writes output to the given out stream.
|
|
1361
|
+
"""
|
|
1362
|
+
out.write(self.get_object_meta(ks, name).export_as_string())
|
|
1363
|
+
out.write("\n")
|
|
1364
|
+
|
|
1365
|
+
def describe_keyspaces_client(self):
|
|
1366
|
+
print('')
|
|
1367
|
+
cmd.Cmd.columnize(self, protect_names(self.get_keyspace_names()))
|
|
1368
|
+
print('')
|
|
1369
|
+
|
|
1370
|
+
def describe_keyspace_client(self, ksname):
|
|
1371
|
+
print('')
|
|
1372
|
+
self.print_recreate_keyspace(self.get_keyspace_meta(ksname), sys.stdout)
|
|
1373
|
+
print('')
|
|
1374
|
+
|
|
1375
|
+
def describe_columnfamily_client(self, ksname, cfname):
|
|
1376
|
+
if ksname is None:
|
|
1377
|
+
ksname = self.current_keyspace
|
|
1378
|
+
if ksname is None:
|
|
1379
|
+
raise NoKeyspaceError("No keyspace specified and no current keyspace")
|
|
1380
|
+
print('')
|
|
1381
|
+
self.print_recreate_columnfamily(ksname, cfname, sys.stdout)
|
|
1382
|
+
print('')
|
|
1383
|
+
|
|
1384
|
+
def describe_index_client(self, ksname, idxname):
|
|
1385
|
+
print('')
|
|
1386
|
+
self.print_recreate_index(ksname, idxname, sys.stdout)
|
|
1387
|
+
print('')
|
|
1388
|
+
|
|
1389
|
+
def describe_materialized_view_client(self, ksname, viewname):
|
|
1390
|
+
if ksname is None:
|
|
1391
|
+
ksname = self.current_keyspace
|
|
1392
|
+
if ksname is None:
|
|
1393
|
+
raise NoKeyspaceError("No keyspace specified and no current keyspace")
|
|
1394
|
+
print('')
|
|
1395
|
+
self.print_recreate_materialized_view(ksname, viewname, sys.stdout)
|
|
1396
|
+
print('')
|
|
1397
|
+
|
|
1398
|
+
def describe_object_client(self, ks, name):
|
|
1399
|
+
print('')
|
|
1400
|
+
self.print_recreate_object(ks, name, sys.stdout)
|
|
1401
|
+
print('')
|
|
1402
|
+
|
|
1403
|
+
def describe_columnfamilies_client(self, ksname):
|
|
1404
|
+
print('')
|
|
1405
|
+
if ksname is None:
|
|
1406
|
+
for k in self.get_keyspaces():
|
|
1407
|
+
name = protect_name(k.name)
|
|
1408
|
+
print('Keyspace %s' % (name,))
|
|
1409
|
+
print('---------%s' % ('-' * len(name)))
|
|
1410
|
+
cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(k.name)))
|
|
1411
|
+
print('')
|
|
1412
|
+
else:
|
|
1413
|
+
cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(ksname)))
|
|
1414
|
+
print('')
|
|
1415
|
+
|
|
1416
|
+
def describe_functions_client(self, ksname):
|
|
1417
|
+
print('')
|
|
1418
|
+
if ksname is None:
|
|
1419
|
+
for ksmeta in self.get_keyspaces():
|
|
1420
|
+
name = protect_name(ksmeta.name)
|
|
1421
|
+
print('Keyspace %s' % (name,))
|
|
1422
|
+
print('---------%s' % ('-' * len(name)))
|
|
1423
|
+
self._columnize_unicode(list(ksmeta.functions.keys()))
|
|
1424
|
+
else:
|
|
1425
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1426
|
+
self._columnize_unicode(list(ksmeta.functions.keys()))
|
|
1427
|
+
|
|
1428
|
+
def describe_function_client(self, ksname, functionname):
|
|
1429
|
+
if ksname is None:
|
|
1430
|
+
ksname = self.current_keyspace
|
|
1431
|
+
if ksname is None:
|
|
1432
|
+
raise NoKeyspaceError("No keyspace specified and no current keyspace")
|
|
1433
|
+
print('')
|
|
1434
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1435
|
+
functions = [f for f in list(ksmeta.functions.values()) if f.name == functionname]
|
|
1436
|
+
if len(functions) == 0:
|
|
1437
|
+
raise FunctionNotFound("User defined function {} not found".format(functionname))
|
|
1438
|
+
print("\n\n".join(func.export_as_string() for func in functions))
|
|
1439
|
+
print('')
|
|
1440
|
+
|
|
1441
|
+
def describe_aggregates_client(self, ksname):
|
|
1442
|
+
print('')
|
|
1443
|
+
if ksname is None:
|
|
1444
|
+
for ksmeta in self.get_keyspaces():
|
|
1445
|
+
name = protect_name(ksmeta.name)
|
|
1446
|
+
print('Keyspace %s' % (name,))
|
|
1447
|
+
print('---------%s' % ('-' * len(name)))
|
|
1448
|
+
self._columnize_unicode(list(ksmeta.aggregates.keys()))
|
|
1449
|
+
else:
|
|
1450
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1451
|
+
self._columnize_unicode(list(ksmeta.aggregates.keys()))
|
|
1452
|
+
|
|
1453
|
+
def describe_aggregate_client(self, ksname, aggregatename):
|
|
1454
|
+
if ksname is None:
|
|
1455
|
+
ksname = self.current_keyspace
|
|
1456
|
+
if ksname is None:
|
|
1457
|
+
raise NoKeyspaceError("No keyspace specified and no current keyspace")
|
|
1458
|
+
print('')
|
|
1459
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1460
|
+
aggregates = [f for f in list(ksmeta.aggregates.values()) if f.name == aggregatename]
|
|
1461
|
+
if len(aggregates) == 0:
|
|
1462
|
+
raise FunctionNotFound("User defined aggregate {} not found".format(aggregatename))
|
|
1463
|
+
print("\n\n".join(aggr.export_as_string() for aggr in aggregates))
|
|
1464
|
+
print('')
|
|
1465
|
+
|
|
1466
|
+
def describe_usertypes_client(self, ksname):
|
|
1467
|
+
print('')
|
|
1468
|
+
if ksname is None:
|
|
1469
|
+
for ksmeta in self.get_keyspaces():
|
|
1470
|
+
name = protect_name(ksmeta.name)
|
|
1471
|
+
print('Keyspace %s' % (name,))
|
|
1472
|
+
print('---------%s' % ('-' * len(name)))
|
|
1473
|
+
self._columnize_unicode(list(ksmeta.user_types.keys()), quote=True)
|
|
1474
|
+
else:
|
|
1475
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1476
|
+
self._columnize_unicode(list(ksmeta.user_types.keys()), quote=True)
|
|
1477
|
+
|
|
1478
|
+
def describe_usertype_client(self, ksname, typename):
|
|
1479
|
+
if ksname is None:
|
|
1480
|
+
ksname = self.current_keyspace
|
|
1481
|
+
if ksname is None:
|
|
1482
|
+
raise NoKeyspaceError("No keyspace specified and no current keyspace")
|
|
1483
|
+
print('')
|
|
1484
|
+
ksmeta = self.get_keyspace_meta(ksname)
|
|
1485
|
+
try:
|
|
1486
|
+
usertype = ksmeta.user_types[typename]
|
|
1487
|
+
except KeyError:
|
|
1488
|
+
raise UserTypeNotFound("User type {} not found".format(typename))
|
|
1489
|
+
print(usertype.export_as_string())
|
|
1490
|
+
|
|
1491
|
+
def _columnize_unicode(self, name_list, quote=False):
|
|
1492
|
+
"""
|
|
1493
|
+
Used when columnizing identifiers that may contain unicode
|
|
1494
|
+
"""
|
|
1495
|
+
names = [n for n in name_list]
|
|
1496
|
+
if quote:
|
|
1497
|
+
names = protect_names(names)
|
|
1498
|
+
cmd.Cmd.columnize(self, names)
|
|
1499
|
+
print('')
|
|
1500
|
+
|
|
1501
|
+
def describe_cluster_client(self):
|
|
1502
|
+
print('\nCluster: %s' % self.get_cluster_name())
|
|
1503
|
+
p = trim_if_present(self.get_partitioner(), 'org.apache.cassandra.dht.')
|
|
1504
|
+
print('Partitioner: %s\n' % p)
|
|
1505
|
+
# TODO: snitch?
|
|
1506
|
+
# snitch = trim_if_present(self.get_snitch(), 'org.apache.cassandra.locator.')
|
|
1507
|
+
# print 'Snitch: %s\n' % snitch
|
|
1508
|
+
if self.current_keyspace is not None and self.current_keyspace != 'system':
|
|
1509
|
+
print("Range ownership:")
|
|
1510
|
+
ring = self.get_ring(self.current_keyspace)
|
|
1511
|
+
for entry in list(ring.items()):
|
|
1512
|
+
print(' %39s [%s]' % (str(entry[0].value), ', '.join([host.address for host in entry[1]])))
|
|
1513
|
+
print('')
|
|
1514
|
+
|
|
1515
|
+
def describe_schema_client(self, include_system=False):
|
|
1516
|
+
print('')
|
|
1517
|
+
for k in self.get_keyspaces():
|
|
1518
|
+
if include_system or k.name not in cql3handling.SYSTEM_KEYSPACES:
|
|
1519
|
+
self.print_recreate_keyspace(k, sys.stdout)
|
|
1520
|
+
print('')
|
|
1521
|
+
|
|
1522
|
+
# Precondition: the first token in `srcstr.lower()` is either `describe` or `desc`.
|
|
1523
|
+
def perform_describe(self, cmdword, tokens, srcstr):
|
|
1524
|
+
"""
|
|
1525
|
+
DESCRIBE [cqlsh only]
|
|
1526
|
+
|
|
1527
|
+
(DESC may be used as a shorthand.)
|
|
1528
|
+
|
|
1529
|
+
Outputs information about the connected Cassandra cluster, or about
|
|
1530
|
+
the data objects stored in the cluster. Use in one of the following ways:
|
|
1531
|
+
|
|
1532
|
+
DESCRIBE KEYSPACES
|
|
1533
|
+
|
|
1534
|
+
Output the names of all keyspaces.
|
|
1535
|
+
|
|
1536
|
+
DESCRIBE KEYSPACE [<keyspacename>]
|
|
1537
|
+
|
|
1538
|
+
Output CQL commands that could be used to recreate the given keyspace,
|
|
1539
|
+
and the objects in it (such as tables, types, functions, etc.).
|
|
1540
|
+
In some cases, as the CQL interface matures, there will be some metadata
|
|
1541
|
+
about a keyspace that is not representable with CQL. That metadata will not be shown.
|
|
1542
|
+
The '<keyspacename>' argument may be omitted, in which case the current
|
|
1543
|
+
keyspace will be described.
|
|
1544
|
+
|
|
1545
|
+
DESCRIBE TABLES
|
|
1546
|
+
|
|
1547
|
+
Output the names of all tables in the current keyspace, or in all
|
|
1548
|
+
keyspaces if there is no current keyspace.
|
|
1549
|
+
|
|
1550
|
+
DESCRIBE TABLE [<keyspace>.]<tablename>
|
|
1551
|
+
|
|
1552
|
+
Output CQL commands that could be used to recreate the given table.
|
|
1553
|
+
In some cases, as above, there may be table metadata which is not
|
|
1554
|
+
representable and which will not be shown.
|
|
1555
|
+
|
|
1556
|
+
DESCRIBE INDEX <indexname>
|
|
1557
|
+
|
|
1558
|
+
Output the CQL command that could be used to recreate the given index.
|
|
1559
|
+
In some cases, there may be index metadata which is not representable
|
|
1560
|
+
and which will not be shown.
|
|
1561
|
+
|
|
1562
|
+
DESCRIBE MATERIALIZED VIEW <viewname>
|
|
1563
|
+
|
|
1564
|
+
Output the CQL command that could be used to recreate the given materialized view.
|
|
1565
|
+
In some cases, there may be materialized view metadata which is not representable
|
|
1566
|
+
and which will not be shown.
|
|
1567
|
+
|
|
1568
|
+
DESCRIBE CLUSTER
|
|
1569
|
+
|
|
1570
|
+
Output information about the connected Cassandra cluster, such as the
|
|
1571
|
+
cluster name, and the partitioner and snitch in use. When you are
|
|
1572
|
+
connected to a non-system keyspace, also shows endpoint-range
|
|
1573
|
+
ownership information for the Cassandra ring.
|
|
1574
|
+
|
|
1575
|
+
DESCRIBE [FULL] SCHEMA
|
|
1576
|
+
|
|
1577
|
+
Output CQL commands that could be used to recreate the entire (non-system) schema.
|
|
1578
|
+
Works as though "DESCRIBE KEYSPACE k" was invoked for each non-system keyspace
|
|
1579
|
+
k. Use DESCRIBE FULL SCHEMA to include the system keyspaces.
|
|
1580
|
+
|
|
1581
|
+
DESCRIBE TYPES
|
|
1582
|
+
|
|
1583
|
+
Output the names of all user-defined-types in the current keyspace, or in all
|
|
1584
|
+
keyspaces if there is no current keyspace.
|
|
1585
|
+
|
|
1586
|
+
DESCRIBE TYPE [<keyspace>.]<type>
|
|
1587
|
+
|
|
1588
|
+
Output the CQL command that could be used to recreate the given user-defined-type.
|
|
1589
|
+
|
|
1590
|
+
DESCRIBE FUNCTIONS
|
|
1591
|
+
|
|
1592
|
+
Output the names of all user-defined-functions in the current keyspace, or in all
|
|
1593
|
+
keyspaces if there is no current keyspace.
|
|
1594
|
+
|
|
1595
|
+
DESCRIBE FUNCTION [<keyspace>.]<function>
|
|
1596
|
+
|
|
1597
|
+
Output the CQL command that could be used to recreate the given user-defined-function.
|
|
1598
|
+
|
|
1599
|
+
DESCRIBE AGGREGATES
|
|
1600
|
+
|
|
1601
|
+
Output the names of all user-defined-aggregates in the current keyspace, or in all
|
|
1602
|
+
keyspaces if there is no current keyspace.
|
|
1603
|
+
|
|
1604
|
+
DESCRIBE AGGREGATE [<keyspace>.]<aggregate>
|
|
1605
|
+
|
|
1606
|
+
Output the CQL command that could be used to recreate the given user-defined-aggregate.
|
|
1607
|
+
|
|
1608
|
+
DESCRIBE <objname>
|
|
1609
|
+
|
|
1610
|
+
Output CQL commands that could be used to recreate the entire object schema,
|
|
1611
|
+
where object can be either a keyspace or a table or an index or a materialized
|
|
1612
|
+
view (in this order).
|
|
1613
|
+
"""
|
|
1614
|
+
|
|
1615
|
+
def perform_describe_locally(parsed):
|
|
1616
|
+
what = parsed.matched[1][1].lower()
|
|
1617
|
+
if what == 'functions':
|
|
1618
|
+
self.describe_functions_client(self.current_keyspace)
|
|
1619
|
+
elif what == 'function':
|
|
1620
|
+
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1621
|
+
functionname = self.cql_unprotect_name(parsed.get_binding('udfname'))
|
|
1622
|
+
self.describe_function_client(ksname, functionname)
|
|
1623
|
+
elif what == 'aggregates':
|
|
1624
|
+
self.describe_aggregates_client(self.current_keyspace)
|
|
1625
|
+
elif what == 'aggregate':
|
|
1626
|
+
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1627
|
+
aggregatename = self.cql_unprotect_name(parsed.get_binding('udaname'))
|
|
1628
|
+
self.describe_aggregate_client(ksname, aggregatename)
|
|
1629
|
+
elif what == 'keyspaces':
|
|
1630
|
+
self.describe_keyspaces_client()
|
|
1631
|
+
elif what == 'keyspace':
|
|
1632
|
+
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', ''))
|
|
1633
|
+
if not ksname:
|
|
1634
|
+
ksname = self.current_keyspace
|
|
1635
|
+
if ksname is None:
|
|
1636
|
+
self.printerr('Not in any keyspace.')
|
|
1637
|
+
return
|
|
1638
|
+
self.describe_keyspace_client(ksname)
|
|
1639
|
+
elif what in ('columnfamily', 'table'):
|
|
1640
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1641
|
+
cf = self.cql_unprotect_name(parsed.get_binding('cfname'))
|
|
1642
|
+
self.describe_columnfamily_client(ks, cf)
|
|
1643
|
+
elif what == 'index':
|
|
1644
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1645
|
+
idx = self.cql_unprotect_name(parsed.get_binding('idxname', None))
|
|
1646
|
+
self.describe_index_client(ks, idx)
|
|
1647
|
+
elif what == 'materialized' and parsed.matched[2][1].lower() == 'view':
|
|
1648
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1649
|
+
mv = self.cql_unprotect_name(parsed.get_binding('mvname'))
|
|
1650
|
+
self.describe_materialized_view_client(ks, mv)
|
|
1651
|
+
elif what in ('columnfamilies', 'tables'):
|
|
1652
|
+
self.describe_columnfamilies_client(self.current_keyspace)
|
|
1653
|
+
elif what == 'types':
|
|
1654
|
+
self.describe_usertypes_client(self.current_keyspace)
|
|
1655
|
+
elif what == 'type':
|
|
1656
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1657
|
+
ut = self.cql_unprotect_name(parsed.get_binding('utname'))
|
|
1658
|
+
self.describe_usertype_client(ks, ut)
|
|
1659
|
+
elif what == 'cluster':
|
|
1660
|
+
self.describe_cluster_client()
|
|
1661
|
+
elif what == 'schema':
|
|
1662
|
+
self.describe_schema_client(False)
|
|
1663
|
+
elif what == 'full' and parsed.matched[2][1].lower() == 'schema':
|
|
1664
|
+
self.describe_schema_client(True)
|
|
1665
|
+
elif what:
|
|
1666
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1667
|
+
name = self.cql_unprotect_name(parsed.get_binding('cfname'))
|
|
1668
|
+
if not name:
|
|
1669
|
+
name = self.cql_unprotect_name(parsed.get_binding('idxname', None))
|
|
1670
|
+
if not name:
|
|
1671
|
+
name = self.cql_unprotect_name(parsed.get_binding('mvname', None))
|
|
1672
|
+
self.describe_object_client(ks, name)
|
|
1673
|
+
|
|
1674
|
+
stmt = SimpleStatement(srcstr, consistency_level=cassandra.ConsistencyLevel.LOCAL_ONE,
|
|
1675
|
+
fetch_size=self.page_size if self.use_paging else None)
|
|
1676
|
+
future = self.session.execute_async(stmt)
|
|
1677
|
+
try:
|
|
1678
|
+
result = future.result()
|
|
1679
|
+
|
|
1680
|
+
# The second token in the statement indicates which
|
|
1681
|
+
# kind of DESCRIBE we're performing.
|
|
1682
|
+
what = srcstr.split()[1].lower().rstrip(';')
|
|
1683
|
+
|
|
1684
|
+
if what in ('columnfamilies', 'tables', 'types', 'functions', 'aggregates'):
|
|
1685
|
+
self.describe_list(result)
|
|
1686
|
+
elif what == 'keyspaces':
|
|
1687
|
+
self.describe_keyspaces(result)
|
|
1688
|
+
elif what == 'cluster':
|
|
1689
|
+
self.describe_cluster(result)
|
|
1690
|
+
elif what:
|
|
1691
|
+
self.describe_element(result)
|
|
1692
|
+
|
|
1693
|
+
except cassandra.protocol.SyntaxException:
|
|
1694
|
+
# Server doesn't support DESCRIBE query, retry with
|
|
1695
|
+
# client-side DESCRIBE implementation
|
|
1696
|
+
parsed = cqlruleset.cql_whole_parse_tokens(tokens, srcstr=srcstr,
|
|
1697
|
+
startsymbol='cqlshCommand')
|
|
1698
|
+
if parsed and not parsed.remainder:
|
|
1699
|
+
return perform_describe_locally(parsed)
|
|
1700
|
+
else:
|
|
1701
|
+
return self.handle_parse_error(cmdword, tokens, parsed, srcstr)
|
|
1702
|
+
except CQL_ERRORS as err:
|
|
1703
|
+
err_msg = err.message if hasattr(err, 'message') else str(err)
|
|
1704
|
+
self.printerr(err_msg.partition("message=")[2].strip('"'))
|
|
1705
|
+
except Exception:
|
|
1706
|
+
import traceback
|
|
1707
|
+
self.printerr(traceback.format_exc())
|
|
1708
|
+
|
|
1709
|
+
if future:
|
|
1710
|
+
if future.warnings:
|
|
1711
|
+
self.print_warnings(future.warnings)
|
|
1712
|
+
|
|
1713
|
+
def describe_keyspaces(self, rows):
|
|
1714
|
+
"""
|
|
1715
|
+
Print the output for a DESCRIBE KEYSPACES query
|
|
1716
|
+
"""
|
|
1717
|
+
names = [r['name'] for r in rows]
|
|
1718
|
+
|
|
1719
|
+
print('')
|
|
1720
|
+
cmd.Cmd.columnize(self, names)
|
|
1721
|
+
print('')
|
|
1722
|
+
|
|
1723
|
+
def describe_list(self, rows):
|
|
1724
|
+
"""
|
|
1725
|
+
Print the output for all the DESCRIBE queries for element names (e.g DESCRIBE TABLES, DESCRIBE FUNCTIONS ...)
|
|
1726
|
+
"""
|
|
1727
|
+
keyspace = None
|
|
1728
|
+
names = list()
|
|
1729
|
+
for row in rows:
|
|
1730
|
+
if row['keyspace_name'] != keyspace:
|
|
1731
|
+
if keyspace is not None:
|
|
1732
|
+
self.print_keyspace_element_names(keyspace, names)
|
|
1733
|
+
|
|
1734
|
+
keyspace = row['keyspace_name']
|
|
1735
|
+
names = list()
|
|
1736
|
+
|
|
1737
|
+
names.append(str(row['name']))
|
|
1738
|
+
|
|
1739
|
+
if keyspace is not None:
|
|
1740
|
+
self.print_keyspace_element_names(keyspace, names)
|
|
1741
|
+
print('')
|
|
1742
|
+
|
|
1743
|
+
def print_keyspace_element_names(self, keyspace, names):
|
|
1744
|
+
print('')
|
|
1745
|
+
if self.current_keyspace is None:
|
|
1746
|
+
print('Keyspace %s' % (keyspace))
|
|
1747
|
+
print('---------%s' % ('-' * len(keyspace)))
|
|
1748
|
+
cmd.Cmd.columnize(self, names)
|
|
1749
|
+
|
|
1750
|
+
def describe_element(self, rows):
|
|
1751
|
+
"""
|
|
1752
|
+
Print the output for all the DESCRIBE queries where an element name as been specified (e.g DESCRIBE TABLE, DESCRIBE INDEX ...)
|
|
1753
|
+
"""
|
|
1754
|
+
for row in rows:
|
|
1755
|
+
print('')
|
|
1756
|
+
self.query_out.write(row['create_statement'])
|
|
1757
|
+
print('')
|
|
1758
|
+
|
|
1759
|
+
def describe_cluster(self, rows):
|
|
1760
|
+
"""
|
|
1761
|
+
Print the output for a DESCRIBE CLUSTER query.
|
|
1762
|
+
|
|
1763
|
+
If a specified keyspace was in use the returned ResultSet will contains a 'range_ownership' column,
|
|
1764
|
+
otherwise not.
|
|
1765
|
+
"""
|
|
1766
|
+
for row in rows:
|
|
1767
|
+
print('\nCluster: %s' % row['cluster'])
|
|
1768
|
+
print('Partitioner: %s' % row['partitioner'])
|
|
1769
|
+
print('Snitch: %s\n' % row['snitch'])
|
|
1770
|
+
if 'range_ownership' in row:
|
|
1771
|
+
print("Range ownership:")
|
|
1772
|
+
for entry in list(row['range_ownership'].items()):
|
|
1773
|
+
print(' %39s [%s]' % (entry[0], ', '.join([host for host in entry[1]])))
|
|
1774
|
+
print('')
|
|
1775
|
+
|
|
1776
|
+
def do_copy(self, parsed):
|
|
1777
|
+
r"""
|
|
1778
|
+
COPY [cqlsh only]
|
|
1779
|
+
|
|
1780
|
+
COPY x FROM: Imports CSV data into a Cassandra table
|
|
1781
|
+
COPY x TO: Exports data from a Cassandra table in CSV format.
|
|
1782
|
+
|
|
1783
|
+
COPY <table_name> [ ( column [, ...] ) ]
|
|
1784
|
+
FROM ( '<file_pattern_1, file_pattern_2, ... file_pattern_n>' | STDIN )
|
|
1785
|
+
[ WITH <option>='value' [AND ...] ];
|
|
1786
|
+
|
|
1787
|
+
File patterns are either file names or valid python glob expressions, e.g. *.csv or folder/*.csv.
|
|
1788
|
+
|
|
1789
|
+
COPY <table_name> [ ( column [, ...] ) ]
|
|
1790
|
+
TO ( '<filename>' | STDOUT )
|
|
1791
|
+
[ WITH <option>='value' [AND ...] ];
|
|
1792
|
+
|
|
1793
|
+
Available common COPY options and defaults:
|
|
1794
|
+
|
|
1795
|
+
DELIMITER=',' - character that appears between records
|
|
1796
|
+
QUOTE='"' - quoting character to be used to quote fields
|
|
1797
|
+
ESCAPE='\' - character to appear before the QUOTE char when quoted
|
|
1798
|
+
HEADER=false - whether to ignore the first line
|
|
1799
|
+
NULL='' - string that represents a null value
|
|
1800
|
+
DATETIMEFORMAT= - timestamp strftime format
|
|
1801
|
+
'%Y-%m-%d %H:%M:%S%z' defaults to time_format value in cqlshrc
|
|
1802
|
+
MAXATTEMPTS=5 - the maximum number of attempts per batch or range
|
|
1803
|
+
REPORTFREQUENCY=0.25 - the frequency with which we display status updates in seconds
|
|
1804
|
+
DECIMALSEP='.' - the separator for decimal values
|
|
1805
|
+
THOUSANDSSEP='' - the separator for thousands digit groups
|
|
1806
|
+
BOOLSTYLE='True,False' - the representation for booleans, case insensitive, specify true followed by false,
|
|
1807
|
+
for example yes,no or 1,0
|
|
1808
|
+
NUMPROCESSES=n - the number of worker processes, by default the number of cores minus one
|
|
1809
|
+
capped at 16
|
|
1810
|
+
CONFIGFILE='' - a configuration file with the same format as .cqlshrc (see the Python ConfigParser
|
|
1811
|
+
documentation) where you can specify WITH options under the following optional
|
|
1812
|
+
sections: [copy], [copy-to], [copy-from], [copy:ks.table], [copy-to:ks.table],
|
|
1813
|
+
[copy-from:ks.table], where <ks> is your keyspace name and <table> is your table
|
|
1814
|
+
name. Options are read from these sections, in the order specified
|
|
1815
|
+
above, and command line options always override options in configuration files.
|
|
1816
|
+
Depending on the COPY direction, only the relevant copy-from or copy-to sections
|
|
1817
|
+
are used. If no configfile is specified then .cqlshrc is searched instead.
|
|
1818
|
+
RATEFILE='' - an optional file where to print the output statistics
|
|
1819
|
+
|
|
1820
|
+
Available COPY FROM options and defaults:
|
|
1821
|
+
|
|
1822
|
+
CHUNKSIZE=5000 - the size of chunks passed to worker processes
|
|
1823
|
+
INGESTRATE=100000 - an approximate ingest rate in rows per second
|
|
1824
|
+
MINBATCHSIZE=10 - the minimum size of an import batch
|
|
1825
|
+
MAXBATCHSIZE=20 - the maximum size of an import batch
|
|
1826
|
+
MAXROWS=-1 - the maximum number of rows, -1 means no maximum
|
|
1827
|
+
SKIPROWS=0 - the number of rows to skip
|
|
1828
|
+
SKIPCOLS='' - a comma separated list of column names to skip
|
|
1829
|
+
MAXPARSEERRORS=-1 - the maximum global number of parsing errors, -1 means no maximum
|
|
1830
|
+
MAXINSERTERRORS=1000 - the maximum global number of insert errors, -1 means no maximum
|
|
1831
|
+
ERRFILE='' - a file where to store all rows that could not be imported, by default this is
|
|
1832
|
+
import_ks_table.err where <ks> is your keyspace and <table> is your table name.
|
|
1833
|
+
PREPAREDSTATEMENTS=True - whether to use prepared statements when importing, by default True. Set this to
|
|
1834
|
+
False if you don't mind shifting data parsing to the cluster. The cluster will also
|
|
1835
|
+
have to compile every batch statement. For large and oversized clusters
|
|
1836
|
+
this will result in a faster import but for smaller clusters it may generate
|
|
1837
|
+
timeouts.
|
|
1838
|
+
TTL=3600 - the time to live in seconds, by default data will not expire
|
|
1839
|
+
|
|
1840
|
+
Available COPY TO options and defaults:
|
|
1841
|
+
|
|
1842
|
+
ENCODING='utf8' - encoding for CSV output
|
|
1843
|
+
PAGESIZE='1000' - the page size for fetching results
|
|
1844
|
+
PAGETIMEOUT=10 - the page timeout in seconds for fetching results
|
|
1845
|
+
BEGINTOKEN='' - the minimum token string to consider when exporting data
|
|
1846
|
+
ENDTOKEN='' - the maximum token string to consider when exporting data
|
|
1847
|
+
MAXREQUESTS=6 - the maximum number of requests each worker process can work on in parallel
|
|
1848
|
+
MAXOUTPUTSIZE='-1' - the maximum size of the output file measured in number of lines,
|
|
1849
|
+
beyond this maximum the output file will be split into segments,
|
|
1850
|
+
-1 means unlimited.
|
|
1851
|
+
FLOATPRECISION=5 - the number of digits displayed after the decimal point for cql float values
|
|
1852
|
+
DOUBLEPRECISION=12 - the number of digits displayed after the decimal point for cql double values
|
|
1853
|
+
|
|
1854
|
+
When entering CSV data on STDIN, you can use the sequence "\."
|
|
1855
|
+
on a line by itself to end the data input.
|
|
1856
|
+
"""
|
|
1857
|
+
|
|
1858
|
+
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
|
|
1859
|
+
if ks is None:
|
|
1860
|
+
ks = self.current_keyspace
|
|
1861
|
+
if ks is None:
|
|
1862
|
+
raise NoKeyspaceError("Not in any keyspace.")
|
|
1863
|
+
table = self.cql_unprotect_name(parsed.get_binding('cfname'))
|
|
1864
|
+
columns = parsed.get_binding('colnames', None)
|
|
1865
|
+
if columns is not None:
|
|
1866
|
+
columns = list(map(self.cql_unprotect_name, columns))
|
|
1867
|
+
else:
|
|
1868
|
+
# default to all known columns
|
|
1869
|
+
columns = self.get_column_names(ks, table)
|
|
1870
|
+
|
|
1871
|
+
fname = parsed.get_binding('fname', None)
|
|
1872
|
+
if fname is not None:
|
|
1873
|
+
fname = self.cql_unprotect_value(fname)
|
|
1874
|
+
|
|
1875
|
+
copyoptnames = list(map(str.lower, parsed.get_binding('optnames', ())))
|
|
1876
|
+
copyoptvals = list(map(self.cql_unprotect_value, parsed.get_binding('optvals', ())))
|
|
1877
|
+
opts = dict(list(zip(copyoptnames, copyoptvals)))
|
|
1878
|
+
|
|
1879
|
+
direction = parsed.get_binding('dir').upper()
|
|
1880
|
+
if direction == 'FROM':
|
|
1881
|
+
task = ImportTask(self, ks, table, columns, fname, opts, self.conn.protocol_version, CONFIG_FILE)
|
|
1882
|
+
elif direction == 'TO':
|
|
1883
|
+
task = ExportTask(self, ks, table, columns, fname, opts, self.conn.protocol_version, CONFIG_FILE)
|
|
1884
|
+
else:
|
|
1885
|
+
raise SyntaxError("Unknown direction %s" % direction)
|
|
1886
|
+
|
|
1887
|
+
task.run()
|
|
1888
|
+
|
|
1889
|
+
def do_show(self, parsed):
|
|
1890
|
+
"""
|
|
1891
|
+
SHOW [cqlsh only]
|
|
1892
|
+
|
|
1893
|
+
Displays information about the current cqlsh session. Can be called in
|
|
1894
|
+
the following ways:
|
|
1895
|
+
|
|
1896
|
+
SHOW VERSION
|
|
1897
|
+
|
|
1898
|
+
Shows the version and build of the connected Cassandra instance, as
|
|
1899
|
+
well as the version of the CQL spec that the connected Cassandra
|
|
1900
|
+
instance understands.
|
|
1901
|
+
|
|
1902
|
+
SHOW HOST
|
|
1903
|
+
|
|
1904
|
+
Shows where cqlsh is currently connected.
|
|
1905
|
+
|
|
1906
|
+
SHOW SESSION <sessionid>
|
|
1907
|
+
|
|
1908
|
+
Pretty-prints the requested tracing session.
|
|
1909
|
+
|
|
1910
|
+
SHOW REPLICAS <token> (<keyspace>)
|
|
1911
|
+
|
|
1912
|
+
Lists the replica nodes by IP address for the given token. The current
|
|
1913
|
+
keyspace is used if one is not specified.
|
|
1914
|
+
"""
|
|
1915
|
+
showwhat = parsed.get_binding('what').lower()
|
|
1916
|
+
if showwhat == 'version':
|
|
1917
|
+
self.get_connection_versions()
|
|
1918
|
+
self.get_scylla_version()
|
|
1919
|
+
self.show_version()
|
|
1920
|
+
elif showwhat == 'host':
|
|
1921
|
+
self.show_host()
|
|
1922
|
+
elif showwhat.startswith('session'):
|
|
1923
|
+
session_id = parsed.get_binding('sessionid').lower()
|
|
1924
|
+
self.show_session(UUID(session_id))
|
|
1925
|
+
elif showwhat.startswith('replicas'):
|
|
1926
|
+
token_id = parsed.get_binding('token')
|
|
1927
|
+
keyspace = parsed.get_binding('keyspace')
|
|
1928
|
+
self.show_replicas(token_id, keyspace)
|
|
1929
|
+
else:
|
|
1930
|
+
self.printerr('Wait, how do I show %r?' % (showwhat,))
|
|
1931
|
+
|
|
1932
|
+
def do_source(self, parsed):
|
|
1933
|
+
"""
|
|
1934
|
+
SOURCE [cqlsh only]
|
|
1935
|
+
|
|
1936
|
+
Executes a file containing CQL statements. Gives the output for each
|
|
1937
|
+
statement in turn, if any, or any errors that occur along the way.
|
|
1938
|
+
|
|
1939
|
+
Errors do NOT abort execution of the CQL source file.
|
|
1940
|
+
|
|
1941
|
+
Usage:
|
|
1942
|
+
|
|
1943
|
+
SOURCE '<file>';
|
|
1944
|
+
|
|
1945
|
+
That is, the path to the file to be executed must be given inside a
|
|
1946
|
+
string literal. The path is interpreted relative to the current working
|
|
1947
|
+
directory. The tilde shorthand notation ('~/mydir') is supported for
|
|
1948
|
+
referring to $HOME.
|
|
1949
|
+
|
|
1950
|
+
See also the --file option to cqlsh.
|
|
1951
|
+
"""
|
|
1952
|
+
fname = parsed.get_binding('fname')
|
|
1953
|
+
fname = os.path.expanduser(self.cql_unprotect_value(fname))
|
|
1954
|
+
try:
|
|
1955
|
+
encoding, bom_size = get_file_encoding_bomsize(fname)
|
|
1956
|
+
f = codecs.open(fname, 'r', encoding)
|
|
1957
|
+
f.seek(bom_size)
|
|
1958
|
+
except IOError as e:
|
|
1959
|
+
self.printerr('Could not open %r: %s' % (fname, e))
|
|
1960
|
+
return
|
|
1961
|
+
subshell = Shell(self.hostname, self.port, color=self.color,
|
|
1962
|
+
username=self.username,
|
|
1963
|
+
encoding=self.encoding, stdin=f, tty=False, use_conn=self.conn,
|
|
1964
|
+
cqlver=self.cql_version, keyspace=self.current_keyspace,
|
|
1965
|
+
tracing_enabled=self.tracing_enabled,
|
|
1966
|
+
display_nanotime_format=self.display_nanotime_format,
|
|
1967
|
+
display_timestamp_format=self.display_timestamp_format,
|
|
1968
|
+
display_date_format=self.display_date_format,
|
|
1969
|
+
display_float_precision=self.display_float_precision,
|
|
1970
|
+
display_double_precision=self.display_double_precision,
|
|
1971
|
+
display_timezone=self.display_timezone,
|
|
1972
|
+
max_trace_wait=self.max_trace_wait, ssl=self.ssl,
|
|
1973
|
+
request_timeout=self.session.default_timeout,
|
|
1974
|
+
connect_timeout=self.conn.connect_timeout,
|
|
1975
|
+
is_subshell=True,
|
|
1976
|
+
auth_provider=self.auth_provider,
|
|
1977
|
+
)
|
|
1978
|
+
# duplicate coverage related settings in subshell
|
|
1979
|
+
if self.coverage:
|
|
1980
|
+
subshell.coverage = True
|
|
1981
|
+
subshell.coveragerc_path = self.coveragerc_path
|
|
1982
|
+
subshell.cmdloop()
|
|
1983
|
+
f.close()
|
|
1984
|
+
|
|
1985
|
+
def do_capture(self, parsed):
|
|
1986
|
+
"""
|
|
1987
|
+
CAPTURE [cqlsh only]
|
|
1988
|
+
|
|
1989
|
+
Begins capturing command output and appending it to a specified file.
|
|
1990
|
+
Output will not be shown at the console while it is captured.
|
|
1991
|
+
|
|
1992
|
+
Usage:
|
|
1993
|
+
|
|
1994
|
+
CAPTURE '<file>';
|
|
1995
|
+
CAPTURE OFF;
|
|
1996
|
+
CAPTURE;
|
|
1997
|
+
|
|
1998
|
+
That is, the path to the file to be appended to must be given inside a
|
|
1999
|
+
string literal. The path is interpreted relative to the current working
|
|
2000
|
+
directory. The tilde shorthand notation ('~/mydir') is supported for
|
|
2001
|
+
referring to $HOME.
|
|
2002
|
+
|
|
2003
|
+
Only query result output is captured. Errors and output from cqlsh-only
|
|
2004
|
+
commands will still be shown in the cqlsh session.
|
|
2005
|
+
|
|
2006
|
+
To stop capturing output and show it in the cqlsh session again, use
|
|
2007
|
+
CAPTURE OFF.
|
|
2008
|
+
|
|
2009
|
+
To inspect the current capture configuration, use CAPTURE with no
|
|
2010
|
+
arguments.
|
|
2011
|
+
"""
|
|
2012
|
+
fname = parsed.get_binding('fname')
|
|
2013
|
+
if fname is None:
|
|
2014
|
+
if self.shunted_query_out is not None:
|
|
2015
|
+
print("Currently capturing query output to %r." % (self.query_out.name,))
|
|
2016
|
+
else:
|
|
2017
|
+
print("Currently not capturing query output.")
|
|
2018
|
+
return
|
|
2019
|
+
|
|
2020
|
+
if fname.upper() == 'OFF':
|
|
2021
|
+
if self.shunted_query_out is None:
|
|
2022
|
+
self.printerr('Not currently capturing output.')
|
|
2023
|
+
return
|
|
2024
|
+
self.query_out.close()
|
|
2025
|
+
self.query_out = self.shunted_query_out
|
|
2026
|
+
self.color = self.shunted_color
|
|
2027
|
+
self.shunted_query_out = None
|
|
2028
|
+
del self.shunted_color
|
|
2029
|
+
return
|
|
2030
|
+
|
|
2031
|
+
if self.shunted_query_out is not None:
|
|
2032
|
+
self.printerr('Already capturing output to %s. Use CAPTURE OFF'
|
|
2033
|
+
' to disable.' % (self.query_out.name,))
|
|
2034
|
+
return
|
|
2035
|
+
|
|
2036
|
+
fname = os.path.expanduser(self.cql_unprotect_value(fname))
|
|
2037
|
+
try:
|
|
2038
|
+
f = open(fname, 'a')
|
|
2039
|
+
except IOError as e:
|
|
2040
|
+
self.printerr('Could not open %r for append: %s' % (fname, e))
|
|
2041
|
+
return
|
|
2042
|
+
self.shunted_query_out = self.query_out
|
|
2043
|
+
self.shunted_color = self.color
|
|
2044
|
+
self.query_out = f
|
|
2045
|
+
self.color = False
|
|
2046
|
+
print('Now capturing query output to %r.' % (fname,))
|
|
2047
|
+
|
|
2048
|
+
def do_tracing(self, parsed):
|
|
2049
|
+
"""
|
|
2050
|
+
TRACING [cqlsh]
|
|
2051
|
+
|
|
2052
|
+
Enables or disables request tracing.
|
|
2053
|
+
|
|
2054
|
+
TRACING ON
|
|
2055
|
+
|
|
2056
|
+
Enables tracing for all further requests.
|
|
2057
|
+
|
|
2058
|
+
TRACING OFF
|
|
2059
|
+
|
|
2060
|
+
Disables tracing.
|
|
2061
|
+
|
|
2062
|
+
TRACING
|
|
2063
|
+
|
|
2064
|
+
TRACING with no arguments shows the current tracing status.
|
|
2065
|
+
"""
|
|
2066
|
+
self.tracing_enabled = SwitchCommand("TRACING", "Tracing").execute(self.tracing_enabled, parsed, self.printerr)
|
|
2067
|
+
|
|
2068
|
+
def do_expand(self, parsed):
|
|
2069
|
+
"""
|
|
2070
|
+
EXPAND [cqlsh]
|
|
2071
|
+
|
|
2072
|
+
Enables or disables expanded (vertical) output.
|
|
2073
|
+
|
|
2074
|
+
EXPAND ON
|
|
2075
|
+
|
|
2076
|
+
Enables expanded (vertical) output.
|
|
2077
|
+
|
|
2078
|
+
EXPAND OFF
|
|
2079
|
+
|
|
2080
|
+
Disables expanded (vertical) output.
|
|
2081
|
+
|
|
2082
|
+
EXPAND
|
|
2083
|
+
|
|
2084
|
+
EXPAND with no arguments shows the current value of expand setting.
|
|
2085
|
+
"""
|
|
2086
|
+
self.expand_enabled = SwitchCommand("EXPAND", "Expanded output").execute(self.expand_enabled, parsed, self.printerr)
|
|
2087
|
+
|
|
2088
|
+
def do_consistency(self, parsed):
|
|
2089
|
+
"""
|
|
2090
|
+
CONSISTENCY [cqlsh only]
|
|
2091
|
+
|
|
2092
|
+
Overrides default consistency level (default level is ONE).
|
|
2093
|
+
|
|
2094
|
+
CONSISTENCY <level>
|
|
2095
|
+
|
|
2096
|
+
Sets consistency level for future requests.
|
|
2097
|
+
|
|
2098
|
+
Valid consistency levels:
|
|
2099
|
+
|
|
2100
|
+
ANY, ONE, TWO, THREE, QUORUM, ALL, LOCAL_ONE, LOCAL_QUORUM, EACH_QUORUM, SERIAL and LOCAL_SERIAL.
|
|
2101
|
+
|
|
2102
|
+
SERIAL and LOCAL_SERIAL may be used only for SELECTs; will be rejected with updates.
|
|
2103
|
+
|
|
2104
|
+
CONSISTENCY
|
|
2105
|
+
|
|
2106
|
+
CONSISTENCY with no arguments shows the current consistency level.
|
|
2107
|
+
"""
|
|
2108
|
+
level = parsed.get_binding('level')
|
|
2109
|
+
if level is None:
|
|
2110
|
+
print('Current consistency level is %s.' % (cassandra.ConsistencyLevel.value_to_name[self.consistency_level]))
|
|
2111
|
+
return
|
|
2112
|
+
|
|
2113
|
+
self.consistency_level = cassandra.ConsistencyLevel.name_to_value[level.upper()]
|
|
2114
|
+
print('Consistency level set to %s.' % (level.upper(),))
|
|
2115
|
+
|
|
2116
|
+
def do_serial(self, parsed):
|
|
2117
|
+
"""
|
|
2118
|
+
SERIAL CONSISTENCY [cqlsh only]
|
|
2119
|
+
|
|
2120
|
+
Overrides serial consistency level (default level is SERIAL).
|
|
2121
|
+
|
|
2122
|
+
SERIAL CONSISTENCY <level>
|
|
2123
|
+
|
|
2124
|
+
Sets consistency level for future conditional updates.
|
|
2125
|
+
|
|
2126
|
+
Valid consistency levels:
|
|
2127
|
+
|
|
2128
|
+
SERIAL, LOCAL_SERIAL.
|
|
2129
|
+
|
|
2130
|
+
SERIAL CONSISTENCY
|
|
2131
|
+
|
|
2132
|
+
SERIAL CONSISTENCY with no arguments shows the current consistency level.
|
|
2133
|
+
"""
|
|
2134
|
+
level = parsed.get_binding('level')
|
|
2135
|
+
if level is None:
|
|
2136
|
+
print('Current serial consistency level is %s.' % (cassandra.ConsistencyLevel.value_to_name[self.serial_consistency_level]))
|
|
2137
|
+
return
|
|
2138
|
+
|
|
2139
|
+
self.serial_consistency_level = cassandra.ConsistencyLevel.name_to_value[level.upper()]
|
|
2140
|
+
print('Serial consistency level set to %s.' % (level.upper(),))
|
|
2141
|
+
|
|
2142
|
+
def do_login(self, parsed):
|
|
2143
|
+
"""
|
|
2144
|
+
LOGIN [cqlsh only]
|
|
2145
|
+
|
|
2146
|
+
Changes login information without requiring restart.
|
|
2147
|
+
|
|
2148
|
+
LOGIN <username> (<password>)
|
|
2149
|
+
|
|
2150
|
+
Login using the specified username. If password is specified, it will be used
|
|
2151
|
+
otherwise, you will be prompted to enter.
|
|
2152
|
+
"""
|
|
2153
|
+
username = parsed.get_binding('username')
|
|
2154
|
+
password = parsed.get_binding('password')
|
|
2155
|
+
if password is None:
|
|
2156
|
+
password = getpass.getpass()
|
|
2157
|
+
else:
|
|
2158
|
+
password = password[1:-1]
|
|
2159
|
+
|
|
2160
|
+
auth_provider = PlainTextAuthProvider(username=username, password=password)
|
|
2161
|
+
|
|
2162
|
+
kwargs = {}
|
|
2163
|
+
kwargs['contact_points'] = (self.hostname,)
|
|
2164
|
+
kwargs['port'] = self.port
|
|
2165
|
+
kwargs['ssl_context'] = self.conn.ssl_context
|
|
2166
|
+
kwargs['ssl_options'] = self.conn.ssl_options
|
|
2167
|
+
|
|
2168
|
+
# Preserve compression setting from original connection
|
|
2169
|
+
if self.no_compression:
|
|
2170
|
+
kwargs['compression'] = False
|
|
2171
|
+
|
|
2172
|
+
conn = Cluster(cql_version=self.conn.cql_version,
|
|
2173
|
+
protocol_version=self.conn.protocol_version,
|
|
2174
|
+
auth_provider=auth_provider,
|
|
2175
|
+
control_connection_timeout=self.conn.connect_timeout,
|
|
2176
|
+
connect_timeout=self.conn.connect_timeout,
|
|
2177
|
+
execution_profiles=self.profiles,
|
|
2178
|
+
**kwargs)
|
|
2179
|
+
|
|
2180
|
+
if self.current_keyspace:
|
|
2181
|
+
session = conn.connect(self.current_keyspace)
|
|
2182
|
+
else:
|
|
2183
|
+
session = conn.connect()
|
|
2184
|
+
|
|
2185
|
+
# Copy session properties
|
|
2186
|
+
session.max_trace_wait = self.session.max_trace_wait
|
|
2187
|
+
|
|
2188
|
+
# Update after we've connected in case we fail to authenticate
|
|
2189
|
+
self.conn = conn
|
|
2190
|
+
self.auth_provider = auth_provider
|
|
2191
|
+
self.username = username
|
|
2192
|
+
self.session = session
|
|
2193
|
+
|
|
2194
|
+
def do_exit(self, parsed=None):
|
|
2195
|
+
"""
|
|
2196
|
+
EXIT/QUIT [cqlsh only]
|
|
2197
|
+
|
|
2198
|
+
Exits cqlsh.
|
|
2199
|
+
"""
|
|
2200
|
+
self.stop = True
|
|
2201
|
+
if self.owns_connection:
|
|
2202
|
+
self.conn.shutdown()
|
|
2203
|
+
do_quit = do_exit
|
|
2204
|
+
|
|
2205
|
+
def do_clear(self, parsed):
|
|
2206
|
+
"""
|
|
2207
|
+
CLEAR/CLS [cqlsh only]
|
|
2208
|
+
|
|
2209
|
+
Clears the console.
|
|
2210
|
+
"""
|
|
2211
|
+
subprocess.call('clear', shell=True)
|
|
2212
|
+
do_cls = do_clear
|
|
2213
|
+
|
|
2214
|
+
def do_debug(self, parsed):
|
|
2215
|
+
import pdb
|
|
2216
|
+
pdb.set_trace()
|
|
2217
|
+
|
|
2218
|
+
def get_help_topics(self):
|
|
2219
|
+
topics = [t[3:] for t in dir(self) if t.startswith('do_') and getattr(self, t, None).__doc__]
|
|
2220
|
+
for hide_from_help in ('quit',):
|
|
2221
|
+
topics.remove(hide_from_help)
|
|
2222
|
+
return topics
|
|
2223
|
+
|
|
2224
|
+
def columnize(self, slist, *a, **kw):
|
|
2225
|
+
return cmd.Cmd.columnize(self, sorted([u.upper() for u in slist]), *a, **kw)
|
|
2226
|
+
|
|
2227
|
+
def do_help(self, parsed):
|
|
2228
|
+
"""
|
|
2229
|
+
HELP [cqlsh only]
|
|
2230
|
+
|
|
2231
|
+
Gives information about cqlsh commands. To see available topics,
|
|
2232
|
+
enter "HELP" without any arguments. To see help on a topic,
|
|
2233
|
+
use "HELP <topic>".
|
|
2234
|
+
"""
|
|
2235
|
+
topics = parsed.get_binding('topic', ())
|
|
2236
|
+
if not topics:
|
|
2237
|
+
shell_topics = [t.upper() for t in self.get_help_topics()]
|
|
2238
|
+
self.print_topics("\nDocumented shell commands:", shell_topics, 15, 80)
|
|
2239
|
+
cql_topics = [t.upper() for t in cqldocs.get_help_topics()]
|
|
2240
|
+
self.print_topics("CQL help topics:", cql_topics, 15, 80)
|
|
2241
|
+
return
|
|
2242
|
+
for t in topics:
|
|
2243
|
+
if t.lower() in self.get_help_topics():
|
|
2244
|
+
doc = getattr(self, 'do_' + t.lower()).__doc__
|
|
2245
|
+
self.stdout.write(doc + "\n")
|
|
2246
|
+
elif t.lower() in cqldocs.get_help_topics():
|
|
2247
|
+
urlpart = cqldocs.get_help_topic(t)
|
|
2248
|
+
if urlpart is not None:
|
|
2249
|
+
url = "%s#%s" % (CASSANDRA_CQL_HTML, urlpart)
|
|
2250
|
+
if self.browser is not None:
|
|
2251
|
+
opened = webbrowser.get(self.browser).open_new_tab(url)
|
|
2252
|
+
else:
|
|
2253
|
+
opened = webbrowser.open_new_tab(url)
|
|
2254
|
+
if not opened:
|
|
2255
|
+
self.printerr("*** No browser to display CQL help. URL for help topic %s : %s" % (t, url))
|
|
2256
|
+
else:
|
|
2257
|
+
self.printerr("*** No help on %s" % (t,))
|
|
2258
|
+
|
|
2259
|
+
def do_unicode(self, parsed):
|
|
2260
|
+
"""
|
|
2261
|
+
Textual input/output
|
|
2262
|
+
|
|
2263
|
+
When control characters, or other characters which can't be encoded
|
|
2264
|
+
in your current locale, are found in values of 'text' or 'ascii'
|
|
2265
|
+
types, it will be shown as a backslash escape. If color is enabled,
|
|
2266
|
+
any such backslash escapes will be shown in a different color from
|
|
2267
|
+
the surrounding text.
|
|
2268
|
+
|
|
2269
|
+
Unicode code points in your data will be output intact, if the
|
|
2270
|
+
encoding for your locale is capable of decoding them. If you prefer
|
|
2271
|
+
that non-ascii characters be shown with Python-style "\\uABCD"
|
|
2272
|
+
escape sequences, invoke cqlsh with an ASCII locale (for example,
|
|
2273
|
+
by setting the $LANG environment variable to "C").
|
|
2274
|
+
"""
|
|
2275
|
+
|
|
2276
|
+
def do_paging(self, parsed):
|
|
2277
|
+
"""
|
|
2278
|
+
PAGING [cqlsh]
|
|
2279
|
+
|
|
2280
|
+
Enables or disables query paging.
|
|
2281
|
+
|
|
2282
|
+
PAGING ON
|
|
2283
|
+
|
|
2284
|
+
Enables query paging for all further queries.
|
|
2285
|
+
|
|
2286
|
+
PAGING OFF
|
|
2287
|
+
|
|
2288
|
+
Disables paging.
|
|
2289
|
+
|
|
2290
|
+
PAGING
|
|
2291
|
+
|
|
2292
|
+
PAGING with no arguments shows the current query paging status.
|
|
2293
|
+
"""
|
|
2294
|
+
(self.use_paging, requested_page_size) = SwitchCommandWithValue(
|
|
2295
|
+
"PAGING", "Query paging", value_type=int).execute(self.use_paging, parsed, self.printerr)
|
|
2296
|
+
if self.use_paging and requested_page_size is not None:
|
|
2297
|
+
self.page_size = requested_page_size
|
|
2298
|
+
if self.use_paging:
|
|
2299
|
+
print(("Page size: {}".format(self.page_size)))
|
|
2300
|
+
else:
|
|
2301
|
+
self.page_size = self.default_page_size
|
|
2302
|
+
|
|
2303
|
+
def applycolor(self, text, color=None):
|
|
2304
|
+
if not color or not self.color:
|
|
2305
|
+
return text
|
|
2306
|
+
return color + text + ANSI_RESET
|
|
2307
|
+
|
|
2308
|
+
def writeresult(self, text, color=None, newline=True, out=None):
|
|
2309
|
+
if out is None:
|
|
2310
|
+
out = self.query_out
|
|
2311
|
+
|
|
2312
|
+
# convert Exceptions, etc to text
|
|
2313
|
+
if not isinstance(text, str):
|
|
2314
|
+
text = str(text)
|
|
2315
|
+
|
|
2316
|
+
to_write = self.applycolor(text, color) + ('\n' if newline else '')
|
|
2317
|
+
out.write(to_write)
|
|
2318
|
+
|
|
2319
|
+
def flush_output(self):
|
|
2320
|
+
self.query_out.flush()
|
|
2321
|
+
|
|
2322
|
+
def printerr(self, text, color=RED, newline=True, shownum=None):
|
|
2323
|
+
self.statement_error = True
|
|
2324
|
+
if shownum is None:
|
|
2325
|
+
shownum = self.show_line_nums
|
|
2326
|
+
if shownum:
|
|
2327
|
+
text = '%s:%d:%s' % (self.stdin.name, self.lineno, text)
|
|
2328
|
+
self.writeresult(text, color, newline=newline, out=sys.stderr)
|
|
2329
|
+
|
|
2330
|
+
def printwarn(self, text, color=YELLOW, newline=True, shownum=None):
|
|
2331
|
+
if shownum is None:
|
|
2332
|
+
shownum = self.show_line_nums
|
|
2333
|
+
if shownum:
|
|
2334
|
+
text = '%s:%d:%s' % (self.stdin.name, self.lineno, text)
|
|
2335
|
+
self.writeresult(text, color, newline=newline, out=sys.stderr)
|
|
2336
|
+
|
|
2337
|
+
def stop_coverage(self):
|
|
2338
|
+
if self.coverage and self.cov is not None:
|
|
2339
|
+
self.cov.stop()
|
|
2340
|
+
self.cov.save()
|
|
2341
|
+
self.cov = None
|
|
2342
|
+
|
|
2343
|
+
|
|
2344
|
+
class SwitchCommand(object):
|
|
2345
|
+
command = None
|
|
2346
|
+
description = None
|
|
2347
|
+
|
|
2348
|
+
def __init__(self, command, desc):
|
|
2349
|
+
self.command = command
|
|
2350
|
+
self.description = desc
|
|
2351
|
+
|
|
2352
|
+
def execute(self, state, parsed, printerr):
|
|
2353
|
+
switch = parsed.get_binding('switch')
|
|
2354
|
+
if switch is None:
|
|
2355
|
+
if state:
|
|
2356
|
+
print("%s is currently enabled. Use %s OFF to disable"
|
|
2357
|
+
% (self.description, self.command))
|
|
2358
|
+
else:
|
|
2359
|
+
print("%s is currently disabled. Use %s ON to enable."
|
|
2360
|
+
% (self.description, self.command))
|
|
2361
|
+
return state
|
|
2362
|
+
|
|
2363
|
+
if switch.upper() == 'ON':
|
|
2364
|
+
if state:
|
|
2365
|
+
printerr('%s is already enabled. Use %s OFF to disable.'
|
|
2366
|
+
% (self.description, self.command))
|
|
2367
|
+
return state
|
|
2368
|
+
print('Now %s is enabled' % (self.description,))
|
|
2369
|
+
return True
|
|
2370
|
+
|
|
2371
|
+
if switch.upper() == 'OFF':
|
|
2372
|
+
if not state:
|
|
2373
|
+
printerr('%s is not enabled.' % (self.description,))
|
|
2374
|
+
return state
|
|
2375
|
+
print('Disabled %s.' % (self.description,))
|
|
2376
|
+
return False
|
|
2377
|
+
|
|
2378
|
+
|
|
2379
|
+
class SwitchCommandWithValue(SwitchCommand):
|
|
2380
|
+
"""The same as SwitchCommand except it also accepts a value in place of ON.
|
|
2381
|
+
|
|
2382
|
+
This returns a tuple of the form: (SWITCH_VALUE, PASSED_VALUE)
|
|
2383
|
+
eg: PAGING 50 returns (True, 50)
|
|
2384
|
+
PAGING OFF returns (False, None)
|
|
2385
|
+
PAGING ON returns (True, None)
|
|
2386
|
+
|
|
2387
|
+
The value_type must match for the PASSED_VALUE, otherwise it will return None.
|
|
2388
|
+
"""
|
|
2389
|
+
def __init__(self, command, desc, value_type=int):
|
|
2390
|
+
SwitchCommand.__init__(self, command, desc)
|
|
2391
|
+
self.value_type = value_type
|
|
2392
|
+
|
|
2393
|
+
def execute(self, state, parsed, printerr):
|
|
2394
|
+
binary_switch_value = SwitchCommand.execute(self, state, parsed, printerr)
|
|
2395
|
+
switch = parsed.get_binding('switch')
|
|
2396
|
+
try:
|
|
2397
|
+
value = self.value_type(switch)
|
|
2398
|
+
binary_switch_value = True
|
|
2399
|
+
except (ValueError, TypeError):
|
|
2400
|
+
value = None
|
|
2401
|
+
return binary_switch_value, value
|
|
2402
|
+
|
|
2403
|
+
|
|
2404
|
+
def option_with_default(cparser_getter, section, option, default=None):
|
|
2405
|
+
try:
|
|
2406
|
+
return cparser_getter(section, option)
|
|
2407
|
+
except configparser.Error:
|
|
2408
|
+
return default
|
|
2409
|
+
|
|
2410
|
+
|
|
2411
|
+
def raw_option_with_default(configs, section, option, default=None):
|
|
2412
|
+
"""
|
|
2413
|
+
Same (almost) as option_with_default() but won't do any string interpolation.
|
|
2414
|
+
Useful for config values that include '%' symbol, e.g. time format string.
|
|
2415
|
+
"""
|
|
2416
|
+
try:
|
|
2417
|
+
return configs.get(section, option, raw=True)
|
|
2418
|
+
except configparser.Error:
|
|
2419
|
+
return default
|
|
2420
|
+
|
|
2421
|
+
|
|
2422
|
+
def should_use_color():
|
|
2423
|
+
if not sys.stdout.isatty():
|
|
2424
|
+
return False
|
|
2425
|
+
if os.environ.get('TERM', '') in ('dumb', ''):
|
|
2426
|
+
return False
|
|
2427
|
+
try:
|
|
2428
|
+
p = subprocess.Popen(['tput', 'colors'], stdout=subprocess.PIPE)
|
|
2429
|
+
stdout, _ = p.communicate()
|
|
2430
|
+
if int(stdout.strip()) < 8:
|
|
2431
|
+
return False
|
|
2432
|
+
except (OSError, ImportError, ValueError):
|
|
2433
|
+
# oh well, we tried. at least we know there's a $TERM and it's
|
|
2434
|
+
# not "dumb".
|
|
2435
|
+
pass
|
|
2436
|
+
return True
|
|
2437
|
+
|
|
2438
|
+
|
|
2439
|
+
def read_options(cmdlineargs, environment):
|
|
2440
|
+
configs = configparser.ConfigParser()
|
|
2441
|
+
configs.read(CONFIG_FILE)
|
|
2442
|
+
|
|
2443
|
+
rawconfigs = configparser.RawConfigParser()
|
|
2444
|
+
rawconfigs.read(CONFIG_FILE)
|
|
2445
|
+
|
|
2446
|
+
username_from_cqlshrc = option_with_default(configs.get, 'authentication', 'username')
|
|
2447
|
+
password_from_cqlshrc = option_with_default(rawconfigs.get, 'authentication', 'password')
|
|
2448
|
+
if username_from_cqlshrc or password_from_cqlshrc:
|
|
2449
|
+
if password_from_cqlshrc and not is_file_secure(os.path.expanduser(CONFIG_FILE)):
|
|
2450
|
+
print("\nWarning: Password is found in an insecure cqlshrc file. The file is owned or readable by other users on the system.",
|
|
2451
|
+
end='', file=sys.stderr)
|
|
2452
|
+
print("\nNotice: Credentials in the cqlshrc file is deprecated and will be ignored in the future."
|
|
2453
|
+
"\nPlease use a credentials file to specify the username and password.\n"
|
|
2454
|
+
"\nTo use basic authentication, place the username and password in the [PlainTextAuthProvider] section of the credentials file.\n", file=sys.stderr)
|
|
2455
|
+
|
|
2456
|
+
optvalues = optparse.Values()
|
|
2457
|
+
|
|
2458
|
+
optvalues.username = None
|
|
2459
|
+
optvalues.password = None
|
|
2460
|
+
optvalues.credentials = os.path.expanduser(option_with_default(configs.get, 'authentication', 'credentials',
|
|
2461
|
+
os.path.join(CQL_DIR, 'credentials')))
|
|
2462
|
+
optvalues.keyspace = option_with_default(configs.get, 'authentication', 'keyspace')
|
|
2463
|
+
optvalues.browser = option_with_default(configs.get, 'ui', 'browser', None)
|
|
2464
|
+
optvalues.completekey = option_with_default(configs.get, 'ui', 'completekey',
|
|
2465
|
+
DEFAULT_COMPLETEKEY)
|
|
2466
|
+
optvalues.color = option_with_default(configs.getboolean, 'ui', 'color')
|
|
2467
|
+
optvalues.time_format = raw_option_with_default(configs, 'ui', 'time_format',
|
|
2468
|
+
DEFAULT_TIMESTAMP_FORMAT)
|
|
2469
|
+
optvalues.nanotime_format = raw_option_with_default(configs, 'ui', 'nanotime_format',
|
|
2470
|
+
DEFAULT_NANOTIME_FORMAT)
|
|
2471
|
+
optvalues.date_format = raw_option_with_default(configs, 'ui', 'date_format',
|
|
2472
|
+
DEFAULT_DATE_FORMAT)
|
|
2473
|
+
optvalues.float_precision = option_with_default(configs.getint, 'ui', 'float_precision',
|
|
2474
|
+
DEFAULT_FLOAT_PRECISION)
|
|
2475
|
+
optvalues.double_precision = option_with_default(configs.getint, 'ui', 'double_precision',
|
|
2476
|
+
DEFAULT_DOUBLE_PRECISION)
|
|
2477
|
+
optvalues.field_size_limit = option_with_default(configs.getint, 'csv', 'field_size_limit', csv.field_size_limit())
|
|
2478
|
+
optvalues.max_trace_wait = option_with_default(configs.getfloat, 'tracing', 'max_trace_wait',
|
|
2479
|
+
DEFAULT_MAX_TRACE_WAIT)
|
|
2480
|
+
optvalues.timezone = option_with_default(configs.get, 'ui', 'timezone', None)
|
|
2481
|
+
|
|
2482
|
+
optvalues.debug = False
|
|
2483
|
+
optvalues.driver_debug = False
|
|
2484
|
+
optvalues.coverage = False
|
|
2485
|
+
if 'CQLSH_COVERAGE' in environment.keys():
|
|
2486
|
+
optvalues.coverage = True
|
|
2487
|
+
|
|
2488
|
+
optvalues.file = None
|
|
2489
|
+
optvalues.ssl = option_with_default(configs.getboolean, 'connection', 'ssl', DEFAULT_SSL)
|
|
2490
|
+
optvalues.no_compression = option_with_default(configs.getboolean, 'connection', 'no_compression', False)
|
|
2491
|
+
optvalues.encoding = option_with_default(configs.get, 'ui', 'encoding', UTF8)
|
|
2492
|
+
|
|
2493
|
+
optvalues.tty = option_with_default(configs.getboolean, 'ui', 'tty', sys.stdin.isatty())
|
|
2494
|
+
optvalues.protocol_version = option_with_default(configs.getint, 'protocol', 'version', 4)
|
|
2495
|
+
optvalues.cqlversion = option_with_default(configs.get, 'cql', 'version', None)
|
|
2496
|
+
optvalues.connect_timeout = option_with_default(configs.getint, 'connection', 'timeout', DEFAULT_CONNECT_TIMEOUT_SECONDS)
|
|
2497
|
+
optvalues.request_timeout = option_with_default(configs.getint, 'connection', 'request_timeout', DEFAULT_REQUEST_TIMEOUT_SECONDS)
|
|
2498
|
+
optvalues.execute = None
|
|
2499
|
+
optvalues.insecure_password_without_warning = False
|
|
2500
|
+
|
|
2501
|
+
(options, arguments) = parser.parse_args(cmdlineargs, values=optvalues)
|
|
2502
|
+
|
|
2503
|
+
# Credentials from cqlshrc will be expanded,
|
|
2504
|
+
# credentials from the command line are also expanded if there is a space...
|
|
2505
|
+
# we need the following so that these two scenarios will work
|
|
2506
|
+
# cqlsh --credentials=~/.cassandra/creds
|
|
2507
|
+
# cqlsh --credentials ~/.cassandra/creds
|
|
2508
|
+
options.credentials = os.path.expanduser(options.credentials)
|
|
2509
|
+
|
|
2510
|
+
if not is_file_secure(options.credentials):
|
|
2511
|
+
print("\nWarning: Credentials file '{0}' exists but is not used, because:"
|
|
2512
|
+
"\n a. the file owner is not the current user; or"
|
|
2513
|
+
"\n b. the file is readable by group or other."
|
|
2514
|
+
"\nPlease ensure the file is owned by the current user and is not readable by group or other."
|
|
2515
|
+
"\nOn a Linux or UNIX-like system, you often can do this by using the `chown` and `chmod` commands:"
|
|
2516
|
+
"\n chown YOUR_USERNAME credentials"
|
|
2517
|
+
"\n chmod 600 credentials\n".format(options.credentials),
|
|
2518
|
+
file=sys.stderr)
|
|
2519
|
+
options.credentials = '' # ConfigParser.read() will ignore unreadable files
|
|
2520
|
+
|
|
2521
|
+
if not options.username:
|
|
2522
|
+
credentials = configparser.ConfigParser()
|
|
2523
|
+
credentials.read(options.credentials)
|
|
2524
|
+
|
|
2525
|
+
# use the username from credentials file but fallback to cqlshrc if username is absent from the command line parameters
|
|
2526
|
+
options.username = option_with_default(credentials.get, 'plain_text_auth', 'username', username_from_cqlshrc)
|
|
2527
|
+
|
|
2528
|
+
if not options.password:
|
|
2529
|
+
rawcredentials = configparser.RawConfigParser()
|
|
2530
|
+
rawcredentials.read(options.credentials)
|
|
2531
|
+
|
|
2532
|
+
# handling password in the same way as username, priority cli > credentials > cqlshrc
|
|
2533
|
+
options.password = option_with_default(rawcredentials.get, 'plain_text_auth', 'password', password_from_cqlshrc)
|
|
2534
|
+
elif not options.insecure_password_without_warning:
|
|
2535
|
+
print("\nWarning: Using a password on the command line interface can be insecure."
|
|
2536
|
+
"\nRecommendation: use the credentials file to securely provide the password.\n", file=sys.stderr)
|
|
2537
|
+
|
|
2538
|
+
# Make sure some user values read from the command line are in unicode
|
|
2539
|
+
options.execute = maybe_ensure_text(options.execute)
|
|
2540
|
+
options.username = maybe_ensure_text(options.username)
|
|
2541
|
+
options.password = maybe_ensure_text(options.password)
|
|
2542
|
+
options.keyspace = maybe_ensure_text(options.keyspace)
|
|
2543
|
+
|
|
2544
|
+
hostname = option_with_default(configs.get, 'connection', 'hostname', DEFAULT_HOST)
|
|
2545
|
+
port = option_with_default(configs.get, 'connection', 'port', DEFAULT_PORT)
|
|
2546
|
+
|
|
2547
|
+
try:
|
|
2548
|
+
options.connect_timeout = int(options.connect_timeout)
|
|
2549
|
+
except ValueError:
|
|
2550
|
+
parser.error('"%s" is not a valid connect timeout.' % (options.connect_timeout,))
|
|
2551
|
+
options.connect_timeout = DEFAULT_CONNECT_TIMEOUT_SECONDS
|
|
2552
|
+
|
|
2553
|
+
try:
|
|
2554
|
+
options.request_timeout = int(options.request_timeout)
|
|
2555
|
+
except ValueError:
|
|
2556
|
+
parser.error('"%s" is not a valid request timeout.' % (options.request_timeout,))
|
|
2557
|
+
options.request_timeout = DEFAULT_REQUEST_TIMEOUT_SECONDS
|
|
2558
|
+
|
|
2559
|
+
hostname = environment.get('CQLSH_HOST', hostname)
|
|
2560
|
+
port = environment.get('CQLSH_PORT', port)
|
|
2561
|
+
|
|
2562
|
+
if len(arguments) > 0:
|
|
2563
|
+
hostname = arguments[0]
|
|
2564
|
+
if len(arguments) > 1:
|
|
2565
|
+
port = arguments[1]
|
|
2566
|
+
|
|
2567
|
+
if options.file or options.execute:
|
|
2568
|
+
options.tty = False
|
|
2569
|
+
|
|
2570
|
+
if options.execute and not options.execute.endswith(';'):
|
|
2571
|
+
options.execute += ';'
|
|
2572
|
+
|
|
2573
|
+
if optvalues.color in (True, False):
|
|
2574
|
+
options.color = optvalues.color
|
|
2575
|
+
else:
|
|
2576
|
+
if options.file is not None:
|
|
2577
|
+
options.color = False
|
|
2578
|
+
else:
|
|
2579
|
+
options.color = should_use_color()
|
|
2580
|
+
|
|
2581
|
+
if options.cqlversion is not None:
|
|
2582
|
+
options.cqlversion, cqlvertup = full_cql_version(options.cqlversion)
|
|
2583
|
+
if cqlvertup[0] < 3:
|
|
2584
|
+
parser.error('%r is not a supported CQL version.' % options.cqlversion)
|
|
2585
|
+
options.cqlmodule = cql3handling
|
|
2586
|
+
|
|
2587
|
+
try:
|
|
2588
|
+
port = int(port)
|
|
2589
|
+
except ValueError:
|
|
2590
|
+
parser.error('%r is not a valid port number.' % port)
|
|
2591
|
+
return options, hostname, port
|
|
2592
|
+
|
|
2593
|
+
|
|
2594
|
+
def setup_cqlruleset(cqlmodule):
|
|
2595
|
+
global cqlruleset
|
|
2596
|
+
cqlruleset = cqlmodule.CqlRuleSet
|
|
2597
|
+
cqlruleset.append_rules(cqlshhandling.cqlsh_extra_syntax_rules)
|
|
2598
|
+
for rulename, termname, func in cqlshhandling.cqlsh_syntax_completers:
|
|
2599
|
+
cqlruleset.completer_for(rulename, termname)(func)
|
|
2600
|
+
cqlruleset.commands_end_with_newline.update(cqlshhandling.my_commands_ending_with_newline)
|
|
2601
|
+
|
|
2602
|
+
|
|
2603
|
+
def setup_cqldocs(cqlmodule):
|
|
2604
|
+
global cqldocs
|
|
2605
|
+
cqldocs = cqlmodule.cqldocs
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
def init_history():
|
|
2609
|
+
if readline is not None:
|
|
2610
|
+
try:
|
|
2611
|
+
readline.read_history_file(HISTORY)
|
|
2612
|
+
except IOError:
|
|
2613
|
+
pass
|
|
2614
|
+
delims = readline.get_completer_delims()
|
|
2615
|
+
delims.replace("'", "")
|
|
2616
|
+
delims += '.'
|
|
2617
|
+
readline.set_completer_delims(delims)
|
|
2618
|
+
|
|
2619
|
+
|
|
2620
|
+
def save_history():
|
|
2621
|
+
if readline is not None:
|
|
2622
|
+
try:
|
|
2623
|
+
readline.write_history_file(HISTORY)
|
|
2624
|
+
except IOError:
|
|
2625
|
+
pass
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
def main(options, hostname, port):
|
|
2629
|
+
if options.driver_debug:
|
|
2630
|
+
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
2631
|
+
stream=sys.stdout)
|
|
2632
|
+
logging.getLogger("cassandra").setLevel(logging.DEBUG)
|
|
2633
|
+
|
|
2634
|
+
setup_cqlruleset(options.cqlmodule)
|
|
2635
|
+
setup_cqldocs(options.cqlmodule)
|
|
2636
|
+
init_history()
|
|
2637
|
+
csv.field_size_limit(options.field_size_limit)
|
|
2638
|
+
|
|
2639
|
+
if options.file is None:
|
|
2640
|
+
stdin = None
|
|
2641
|
+
else:
|
|
2642
|
+
try:
|
|
2643
|
+
encoding, bom_size = get_file_encoding_bomsize(options.file)
|
|
2644
|
+
stdin = codecs.open(options.file, 'r', encoding)
|
|
2645
|
+
stdin.seek(bom_size)
|
|
2646
|
+
except IOError as e:
|
|
2647
|
+
sys.exit("Can't open %r: %s" % (options.file, e))
|
|
2648
|
+
|
|
2649
|
+
if options.debug:
|
|
2650
|
+
sys.stderr.write("Using CQL driver: %s\n" % (cassandra,))
|
|
2651
|
+
sys.stderr.write("Using connect timeout: %s seconds\n" % (options.connect_timeout,))
|
|
2652
|
+
sys.stderr.write("Using '%s' encoding\n" % (options.encoding,))
|
|
2653
|
+
sys.stderr.write("Using ssl: %s\n" % (options.ssl,))
|
|
2654
|
+
sys.stderr.write("Using compression: %s\n" % (not options.no_compression,))
|
|
2655
|
+
|
|
2656
|
+
# create timezone based on settings, environment or auto-detection
|
|
2657
|
+
timezone = None
|
|
2658
|
+
if options.timezone or 'TZ' in os.environ:
|
|
2659
|
+
try:
|
|
2660
|
+
import pytz
|
|
2661
|
+
if options.timezone:
|
|
2662
|
+
try:
|
|
2663
|
+
timezone = pytz.timezone(options.timezone)
|
|
2664
|
+
except Exception:
|
|
2665
|
+
sys.stderr.write("Warning: could not recognize timezone '%s' specified in cqlshrc\n\n" % (options.timezone))
|
|
2666
|
+
if 'TZ' in os.environ:
|
|
2667
|
+
try:
|
|
2668
|
+
timezone = pytz.timezone(os.environ['TZ'])
|
|
2669
|
+
except Exception:
|
|
2670
|
+
sys.stderr.write("Warning: could not recognize timezone '%s' from environment value TZ\n\n" % (os.environ['TZ']))
|
|
2671
|
+
except ImportError:
|
|
2672
|
+
sys.stderr.write("Warning: Timezone defined and 'pytz' module for timezone conversion not installed. Timestamps will be displayed in UTC timezone.\n\n")
|
|
2673
|
+
|
|
2674
|
+
# try auto-detect timezone if tzlocal is installed
|
|
2675
|
+
if not timezone:
|
|
2676
|
+
try:
|
|
2677
|
+
from tzlocal import get_localzone
|
|
2678
|
+
timezone = get_localzone()
|
|
2679
|
+
except ImportError:
|
|
2680
|
+
# we silently ignore and fallback to UTC unless a custom timestamp format (which likely
|
|
2681
|
+
# does contain a TZ part) was specified
|
|
2682
|
+
if options.time_format != DEFAULT_TIMESTAMP_FORMAT:
|
|
2683
|
+
sys.stderr.write("Warning: custom timestamp format specified in cqlshrc, "
|
|
2684
|
+
+ "but local timezone could not be detected.\n"
|
|
2685
|
+
+ "Either install Python 'tzlocal' module for auto-detection "
|
|
2686
|
+
+ "or specify client timezone in your cqlshrc.\n\n")
|
|
2687
|
+
|
|
2688
|
+
try:
|
|
2689
|
+
shell = Shell(hostname,
|
|
2690
|
+
port,
|
|
2691
|
+
color=options.color,
|
|
2692
|
+
username=options.username,
|
|
2693
|
+
stdin=stdin,
|
|
2694
|
+
tty=options.tty,
|
|
2695
|
+
completekey=options.completekey,
|
|
2696
|
+
browser=options.browser,
|
|
2697
|
+
protocol_version=options.protocol_version,
|
|
2698
|
+
cqlver=options.cqlversion,
|
|
2699
|
+
keyspace=options.keyspace,
|
|
2700
|
+
display_timestamp_format=options.time_format,
|
|
2701
|
+
display_nanotime_format=options.nanotime_format,
|
|
2702
|
+
display_date_format=options.date_format,
|
|
2703
|
+
display_float_precision=options.float_precision,
|
|
2704
|
+
display_double_precision=options.double_precision,
|
|
2705
|
+
display_timezone=timezone,
|
|
2706
|
+
max_trace_wait=options.max_trace_wait,
|
|
2707
|
+
ssl=options.ssl,
|
|
2708
|
+
single_statement=options.execute,
|
|
2709
|
+
request_timeout=options.request_timeout,
|
|
2710
|
+
connect_timeout=options.connect_timeout,
|
|
2711
|
+
encoding=options.encoding,
|
|
2712
|
+
auth_provider=authproviderhandling.load_auth_provider(
|
|
2713
|
+
config_file=CONFIG_FILE,
|
|
2714
|
+
cred_file=options.credentials,
|
|
2715
|
+
username=options.username,
|
|
2716
|
+
password=options.password),
|
|
2717
|
+
no_compression=options.no_compression,
|
|
2718
|
+
)
|
|
2719
|
+
except KeyboardInterrupt:
|
|
2720
|
+
sys.exit('Connection aborted.')
|
|
2721
|
+
except CQL_ERRORS as e:
|
|
2722
|
+
sys.exit('Connection error: %s' % (e,))
|
|
2723
|
+
except VersionNotSupported as e:
|
|
2724
|
+
sys.exit('Unsupported CQL version: %s' % (e,))
|
|
2725
|
+
if options.debug:
|
|
2726
|
+
shell.debug = True
|
|
2727
|
+
if options.coverage:
|
|
2728
|
+
shell.coverage = True
|
|
2729
|
+
import signal
|
|
2730
|
+
|
|
2731
|
+
def handle_sighup():
|
|
2732
|
+
shell.stop_coverage()
|
|
2733
|
+
shell.do_exit()
|
|
2734
|
+
|
|
2735
|
+
signal.signal(signal.SIGHUP, handle_sighup)
|
|
2736
|
+
|
|
2737
|
+
shell.cmdloop()
|
|
2738
|
+
save_history()
|
|
2739
|
+
|
|
2740
|
+
if shell.batch_mode and shell.statement_error:
|
|
2741
|
+
sys.exit(2)
|
|
2742
|
+
|
|
2743
|
+
|
|
2744
|
+
# always call this regardless of module name: when a sub-process is spawned
|
|
2745
|
+
# on Windows then the module name is not __main__, see CASSANDRA-9304 (Windows support was dropped in CASSANDRA-16956)
|
|
2746
|
+
insert_driver_hooks()
|
|
2747
|
+
|
|
2748
|
+
if __name__ == '__main__':
|
|
2749
|
+
main(*read_options(sys.argv[1:], os.environ))
|
|
2750
|
+
|
|
2751
|
+
# vim: set ft=python et ts=4 sw=4 :
|