Predicate pushdown support for CqlStorage

Patch by Alex Liu, reviewed by brandonwilliams for CASSANDRA-5790
This commit is contained in:
Brandon Williams 2013-07-25 10:52:26 -05:00
parent 422d2236b5
commit a3dbc3ddc7
4 changed files with 100 additions and 68 deletions

View File

@ -100,7 +100,7 @@ CqlStorage
The CqlStorage class is somewhat similar to CassandraStorage, but it can work with CQL3-defined ColumnFamilies. The main difference is in the URL format: The CqlStorage class is somewhat similar to CassandraStorage, but it can work with CQL3-defined ColumnFamilies. The main difference is in the URL format:
cql://[username:password@]<keyspace>/<columnfamily>[?[page_size=<size>][&columns=<col1,col2>][&output_query=<prepared_statement>][&where_clause=<clause>][&split_size=<size>][&partitioner=<partitioner>]] cql://[username:password@]<keyspace>/<columnfamily>[?[page_size=<size>][&columns=<col1,col2>][&output_query=<prepared_statement>][&where_clause=<clause>][&split_size=<size>][&use_secondary=true|false][&partitioner=<partitioner>]]
Which in grunt, the simplest example would look like: Which in grunt, the simplest example would look like:

View File

@ -18,7 +18,9 @@
package org.apache.cassandra.hadoop.pig; package org.apache.cassandra.hadoop.pig;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URLDecoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.util.*; import java.util.*;
@ -76,6 +78,8 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
protected String DEFAULT_INPUT_FORMAT; protected String DEFAULT_INPUT_FORMAT;
protected String DEFAULT_OUTPUT_FORMAT; protected String DEFAULT_OUTPUT_FORMAT;
public final static String PARTITION_FILTER_SIGNATURE = "cassandra.partition.filter";
protected static final Logger logger = LoggerFactory.getLogger(AbstractCassandraStorage.class); protected static final Logger logger = LoggerFactory.getLogger(AbstractCassandraStorage.class);
protected String username; protected String username;
@ -90,6 +94,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
protected String outputFormatClass; protected String outputFormatClass;
protected int splitSize = 64 * 1024; protected int splitSize = 64 * 1024;
protected String partitionerClass; protected String partitionerClass;
protected boolean usePartitionFilter = false;
public AbstractCassandraStorage() public AbstractCassandraStorage()
{ {
@ -248,15 +253,15 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
} }
} }
/** decompose the query to store the parameters in a map*/ /** decompose the query to store the parameters in a map */
public static Map<String, String> getQueryMap(String query) public static Map<String, String> getQueryMap(String query) throws UnsupportedEncodingException
{ {
String[] params = query.split("&"); String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<String, String>();
for (String param : params) for (String param : params)
{ {
String[] keyValue = param.split("="); String[] keyValue = param.split("=");
map.put(keyValue[0], keyValue[1]); map.put(keyValue[0], URLDecoder.decode(keyValue[1],"UTF-8"));
} }
return map; return map;
} }
@ -674,7 +679,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
logger.debug("Found ksDef name: {}", name); logger.debug("Found ksDef name: {}", name);
String keyString = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(0).getValue())); String keyString = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(0).getValue()));
logger.debug("partition keys: " + keyString); logger.debug("partition keys: {}", keyString);
List<String> keyNames = FBUtilities.fromJsonList(keyString); List<String> keyNames = FBUtilities.fromJsonList(keyString);
Iterator<String> iterator = keyNames.iterator(); Iterator<String> iterator = keyNames.iterator();
@ -687,7 +692,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
keyString = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(1).getValue())); keyString = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(1).getValue()));
logger.debug("cluster keys: " + keyString); logger.debug("cluster keys: {}", keyString);
keyNames = FBUtilities.fromJsonList(keyString); keyNames = FBUtilities.fromJsonList(keyString);
iterator = keyNames.iterator(); iterator = keyNames.iterator();
@ -699,7 +704,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
} }
String validator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(2).getValue())); String validator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(2).getValue()));
logger.debug("row key validator: " + validator); logger.debug("row key validator: {}", validator);
AbstractType<?> keyValidator = parseType(validator); AbstractType<?> keyValidator = parseType(validator);
Iterator<ColumnDef> keyItera = keys.iterator(); Iterator<ColumnDef> keyItera = keys.iterator();
@ -713,7 +718,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
keyItera.next().validation_class = keyValidator.toString(); keyItera.next().validation_class = keyValidator.toString();
validator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(3).getValue())); validator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(3).getValue()));
logger.debug("cluster key validator: " + validator); logger.debug("cluster key validator: {}", validator);
if (keyItera.hasNext() && validator != null && !validator.isEmpty()) if (keyItera.hasNext() && validator != null && !validator.isEmpty())
{ {
@ -735,7 +740,7 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
try try
{ {
String compactValidator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(6).getValue())); String compactValidator = ByteBufferUtil.string(ByteBuffer.wrap(cqlRow.columns.get(6).getValue()));
logger.debug("default validator: " + compactValidator); logger.debug("default validator: {}", compactValidator);
AbstractType<?> defaultValidator = parseType(compactValidator); AbstractType<?> defaultValidator = parseType(compactValidator);
ColumnDef cDef = new ColumnDef(); ColumnDef cDef = new ColumnDef();
@ -766,5 +771,32 @@ public abstract class AbstractCassandraStorage extends LoadFunc implements Store
else else
return null; return null;
} }
/** return partition keys */
public String[] getPartitionKeys(String location, Job job)
{
if (!usePartitionFilter)
return null;
List<ColumnDef> indexes = getIndexes();
String[] partitionKeys = new String[indexes.size()];
for (int i = 0; i < indexes.size(); i++)
{
partitionKeys[i] = new String(indexes.get(i).getName());
}
return partitionKeys;
}
/** get a list of columns with defined index*/
protected List<ColumnDef> getIndexes()
{
CfDef cfdef = getCfDef(loadSignature);
List<ColumnDef> indexes = new ArrayList<ColumnDef>();
for (ColumnDef cdef : cfdef.column_metadata)
{
if (cdef.index_type != null)
indexes.add(cdef);
}
return indexes;
}
} }

