/**
* 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.tools;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.io.util.BufferedRandomAccessFile;
import org.apache.cassandra.service.StorageService;
import org.apache.commons.cli.*;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.sstable.*;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.utils.ByteBufferUtil.bytesToHex;
import static org.apache.cassandra.utils.ByteBufferUtil.hexToBytes;
/**
* Export SSTables to JSON format.
*/
public class SSTableExport
{
// size of the columns page
private static final int PAGE_SIZE = 1000;
private static final String KEY_OPTION = "k";
private static final String EXCLUDEKEY_OPTION = "x";
private static final String ENUMERATEKEYS_OPTION = "e";
private static Options options;
private static CommandLine cmd;
static
{
options = new Options();
Option optKey = new Option(KEY_OPTION, true, "Row key");
// Number of times -k <key> can be passed on the command line.
optKey.setArgs(500);
options.addOption(optKey);
Option excludeKey = new Option(EXCLUDEKEY_OPTION, true, "Excluded row key");
// Number of times -x <key> can be passed on the command line.
excludeKey.setArgs(500);
options.addOption(excludeKey);
Option optEnumerate = new Option(ENUMERATEKEYS_OPTION, false, "enumerate keys only");
options.addOption(optEnumerate);
}
/**
* Wraps given string into quotes
* @param val string to quote
* @return quoted string
*/
private static String quote(String val)
{
return String.format("\"%s\"", val);
}
/**
* JSON Hash Key serializer
* @param val value to set as a key
* @return JSON Hash key
*/
private static String asKey(String val)
{
return String.format("%s: ", quote(val));
}
/**
* Serialize columns using given column iterator
* @param columns column iterator
* @param out output stream
* @return pair of (number of columns serialized, last column serialized)
*/
private static void serializeColumns(Iterator<IColumn> columns, PrintStream out)
{
while (columns.hasNext())
{
IColumn column = columns.next();
serializeColumn(column, out);
if (columns.hasNext())
out.print(", ");
}
}
/**
* Serialize a given column to the JSON format
* @param column column presentation
* @param out output stream
*/
private static void serializeColumn(IColumn column, PrintStream out)
{
out.print("[");
out.print(quote(bytesToHex(column.name())));
out.print(", ");
out.print(quote(bytesToHex(column.value())));
out.print(", ");
out.print(column.timestamp());
out.print(", ");
out.print(column.isMarkedForDelete());
if (column instanceof ExpiringColumn)
{
out.print(", ");
out.print(((ExpiringColumn) column).getTimeToLive());
out.print(", ");
out.print(column.getLocalDeletionTime());
}
out.print("]");
}
/**
* Get portion of the columns and serialize in loop while not more columns left in the row
* @param row SSTableIdentityIterator row representation with Column Family
* @param key Decorated Key for the required row
* @param out output stream
*/
private static void serializeRow(SSTableIdentityIterator row, DecoratedKey key, PrintStream out)
{
ColumnFamily columnFamily = row.getColumnFamily();
boolean isSuperCF = columnFamily.isSuper();
out.print(asKey(bytesToHex(key.key)));
out.print(isSuperCF ? "{" : "[");
if (isSuperCF)
{
while (row.hasNext())
{
IColumn column = row.next();
out.print(asKey(bytesToHex(column.name())));
out.print("{");
out.print(asKey("deletedAt"));
out.print(column.getMarkedForDeleteAt());
out.print(", ");
out.print(asKey("subColumns"));
out.print("[");
serializeColumns(column.getSubColumns().iterator(), out);
out.print("]");
out.print("}");
if (row.hasNext())
out.print(", ");
}
}
else
{
serializeColumns(row, out);
}
out.print(isSuperCF ? "}" : "]");
}
/**
* Enumerate row keys from an SSTableReader and write the result to a PrintStream.
*
* @param ssTableFile the file to export the rows from
* @param outs PrintStream to write the output to
* @throws IOException on failure to read/write input/output
*/
public static void enumeratekeys(String ssTableFile, PrintStream outs)
throws IOException
{
Descriptor desc = Descriptor.fromFilename(ssTableFile);
KeyIterator iter = new KeyIterator(desc);
DecoratedKey lastKey = null;
while (iter.hasNext())
{
DecoratedKey key = iter.next();
// validate order of the keys in the sstable
if (lastKey != null && lastKey.compareTo(key) > 0 )
throw new IOException("Key out of order! " + lastKey + " > " + key);
lastKey = key;
outs.println(bytesToHex(key.key));
}
iter.close();
outs.flush();
}
/**
* Export specific rows from an SSTable and write the resulting JSON to a PrintStream.
*
* @param ssTableFile the SSTableScanner to export the rows from
* @param outs PrintStream to write the output to
* @param toExport the keys corresponding to the rows to export
* @param excludes keys to exclude from export
* @throws IOException on failure to read/write input/output
*/
public static void export(String ssTableFile, PrintStream outs, Collection<String> toExport, String[] excludes) throws IOException
{
SSTableReader reader = SSTableReader.open(Descriptor.fromFilename(ssTableFile));
SSTableScanner scanner = reader.getDirectScanner(BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE);
IPartitioner<?> partitioner = StorageService.getPartitioner();
if (excludes != null)
toExport.removeAll(Arrays.asList(excludes));
outs.println("{");
int i = 0;
// last key to compare order
DecoratedKey lastKey = null;
for (String key : toExport)
{
DecoratedKey decoratedKey = partitioner.decorateKey(hexToBytes(key));
if (lastKey != null && lastKey.compareTo(decoratedKey) > 0)
throw new IOException("Key out of order! " + lastKey + " > " + decoratedKey);
lastKey = decoratedKey;
scanner.seekTo(decoratedKey);
if (!scanner.hasNext())
continue;
SSTableIdentityIterator row = (SSTableIdentityIterator) scanner.next();
if (!row.getKey().equals(decoratedKey))
continue;
serializeRow(row, decoratedKey, outs);
if (i != 0)
outs.println(",");
i++;
}
outs.println("\n}");
outs.flush();
scanner.close();
}
// This is necessary to accommodate the test suite since you cannot open a Reader more
// than once from within the same process.
static void export(SSTableReader reader, PrintStream outs, String[] excludes) throws IOException
{
Set<String> excludeSet = new HashSet<String>();
if (excludes != null)
excludeSet = new HashSet<String>(Arrays.asList(excludes));
SSTableIdentityIterator row;
SSTableScanner scanner = reader.getDirectScanner(BufferedRandomAccessFile.DEFAULT_BUFFER_SIZE);
outs.println("{");
int i = 0;
// collecting keys to export
while (scanner.hasNext())
{
row = (SSTableIdentityIterator) scanner.next();
String currentKey = bytesToHex(row.getKey().key);
if (excludeSet.contains(currentKey))
continue;
else if (i != 0)
outs.println(",");
serializeRow(row, row.getKey(), outs);
i++;
}
outs.println("\n}");
outs.flush();
scanner.close();
}
/**
* Export an SSTable and write the resulting JSON to a PrintStream.
*
* @param ssTableFile the SSTable to export
* @param outs PrintStream to write the output to
* @param excludes keys to exclude from export
*
* @throws IOException on failure to read/write input/output
*/
public static void export(String ssTableFile, PrintStream outs, String[] excludes) throws IOException
{
export(SSTableReader.open(Descriptor.fromFilename(ssTableFile)), outs, excludes);
}
/**
* Export an SSTable and write the resulting JSON to standard out.
*
* @param ssTableFile SSTable to export
* @param excludes keys to exclude from export
*
* @throws IOException on failure to read/write SSTable/standard out
*/
public static void export(String ssTableFile, String[] excludes) throws IOException
{
export(ssTableFile, System.out, excludes);
}
/**
* Given arguments specifying an SSTable, and optionally an output file,
* export the contents of the SSTable to JSON.
*
* @param args command lines arguments
*
* @throws IOException on failure to open/read/write files or output streams
* @throws ConfigurationException on configuration failure (wrong params given)
*/
public static void main(String[] args) throws IOException, ConfigurationException
{
String usage = String.format("Usage: %s <sstable> [-k key [-k key [...]] -x key [-x key [...]]]%n", SSTableExport.class.getName());
CommandLineParser parser = new PosixParser();
try
{
cmd = parser.parse(options, args);
}
catch (ParseException e1)
{
System.err.println(e1.getMessage());
System.err.println(usage);
System.exit(1);
}
if (cmd.getArgs().length != 1)
{
System.err.println("You must supply exactly one sstable");
System.err.println(usage);
System.exit(1);
}
String[] keys = cmd.getOptionValues(KEY_OPTION);
String[] excludes = cmd.getOptionValues(EXCLUDEKEY_OPTION);
String ssTableFileName = new File(cmd.getArgs()[0]).getAbsolutePath();
DatabaseDescriptor.loadSchemas();
if (DatabaseDescriptor.getNonSystemTables().size() < 1)
{
String msg = "no non-system tables are defined";
System.err.println(msg);
throw new ConfigurationException(msg);
}
if (cmd.hasOption(ENUMERATEKEYS_OPTION))
{
enumeratekeys(ssTableFileName, System.out);
}
else
{
if ((keys != null) && (keys.length > 0))
export(ssTableFileName, System.out, Arrays.asList(keys), excludes);
else
export(ssTableFileName, excludes);
}
System.exit(0);
}
}