All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.astyanax.cql.reads.CFRowKeysQueryGen Maven / Gradle / Ivy

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> SelectAllColumnsForRowKeys = new QueryGenCache>(sessionRef) {

		@Override
		public Callable getQueryGen(final CqlRowSliceQueryImpl rowSliceQuery) {
			return new Callable() {

				@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> SelectColumnSetForRowKeys = new QueryGenCache>(sessionRef) {
	
		@Override
		public Callable getQueryGen(final CqlRowSliceQueryImpl rowSliceQuery) {
			return new Callable() {

				@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 values = new ArrayList();
			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> SelectColumnRangeForRowKeys = new QueryGenCache>(sessionRef) {

		@Override
		public Callable getQueryGen(final CqlRowSliceQueryImpl rowSliceQuery) {
			return new Callable() {

				@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 values = new ArrayList();

			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> SelectCompositeColumnRangeForRowKeys = new QueryGenCache>(sessionRef) {

		@Override
		public Callable getQueryGen(final CqlRowSliceQueryImpl rowSliceQuery) {
			return new Callable() {

				@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 values = new ArrayList();

			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.");
		}
	}
}