package com.netflix.astyanax.cql.reads;
import static com.datastax.driver.core.querybuilder.QueryBuilder.desc;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.gt;
import static com.datastax.driver.core.querybuilder.QueryBuilder.gte;
import static com.datastax.driver.core.querybuilder.QueryBuilder.lt;
import static com.datastax.driver.core.querybuilder.QueryBuilder.lte;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Select.Where;
import com.netflix.astyanax.cql.reads.model.CqlColumnSlice;
import com.netflix.astyanax.cql.schema.CqlColumnFamilyDefinitionImpl;
import com.netflix.astyanax.ddl.ColumnDefinition;
import com.netflix.astyanax.query.RowSliceQuery;
import com.netflix.astyanax.serializers.CompositeRangeBuilder.CompositeByteBufferRange;
import com.netflix.astyanax.serializers.CompositeRangeBuilder.RangeQueryOp;
import com.netflix.astyanax.serializers.CompositeRangeBuilder.RangeQueryRecord;
/**
* Base class that contains the utilities for generating queries for read operations via the
* {@link RowSliceQuery} class.
*
* Note that this class is just a place holder for some useful generic utilities.
* See {@link CFRowKeysQueryGen} and {@link CFRowRangeQueryGen} which are the 2 extending classes
* for functionality that actually supports the queries.
*
* @author poberai
*/
public class CFRowSliceQueryGen {
// Thread safe reference to the underlying session object. We need the session object to be able to "prepare" query statements
protected final AtomicReference<Session> sessionRef = new AtomicReference<Session>(null);
// the keyspace being queried. Used for all the underlying queries being generated
protected final String keyspace;
// the cf definition which helps extending classes construct the right query as per the schema
protected final CqlColumnFamilyDefinitionImpl cfDef;
// Other useful derivatives of the cf definition that are frequently used by query generators
protected final String partitionKeyCol;
protected final String[] allPrimayKeyCols;
protected final List<ColumnDefinition> clusteringKeyCols;
protected final List<ColumnDefinition> regularCols;
// Condition tracking whether the underlying schema uses composite columns. This is imp since it influences how
// a single Column (composite column) can be decomposed into it's individual components that form different parts of the query.
protected boolean isCompositeColumn;
// bind marker for generating the prepared statements
protected static final String BIND_MARKER = "?";
public CFRowSliceQueryGen(Session session, String keyspaceName, CqlColumnFamilyDefinitionImpl cfDefinition) {
this.keyspace = keyspaceName;
this.cfDef = cfDefinition;
this.sessionRef.set(session);
partitionKeyCol = cfDef.getPartitionKeyColumnDefinition().getName();
allPrimayKeyCols = cfDef.getAllPkColNames();
clusteringKeyCols = cfDef.getClusteringKeyColumnDefinitionList();
regularCols = cfDef.getRegularColumnDefinitionList();
isCompositeColumn = (clusteringKeyCols.size() > 1);
}
/**
*
* SOME BASIC UTILITY METHODS USED BY ALL THE ROW SLICE QUERY GENERATORS
*/
protected Select selectAllColumnsFromKeyspaceAndCF() {
Select.Selection select = QueryBuilder.select();
for (int i=0; i<allPrimayKeyCols.length; i++) {
select.column(allPrimayKeyCols[i]);
}
for (ColumnDefinition colDef : regularCols) {
String colName = colDef.getName();
select.column(colName).ttl(colName).writeTime(colName);
}
return select.from(keyspace, cfDef.getName());
}
protected Where addWhereClauseForColumnRange(Where where, CqlColumnSlice<?> columnSlice) {
String clusteringKeyCol = clusteringKeyCols.get(0).getName();
if (!columnSlice.isRangeQuery()) {
return where;
}
if (columnSlice.getStartColumn() != null) {
where.and(gte(clusteringKeyCol, columnSlice.getStartColumn()));
}
if (columnSlice.getEndColumn() != null) {
where.and(lte(clusteringKeyCol, columnSlice.getEndColumn()));
}
if (columnSlice.getReversed()) {
where.orderBy(desc(clusteringKeyCol));
}
if (columnSlice.getLimit() != -1) {
where.limit(columnSlice.getLimit());
}
return where;
}
protected void bindWhereClauseForColumnRange(List<Object> values, CqlColumnSlice<?> columnSlice) {
if (!columnSlice.isRangeQuery()) {
return;
}
if (columnSlice.getStartColumn() != null) {
values.add(columnSlice.getStartColumn());
}
if (columnSlice.getEndColumn() != null) {
values.add(columnSlice.getEndColumn());
}
if (columnSlice.getLimit() != -1) {
values.add(columnSlice.getLimit());
}
return;
}
protected Where addWhereClauseForCompositeColumnRange(Where stmt, CompositeByteBufferRange compositeRange) {
List<RangeQueryRecord> records = compositeRange.getRecords();
int componentIndex = 0;
for (RangeQueryRecord record : records) {
for (RangeQueryOp op : record.getOps()) {
String columnName = clusteringKeyCols.get(componentIndex).getName();
switch (op.getOperator()) {
case EQUAL:
stmt.and(eq(columnName, BIND_MARKER));
componentIndex++;
break;
case LESS_THAN :
stmt.and(lt(columnName, BIND_MARKER));
break;
case LESS_THAN_EQUALS:
stmt.and(lte(columnName, BIND_MARKER));
break;
case GREATER_THAN:
stmt.and(gt(columnName, BIND_MARKER));
break;
case GREATER_THAN_EQUALS:
stmt.and(gte(columnName, BIND_MARKER));
break;
default:
throw new RuntimeException("Cannot recognize operator: " + op.getOperator().name());
}; // end of switch stmt
} // end of inner for for ops for each range query record
}
return stmt;
}
protected void bindWhereClauseForCompositeColumnRange(List<Object> values, CompositeByteBufferRange compositeRange) {
List<RangeQueryRecord> records = compositeRange.getRecords();
for (RangeQueryRecord record : records) {
for (RangeQueryOp op : record.getOps()) {
values.add(op.getValue());
}
}
return;
}
protected Object[] bindMarkerArray(int n) {
Object[] arr = new Object[n];
for (int i=0; i<n; i++) {
arr[i] = BIND_MARKER;
}
return arr;
}
}