View File

@ -54,8 +54,6 @@ public class CassandraStorage extends AbstractCassandraStorage
public final static String PIG_WIDEROW_INPUT = "PIG_WIDEROW_INPUT"; public final static String PIG_WIDEROW_INPUT = "PIG_WIDEROW_INPUT";
public final static String PIG_USE_SECONDARY = "PIG_USE_SECONDARY"; public final static String PIG_USE_SECONDARY = "PIG_USE_SECONDARY";
private final static String PARTITION_FILTER_SIGNATURE = "cassandra.partition.filter";
private final static ByteBuffer BOUND = ByteBufferUtil.EMPTY_BYTE_BUFFER; private final static ByteBuffer BOUND = ByteBufferUtil.EMPTY_BYTE_BUFFER;
private static final Logger logger = LoggerFactory.getLogger(CassandraStorage.class); private static final Logger logger = LoggerFactory.getLogger(CassandraStorage.class);
@ -68,7 +66,6 @@ public class CassandraStorage extends AbstractCassandraStorage
private RecordWriter<ByteBuffer, List<Mutation>> writer; private RecordWriter<ByteBuffer, List<Mutation>> writer;
private boolean widerows = false; private boolean widerows = false;
private boolean usePartitionFilter = false;
private int limit; private int limit;
// wide row hacks // wide row hacks
@ -455,20 +452,6 @@ public class CassandraStorage extends AbstractCassandraStorage
return schema; return schema;
} }
/** return partition keys */
public String[] getPartitionKeys(String location, Job job)
{
if (!usePartitionFilter)
return null;
List<ColumnDef> indexes = getIndexes();
String[] partitionKeys = new String[indexes.size()];
for (int i = 0; i < indexes.size(); i++)
{
partitionKeys[i] = new String(indexes.get(i).getName());
}
return partitionKeys;
}
/** set partition filter */ /** set partition filter */
public void setPartitionFilter(Expression partitionFilter) public void setPartitionFilter(Expression partitionFilter)
{ {
@ -665,19 +648,6 @@ public class CassandraStorage extends AbstractCassandraStorage
return indexExpressions; return indexExpressions;
} }
/** get a list of columns with defined index*/
private List<ColumnDef> getIndexes()
{
CfDef cfdef = getCfDef(loadSignature);
List<ColumnDef> indexes = new ArrayList<ColumnDef>();
for (ColumnDef cdef : cfdef.column_metadata)
{
if (cdef.index_type != null)
indexes.add(cdef);
}
return indexes;
}
/** convert a list of index expression to string */ /** convert a list of index expression to string */
private static String indexExpressionsToString(List<IndexExpression> indexExpressions) private static String indexExpressionsToString(List<IndexExpression> indexExpressions)
{ {

View File

@ -32,10 +32,12 @@ import org.apache.cassandra.thrift.*;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.hadoop.mapreduce.*; import org.apache.hadoop.mapreduce.*;
import org.apache.pig.Expression; import org.apache.pig.Expression;
import org.apache.pig.Expression.OpType;
import org.apache.pig.ResourceSchema; import org.apache.pig.ResourceSchema;
import org.apache.pig.ResourceSchema.ResourceFieldSchema; import org.apache.pig.ResourceSchema.ResourceFieldSchema;
import org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.PigSplit; import org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.PigSplit;
import org.apache.pig.data.*; import org.apache.pig.data.*;
import org.apache.pig.impl.util.UDFContext;
import org.apache.thrift.TException; import org.apache.thrift.TException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -137,9 +139,18 @@ public class CqlStorage extends AbstractCassandraStorage
CqlConfigHelper.setInputCQLPageRowSize(conf, String.valueOf(pageSize)); CqlConfigHelper.setInputCQLPageRowSize(conf, String.valueOf(pageSize));
if (columns != null && !columns.trim().isEmpty()) if (columns != null && !columns.trim().isEmpty())
CqlConfigHelper.setInputColumns(conf, columns); CqlConfigHelper.setInputColumns(conf, columns);
if (whereClause != null && !whereClause.trim().isEmpty())
CqlConfigHelper.setInputWhereClauses(conf, whereClause); String whereClauseForPartitionFilter = getWhereClauseForPartitionFilter();
String wc = whereClause != null && !whereClause.trim().isEmpty()
? whereClauseForPartitionFilter == null ? whereClause: String.format("%s AND %s", whereClause.trim(), whereClauseForPartitionFilter)
: whereClauseForPartitionFilter;
if (wc != null)
{
logger.debug("where clause: {}", wc);
CqlConfigHelper.setInputWhereClauses(conf, wc);
}
if (System.getenv(PIG_INPUT_SPLIT_SIZE) != null) if (System.getenv(PIG_INPUT_SPLIT_SIZE) != null)
{ {
@ -193,7 +204,7 @@ public class CqlStorage extends AbstractCassandraStorage
initSchema(storeSignature); initSchema(storeSignature);
} }
/** schema: ((name, value), (name, value), (name, value)) where keys are in the front. */ /** schema: (value, value, value) where keys are in the front. */
public ResourceSchema getSchema(String location, Job job) throws IOException public ResourceSchema getSchema(String location, Job job) throws IOException
{ {
setLocation(location, job); setLocation(location, job);
@ -209,28 +220,15 @@ public class CqlStorage extends AbstractCassandraStorage
// will contain all fields for this schema // will contain all fields for this schema
List<ResourceFieldSchema> allSchemaFields = new ArrayList<ResourceFieldSchema>(); List<ResourceFieldSchema> allSchemaFields = new ArrayList<ResourceFieldSchema>();
// defined validators/indexes
for (ColumnDef cdef : cfDef.column_metadata) for (ColumnDef cdef : cfDef.column_metadata)
{ {
// make a new tuple for each col/val pair
ResourceSchema innerTupleSchema = new ResourceSchema();
ResourceFieldSchema innerTupleField = new ResourceFieldSchema();
innerTupleField.setType(DataType.TUPLE);
innerTupleField.setSchema(innerTupleSchema);
innerTupleField.setName(new String(cdef.getName()));
ResourceFieldSchema idxColSchema = new ResourceFieldSchema();
idxColSchema.setName("name");
idxColSchema.setType(getPigType(UTF8Type.instance));
ResourceFieldSchema valSchema = new ResourceFieldSchema(); ResourceFieldSchema valSchema = new ResourceFieldSchema();
AbstractType validator = validators.get(cdef.name); AbstractType validator = validators.get(cdef.name);
if (validator == null) if (validator == null)
validator = marshallers.get(MarshallerType.DEFAULT_VALIDATOR); validator = marshallers.get(MarshallerType.DEFAULT_VALIDATOR);
valSchema.setName("value"); valSchema.setName(new String(cdef.getName()));
valSchema.setType(getPigType(validator)); valSchema.setType(getPigType(validator));
allSchemaFields.add(valSchema);
innerTupleSchema.setFields(new ResourceFieldSchema[] { idxColSchema, valSchema });
allSchemaFields.add(innerTupleField);
} }
// top level schema contains everything // top level schema contains everything
@ -238,16 +236,19 @@ public class CqlStorage extends AbstractCassandraStorage
return schema; return schema;
} }
/** We use CQL3 where clause to define the partition, so do nothing here*/
public String[] getPartitionKeys(String location, Job job)
{
return null;
}
/** We use CQL3 where clause to define the partition, so do nothing here*/
public void setPartitionFilter(Expression partitionFilter) public void setPartitionFilter(Expression partitionFilter)
{ {
UDFContext context = UDFContext.getUDFContext();
Properties property = context.getUDFProperties(AbstractCassandraStorage.class);
property.setProperty(PARTITION_FILTER_SIGNATURE, partitionFilterToWhereClauseString(partitionFilter));
}
/** retrieve where clause for partition filter */
private String getWhereClauseForPartitionFilter()
{
UDFContext context = UDFContext.getUDFContext();
Properties property = context.getUDFProperties(AbstractCassandraStorage.class);
return property.getProperty(PARTITION_FILTER_SIGNATURE);
} }
public void prepareToWrite(RecordWriter writer) public void prepareToWrite(RecordWriter writer)
@ -386,7 +387,7 @@ public class CqlStorage extends AbstractCassandraStorage
/** cql://[username:password@]<keyspace>/<columnfamily>[?[page_size=<size>] /** cql://[username:password@]<keyspace>/<columnfamily>[?[page_size=<size>]
* [&columns=<col1,col2>][&output_query=<prepared_statement_query>][&where_clause=<clause>] * [&columns=<col1,col2>][&output_query=<prepared_statement_query>][&where_clause=<clause>]
* [&split_size=<size>][&partitioner=<partitioner>]] */ * [&split_size=<size>][&partitioner=<partitioner>][&use_secondary=true|false]] */
private void setLocationFromUri(String location) throws IOException private void setLocationFromUri(String location) throws IOException
{ {
try try
@ -420,6 +421,8 @@ public class CqlStorage extends AbstractCassandraStorage
splitSize = Integer.parseInt(urlQuery.get("split_size")); splitSize = Integer.parseInt(urlQuery.get("split_size"));
if (urlQuery.containsKey("partitioner")) if (urlQuery.containsKey("partitioner"))
partitionerClass = urlQuery.get("partitioner"); partitionerClass = urlQuery.get("partitioner");
if (urlQuery.containsKey("use_secondary"))
usePartitionFilter = Boolean.parseBoolean(urlQuery.get("use_secondary"));
} }
String[] parts = urlParts[0].split("/+"); String[] parts = urlParts[0].split("/+");
String[] credentialsAndKeyspace = parts[1].split("@"); String[] credentialsAndKeyspace = parts[1].split("@");
@ -440,7 +443,34 @@ public class CqlStorage extends AbstractCassandraStorage
{ {
throw new IOException("Expected 'cql://[username:password@]<keyspace>/<columnfamily>" + throw new IOException("Expected 'cql://[username:password@]<keyspace>/<columnfamily>" +
"[?[page_size=<size>][&columns=<col1,col2>][&output_query=<prepared_statement>]" + "[?[page_size=<size>][&columns=<col1,col2>][&output_query=<prepared_statement>]" +
"[&where_clause=<clause>][&split_size=<size>][&partitioner=<partitioner>]]': " + e.getMessage()); "[&where_clause=<clause>][&split_size=<size>][&partitioner=<partitioner>][&use_secondary=true|false]]': " + e.getMessage());
}
}
/**
* Return cql where clauses for the corresponding partition filter. Make sure the data format matches
* Only support the following Pig data types: int, long, float, double, boolean and chararray
* */
private String partitionFilterToWhereClauseString(Expression expression)
{
Expression.BinaryExpression be = (Expression.BinaryExpression) expression;
String name = be.getLhs().toString();
String value = be.getRhs().toString();
OpType op = expression.getOpType();
String opString = op.name();
switch (op)
{
case OP_EQ:
opString = " = ";
case OP_GE:
case OP_GT:
case OP_LE:
case OP_LT:
return String.format("%s %s %s", name, opString, value);
case OP_AND:
return String.format("%s AND %s", partitionFilterToWhereClauseString(be.getLhs()), partitionFilterToWhereClauseString(be.getRhs()));
default:
throw new RuntimeException("Unsupported expression type: " + opString);
} }
} }
} }