/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cassandra.cli; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.ArrayUtils; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.Tree; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.thrift.*; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.thrift.TException; import static org.apache.cassandra.thrift.ThriftGlue.*; // Cli Client Side Library public class CliClient { private Cassandra.Client thriftClient_ = null; private CliSessionState css_ = null; private Map<String, Map<String, Map<String, String>>> keyspacesMap = new HashMap<String, Map<String,Map<String,String>>>(); public CliClient(CliSessionState css, Cassandra.Client thriftClient) { css_ = css; thriftClient_ = thriftClient; } // Execute a CLI Statement public void executeCLIStmt(String stmt) throws TException, NotFoundException, InvalidRequestException, UnavailableException, TimedOutException, IllegalAccessException, ClassNotFoundException, InstantiationException { CommonTree ast = null; ast = CliCompiler.compileQuery(stmt); try { switch (ast.getType()) { case CliParser.NODE_EXIT: cleanupAndExit(); break; case CliParser.NODE_THRIFT_GET: executeGet(ast); break; case CliParser.NODE_HELP: printCmdHelp(); break; case CliParser.NODE_THRIFT_SET: executeSet(ast); break; case CliParser.NODE_THRIFT_DEL: executeDelete(ast); break; case CliParser.NODE_THRIFT_COUNT: executeCount(ast); break; case CliParser.NODE_SHOW_CLUSTER_NAME: executeShowProperty(ast, "cluster name"); break; case CliParser.NODE_SHOW_CONFIG_FILE: executeShowProperty(ast, "config file"); break; case CliParser.NODE_SHOW_VERSION: executeShowProperty(ast, "version"); break; case CliParser.NODE_SHOW_TABLES: executeShowTables(ast); break; case CliParser.NODE_DESCRIBE_TABLE: executeDescribeTable(ast); break; case CliParser.NODE_CONNECT: executeConnect(ast); break; case CliParser.NODE_NO_OP: // comment lines come here; they are treated as no ops. break; default: css_.err.println("Invalid Statement (Type: " + ast.getType() + ")"); break; } } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unable to encode string as UTF-8", e); } } private void printCmdHelp() { css_.out.println("List of all CLI commands:"); css_.out.println("? Same as help."); css_.out.println("help Display this help."); css_.out.println("connect <hostname>/<port> Connect to thrift service."); css_.out.println("describe keyspace <keyspacename> Describe keyspace."); css_.out.println("exit Exit CLI."); css_.out.println("quit Exit CLI."); css_.out.println("show config file Display contents of config file."); css_.out.println("show cluster name Display cluster name."); css_.out.println("show keyspaces Show list of keyspaces."); css_.out.println("show api version Show server API version."); css_.out.println("get <ksp>.<cf>['<key>'] Get a slice of columns."); css_.out.println("get <ksp>.<cf>['<key>']['<super>'] Get a slice of sub columns."); css_.out.println("get <ksp>.<cf>['<key>']['<col>'] Get a column value."); css_.out.println("get <ksp>.<cf>['<key>']['<super>']['<col>'] Get a sub column value."); css_.out.println("set <ksp>.<cf>['<key>']['<col>'] = '<value>' Set a column."); css_.out.println("set <ksp>.<cf>['<key>']['<super>']['<col>'] = '<value>' Set a sub column."); css_.out.println("del <ksp>.<cf>['<key>'] Delete record."); css_.out.println("del <ksp>.<cf>['<key>']['<col>'] Delete column."); css_.out.println("del <ksp>.<cf>['<key>']['<super>']['<col>'] Delete sub column."); css_.out.println("count <ksp>.<cf>['<key>'] Count columns in record."); css_.out.println("count <ksp>.<cf>['<key>']['<super>'] Count columns in a super column."); } private void cleanupAndExit() { CliMain.disconnect(); System.exit(0); } Map<String, Map<String, String>> getCFMetaData(String keyspace) throws NotFoundException, TException { // Lazily lookup column family meta-data. if (!(keyspacesMap.containsKey(keyspace))) keyspacesMap.put(keyspace, thriftClient_.describe_keyspace(keyspace)); return keyspacesMap.get(keyspace); } private void executeCount(CommonTree ast) throws TException, InvalidRequestException, UnavailableException, TimedOutException, UnsupportedEncodingException { if (!CliMain.isConnected()) return; int childCount = ast.getChildCount(); assert(childCount == 1); CommonTree columnFamilySpec = (CommonTree)ast.getChild(0); if (!(columnFamilySpec.getType() == CliParser.NODE_COLUMN_ACCESS)) return; String tableName = CliCompiler.getTableName(columnFamilySpec); String key = CliCompiler.getKey(columnFamilySpec); String columnFamily = CliCompiler.getColumnFamily(columnFamilySpec); int columnSpecCnt = CliCompiler.numColumnSpecifiers(columnFamilySpec); ColumnParent colParent; if (columnSpecCnt == 0) { colParent = createColumnParent(columnFamily, (byte[]) null); } else { assert (columnSpecCnt == 1); colParent = createColumnParent(columnFamily, CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8")); } int count = thriftClient_.get_count(tableName, key, colParent, ConsistencyLevel.ONE); css_.out.printf("%d columns\n", count); } private void executeDelete(CommonTree ast) throws TException, InvalidRequestException, UnavailableException, TimedOutException, UnsupportedEncodingException { if (!CliMain.isConnected()) return; int childCount = ast.getChildCount(); assert(childCount == 1); CommonTree columnFamilySpec = (CommonTree)ast.getChild(0); if (!(columnFamilySpec.getType() == CliParser.NODE_COLUMN_ACCESS)) return; String tableName = CliCompiler.getTableName(columnFamilySpec); String key = CliCompiler.getKey(columnFamilySpec); String columnFamily = CliCompiler.getColumnFamily(columnFamilySpec); int columnSpecCnt = CliCompiler.numColumnSpecifiers(columnFamilySpec); byte[] superColumnName = null; byte[] columnName = null; boolean isSuper; try { if (!(getCFMetaData(tableName).containsKey(columnFamily))) { css_.out.println("No such column family: " + columnFamily); return; } isSuper = getCFMetaData(tableName).get(columnFamily).get("Type").equals("Super") ? true : false; } catch (NotFoundException nfe) { css_.out.printf("No such keyspace: %s\n", tableName); return; } if ((columnSpecCnt < 0) || (columnSpecCnt > 2)) { css_.out.println("Invalid row, super column, or column specification."); return; } if (columnSpecCnt == 1) { // table.cf['key']['column'] if (isSuper) superColumnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); else columnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); } else if (columnSpecCnt == 2) { // table.cf['key']['column']['column'] superColumnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); columnName = CliCompiler.getColumn(columnFamilySpec, 1).getBytes("UTF-8"); } thriftClient_.remove(tableName, key, createColumnPath(columnFamily, superColumnName, columnName), timestampMicros(), ConsistencyLevel.ONE); css_.out.println(String.format("%s removed.", (columnSpecCnt == 0) ? "row" : "column")); } private static long timestampMicros() { // we use microsecond resolution for compatibility with other client libraries, even though // we can't actually get microsecond precision. return System.currentTimeMillis() * 1000; } private void doSlice(String keyspace, String key, String columnFamily, byte[] superColumnName) throws InvalidRequestException, UnavailableException, TimedOutException, TException, UnsupportedEncodingException, IllegalAccessException, NotFoundException, InstantiationException, ClassNotFoundException { SliceRange range = new SliceRange(ByteBufferUtil.EMPTY_BYTE_BUFFER, ByteBufferUtil.EMPTY_BYTE_BUFFER, true, 1000000); List<ColumnOrSuperColumn> columns = thriftClient_.get_slice(keyspace, key, createColumnParent(columnFamily, superColumnName), createSlicePredicate(null, range), ConsistencyLevel.ONE); int size = columns.size(); // Print out super columns or columns. for (ColumnOrSuperColumn cosc : columns) { if (cosc.isSetSuper_column()) { SuperColumn superColumn = cosc.super_column; css_.out.printf("=> (super_column=%s,", formatSuperColumnName(keyspace, columnFamily, superColumn)); for (Column col : superColumn.getColumns()) css_.out.printf("\n (column=%s, value=%s, timestamp=%d)", formatSubcolumnName(keyspace, columnFamily, col), new String(col.getValue(), "UTF-8"), col.timestamp); css_.out.println(")"); } else { Column column = cosc.column; css_.out.printf("=> (column=%s, value=%s, timestamp=%d)\n", formatColumnName(keyspace, columnFamily, column), new String(column.getValue(), "UTF-8"), column.timestamp); } } css_.out.println("Returned " + size + " results."); } private String formatSuperColumnName(String keyspace, String columnFamily, SuperColumn column) throws NotFoundException, TException, ClassNotFoundException, IllegalAccessException, InstantiationException { return getFormatTypeForColumn(getCFMetaData(keyspace).get(columnFamily).get("CompareWith")).getString(column.getName()); } private String formatSubcolumnName(String keyspace, String columnFamily, Column subcolumn) throws NotFoundException, TException, ClassNotFoundException, IllegalAccessException, InstantiationException { return getFormatTypeForColumn(getCFMetaData(keyspace).get(columnFamily).get("CompareSubcolumnsWith")).getString(subcolumn.getName()); } private String formatColumnName(String keyspace, String columnFamily, Column column) throws ClassNotFoundException, NotFoundException, TException, IllegalAccessException, InstantiationException { return getFormatTypeForColumn(getCFMetaData(keyspace).get(columnFamily).get("CompareWith")).getString(column.getName()); } private AbstractType getFormatTypeForColumn(String compareWith) throws ClassNotFoundException, IllegalAccessException, InstantiationException { AbstractType type; try { type = (AbstractType) Class.forName(compareWith).newInstance(); } catch (ClassNotFoundException e) { type = BytesType.class.newInstance(); } return type; } // Execute GET statement private void executeGet(CommonTree ast) throws TException, NotFoundException, InvalidRequestException, UnavailableException, TimedOutException, UnsupportedEncodingException, IllegalAccessException, InstantiationException, ClassNotFoundException { if (!CliMain.isConnected()) return; // This will never happen unless the grammar is broken assert (ast.getChildCount() == 1) : "serious parsing error (this is a bug)."; CommonTree columnFamilySpec = (CommonTree)ast.getChild(0); if (!(columnFamilySpec.getType() == CliParser.NODE_COLUMN_ACCESS)) return; String tableName = CliCompiler.getTableName(columnFamilySpec); String key = CliCompiler.getKey(columnFamilySpec); String columnFamily = CliCompiler.getColumnFamily(columnFamilySpec); int columnSpecCnt = CliCompiler.numColumnSpecifiers(columnFamilySpec); if (!(getCFMetaData(tableName).containsKey(columnFamily))) { css_.out.println("No such column family: " + columnFamily); return; } boolean isSuper = getCFMetaData(tableName).get(columnFamily).get("Type").equals("Super") ? true : false; byte[] superColumnName = null; byte[] columnName = null; // table.cf['key'] -- row slice if (columnSpecCnt == 0) { doSlice(tableName, key, columnFamily, superColumnName); return; } // table.cf['key']['column'] -- slice of a super, or get of a standard if (columnSpecCnt == 1) { if (isSuper) { superColumnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); doSlice(tableName, key, columnFamily, superColumnName); return; } else { columnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); } } // table.cf['key']['column']['column'] -- get of a sub-column else if (columnSpecCnt == 2) { superColumnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); columnName = CliCompiler.getColumn(columnFamilySpec, 1).getBytes("UTF-8"); } // The parser groks an arbitrary number of these so it is possible to get here. else { css_.out.println("Invalid row, super column, or column specification."); return; } // Perform a get(), print out the results. ColumnPath path = createColumnPath(columnFamily, superColumnName, columnName); Column column = thriftClient_.get(tableName, key, path, ConsistencyLevel.ONE).column; css_.out.printf("=> (column=%s, value=%s, timestamp=%d)\n", formatColumnName(tableName, columnFamily, column), new String(column.getValue(), "UTF-8"), column.timestamp); } // Execute SET statement private void executeSet(CommonTree ast) throws TException, InvalidRequestException, UnavailableException, TimedOutException, UnsupportedEncodingException { if (!CliMain.isConnected()) return; assert (ast.getChildCount() == 2) : "serious parsing error (this is a bug)."; CommonTree columnFamilySpec = (CommonTree)ast.getChild(0); if (!(columnFamilySpec.getType() == CliParser.NODE_COLUMN_ACCESS)) return; String tableName = CliCompiler.getTableName(columnFamilySpec); String key = CliCompiler.getKey(columnFamilySpec); String columnFamily = CliCompiler.getColumnFamily(columnFamilySpec); int columnSpecCnt = CliCompiler.numColumnSpecifiers(columnFamilySpec); String value = CliUtils.unescapeSQLString(ast.getChild(1).getText()); byte[] superColumnName = null; byte[] columnName = null; // table.cf['key'] if (columnSpecCnt == 0) { css_.err.println("No column name specified, (type 'help' or '?' for help on syntax)."); return; } // table.cf['key']['column'] = 'value' else if (columnSpecCnt == 1) { // get the column name columnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); } // table.cf['key']['super_column']['column'] = 'value' else { assert (columnSpecCnt == 2) : "serious parsing error (this is a bug)."; // get the super column and column names superColumnName = CliCompiler.getColumn(columnFamilySpec, 0).getBytes("UTF-8"); columnName = CliCompiler.getColumn(columnFamilySpec, 1).getBytes("UTF-8"); } // do the insert thriftClient_.insert(tableName, key, createColumnPath(columnFamily, superColumnName, columnName), ByteBuffer.wrap( value.getBytes() ), timestampMicros(), ConsistencyLevel.ONE); css_.out.println("Value inserted."); } private void executeShowProperty(CommonTree ast, String propertyName) throws TException { if (!CliMain.isConnected()) return; String propertyValue = thriftClient_.get_string_property(propertyName); css_.out.println(propertyValue); } // process "show tables" statement private void executeShowTables(CommonTree ast) throws TException { if (!CliMain.isConnected()) return; List<String> tables = thriftClient_.get_string_list_property("keyspaces"); for (String table : tables) { css_.out.println(table); } } // process a statement of the form: describe table <tablename> private void executeDescribeTable(CommonTree ast) throws TException { if (!CliMain.isConnected()) return; // Get table name int childCount = ast.getChildCount(); assert(childCount == 1); String tableName = ast.getChild(0).getText(); if( tableName == null ) { css_.out.println("Keyspace argument required"); return; } // Describe and display Map<String, Map<String, String>> columnFamiliesMap; try { columnFamiliesMap = thriftClient_.describe_keyspace(tableName); for (String columnFamilyName: columnFamiliesMap.keySet()) { Map<String, String> columnMap = columnFamiliesMap.get(columnFamilyName); String desc = columnMap.get("Desc"); String columnFamilyType = columnMap.get("Type"); String sort = columnMap.get("CompareWith"); String flushperiod = columnMap.get("FlushPeriodInMinutes"); css_.out.println(desc); css_.out.println("Column Family Type: " + columnFamilyType); css_.out.println("Column Sorted By: " + sort); css_.out.println("flush period: " + flushperiod + " minutes"); css_.out.println("------"); } } catch (NotFoundException e) { css_.out.println("Keyspace " + tableName + " could not be found."); } } // process a statement of the form: connect hostname/port private void executeConnect(CommonTree ast) { int portNumber = Integer.parseInt(ast.getChild(1).getText()); Tree idList = ast.getChild(0); StringBuilder hostName = new StringBuilder(); int idCount = idList.getChildCount(); for (int idx = 0; idx < idCount; idx++) { hostName.append(idList.getChild(idx).getText()); } // disconnect current connection, if any. // This is a no-op, if you aren't currently connected. CliMain.disconnect(); // now, connect to the newly specified host name and port css_.hostName = hostName.toString(); css_.thriftPort = portNumber; CliMain.connect(css_.hostName, css_.thriftPort); } }