package com.netflix.astyanax.cql.reads; import static com.datastax.driver.core.querybuilder.QueryBuilder.in; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.RegularStatement; import com.datastax.driver.core.Session; import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Select.Where; import com.netflix.astyanax.cql.schema.CqlColumnFamilyDefinitionImpl; /** * This class encapsulates all the query generators for row slice queries that use a collection of row keys. * There are different row query generators depending on the specific query signature. * * e.g * 1. Select all columns for all the rows in the row range * 2. Select rows with column slice * 3. Select rows with column range * 4. Select rows using a composite range builder for composite column based schema * * Note that for simplicity and brevity, there is another class that handles similar operations for queries that * specify a row range as opposed to a collection of row keys (as is done here). * See {@link CFRowRangeQueryGen} for that implementation. The current class is meant for row slice queries using only row key collections. * * Each of the query generators uses the {@link QueryGenCache} so that it can cache the {@link PreparedStatement} as well * for future use by queries with the same signatures. * * But one must use this with care, since the subsequent query must have the exact signature, else binding values with * the previously constructed prepared statement will break. * * Here is a simple example of a bad query that is not cacheable. * * Say that we want a simple query with a column range in it. * * ks.prepareQuery(myCF) * .getRow("1") * .withColumnSlice("colStart") * .execute(); * * In most cases this query lends itself to a CQL3 representation as follows * * SELECT * FROM ks.mfCF WHERE KEY = ? AND COLUMN1 > ?; * * Now say that we want to perform a successive query (with caching turned ON), but add to the column range query * * ks.prepareQuery(myCF) * .getRow("1") * .withColumnSlice("colStart", "colEnd") * .execute(); * * NOTE THE USE OF BOTH colStart AND colEnd <----- THIS IS A DIFFERENT QUERY SIGNATURE * AND THE CQL QUERY WILL PROBABLY LOOK LIKE * * SELECT * FROM ks.mfCF WHERE KEY = ? AND COLUMN1 > ? AND COLUMN1 < ?; <----- NOTE THE EXTRA BIND MARKER AT THE END FOR THE colEnd * * If we re-use the previously cached prepared statement, then it will not work for the new query signature. The way out of this is to NOT * use caching with different query signatures. * * @author poberai * */ public class CFRowKeysQueryGen extends CFRowSliceQueryGen { public CFRowKeysQueryGen(Session session, String keyspaceName, CqlColumnFamilyDefinitionImpl cfDefinition) { super(session, keyspaceName, cfDefinition); } /** * Query generator for selecting all columns for the specified row keys. * * Note that this object is an implementation of {@link QueryGenCache} * and hence it maintains a cached reference to the previously constructed {@link PreparedStatement} for row range queries with the same * signature (i.e all columns for a similar set of row keys) */ private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectAllColumnsForRowKeys = new QueryGenCache<CqlRowSliceQueryImpl<?,?>>(sessionRef) { @Override public Callable<RegularStatement> getQueryGen(final CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { return new Callable<RegularStatement>() { @Override public RegularStatement call() throws Exception { Select select = selectAllColumnsFromKeyspaceAndCF(); return select.where(in(partitionKeyCol, bindMarkerArray(rowSliceQuery.getRowSlice().getKeys().size()))); } }; } @Override public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { return pStatement.bind(rowSliceQuery.getRowSlice().getKeys().toArray()); } }; /** * Query generator for selecting a column set for the specified row keys. * * Note that this object is an implementation of {@link QueryGenCache} * and hence it maintains a cached reference to the previously constructed {@link PreparedStatement} for row range queries with the same * signature (i.e a similar set of columns for a similar set of rows ) */ private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectColumnSetForRowKeys = new QueryGenCache<CqlRowSliceQueryImpl<?,?>>(sessionRef) { @Override public Callable<RegularStatement> getQueryGen(final CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { return new Callable<RegularStatement>() { @Override public RegularStatement call() throws Exception { if (clusteringKeyCols.size() != 1) { throw new RuntimeException("Cannot perform row slice with col slice query for this schema, clusteringKeyCols.size(): " + clusteringKeyCols.size()); } Collection<?> rowKeys = rowSliceQuery.getRowSlice().getKeys(); Collection<?> cols = rowSliceQuery.getColumnSlice().getColumns(); // THIS IS A QUERY WHERE THE COLUMN NAME IS DYNAMIC E.G TIME SERIES Object[] columns = cols.toArray(new Object[cols.size()]); String clusteringCol = clusteringKeyCols.get(0).getName(); Select select = selectAllColumnsFromKeyspaceAndCF(); return select.where(in(partitionKeyCol, bindMarkerArray(rowKeys.size()))) .and(in(clusteringCol, bindMarkerArray(columns.length))); } }; } @Override public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { if (clusteringKeyCols.size() != 1) { throw new RuntimeException("Cannot perform row slice with col slice query for this schema, clusteringKeyCols.size(): " + clusteringKeyCols.size()); } List<Object> values = new ArrayList<Object>(); values.addAll(rowSliceQuery.getRowSlice().getKeys()); values.addAll(rowSliceQuery.getColumnSlice().getColumns()); return pStatement.bind(values.toArray()); } }; /** * Query generator for selecting a column range for the specified row keys. * * Note that this object is an implementation of {@link QueryGenCache} * and hence it maintains a cached reference to the previously constructed {@link PreparedStatement} for row range queries with the same * signature (i.e a similar column range for a similar set of rows) */ private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectColumnRangeForRowKeys = new QueryGenCache<CqlRowSliceQueryImpl<?,?>>(sessionRef) { @Override public Callable<RegularStatement> getQueryGen(final CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { return new Callable<RegularStatement>() { @Override public RegularStatement call() throws Exception { if (clusteringKeyCols.size() != 1) { throw new RuntimeException("Cannot perform row slice with col slice query for this schema, clusteringKeyCols.size(): " + clusteringKeyCols.size()); } Select select = selectAllColumnsFromKeyspaceAndCF(); Where where = select.where(in(partitionKeyCol, bindMarkerArray(rowSliceQuery.getRowSlice().getKeys().size()))); where = addWhereClauseForColumnRange(where, rowSliceQuery.getColumnSlice()); return where; } }; } @Override public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { if (clusteringKeyCols.size() != 1) { throw new RuntimeException("Cannot perform row slice with col slice query for this schema, clusteringKeyCols.size(): " + clusteringKeyCols.size()); } List<Object> values = new ArrayList<Object>(); values.addAll(rowSliceQuery.getRowSlice().getKeys()); bindWhereClauseForColumnRange(values, rowSliceQuery.getColumnSlice()); return pStatement.bind(values.toArray()); } }; /** * Query generator for selecting a composite column range for the specified row keys. * * Note that this object is an implementation of {@link QueryGenCache} * and hence it maintains a cached reference to the previously constructed {@link PreparedStatement} for row range queries with the same * signature (i.e a similar composite column range for a similar set of rows) */ private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectCompositeColumnRangeForRowKeys = new QueryGenCache<CqlRowSliceQueryImpl<?,?>>(sessionRef) { @Override public Callable<RegularStatement> getQueryGen(final CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { return new Callable<RegularStatement>() { @Override public RegularStatement call() throws Exception { Select select = selectAllColumnsFromKeyspaceAndCF(); Where stmt = select.where(in(partitionKeyCol, bindMarkerArray(rowSliceQuery.getRowSlice().getKeys().size()))); stmt = addWhereClauseForCompositeColumnRange(stmt, rowSliceQuery.getCompositeRange()); return stmt; } }; } @Override public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) { List<Object> values = new ArrayList<Object>(); values.addAll(rowSliceQuery.getRowSlice().getKeys()); bindWhereClauseForCompositeColumnRange(values, rowSliceQuery.getCompositeRange()); return pStatement.bind(values.toArray()); } }; /** * Main method that is used to generate the java driver statement from the given Astyanax row slice query. * Note that the method allows the caller to specify whether to use caching or not. * * If caching is disabled, then the PreparedStatement is generated every time * If caching is enabled, then the cached PreparedStatement is used for the given Astyanax RowSliceQuery. * In this case if the PreparedStatement is missing, then it is constructed from the Astyanax query and * used to init the cached reference and hence can be used by other subsequent Astayanx RowSliceQuery * operations with the same signature (that opt in for caching) * * @param rowSliceQuery * @param useCaching * @return */ public BoundStatement getQueryStatement(CqlRowSliceQueryImpl<?,?> rowSliceQuery, boolean useCaching) { switch (rowSliceQuery.getColQueryType()) { case AllColumns: return SelectAllColumnsForRowKeys.getBoundStatement(rowSliceQuery, useCaching); case ColumnSet: return SelectColumnSetForRowKeys.getBoundStatement(rowSliceQuery, useCaching); case ColumnRange: if (isCompositeColumn) { return SelectCompositeColumnRangeForRowKeys.getBoundStatement(rowSliceQuery, useCaching); } else { return SelectColumnRangeForRowKeys.getBoundStatement(rowSliceQuery, useCaching); } default : throw new RuntimeException("RowSliceQuery with row keys use case not supported."); } } }