Package com.netflix.astyanax.cql.reads

Source Code of com.netflix.astyanax.cql.reads.CFRowRangeQueryGen

package com.netflix.astyanax.cql.reads;

import static com.datastax.driver.core.querybuilder.QueryBuilder.gte;
import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
import static com.datastax.driver.core.querybuilder.QueryBuilder.lte;

import java.math.BigInteger;
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.reads.model.CqlColumnSlice;
import com.netflix.astyanax.cql.reads.model.CqlRowSlice.RowRange;
import com.netflix.astyanax.cql.schema.CqlColumnFamilyDefinitionImpl;
import com.netflix.astyanax.serializers.CompositeRangeBuilder.CompositeByteBufferRange;

/**
* This class encapsulates all the query generators for row range queries. 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 row ranges with column slice
* 3. Select row ranges with column range
* 4. Select row ranges 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 collection of row keys as opposed to a row range.
* See {@link CFRowKeysQueryGen} for that implementation. The current class is meant for row range queries only. 
*
* 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 CFRowRangeQueryGen extends CFRowSliceQueryGen {

  /**
   * Constructor
   *
   * @param session
   * @param keyspaceName
   * @param cfDefinition
   */
  public CFRowRangeQueryGen(Session session, String keyspaceName, CqlColumnFamilyDefinitionImpl cfDefinition) {
    super(session, keyspaceName, cfDefinition);
  }

  /**
   * Private helper for constructing the where clause for row ranges
   * @param keyAlias
   * @param select
   * @param rowRange
   * @return
   */
  private Where addWhereClauseForRowRange(String keyAlias, Select select, RowRange<?> rowRange) {

    Where where = null;

    boolean keyIsPresent = false;
    boolean tokenIsPresent = false;

    if (rowRange.getStartKey() != null || rowRange.getEndKey() != null) {
      keyIsPresent = true;
    }
    if (rowRange.getStartToken() != null || rowRange.getEndToken() != null) {
      tokenIsPresent = true;
    }

    if (keyIsPresent && tokenIsPresent) {
      throw new RuntimeException("Cannot provide both token and keys for range query");
    }
   
    if (keyIsPresent) {
      if (rowRange.getStartKey() != null && rowRange.getEndKey() != null) {

        where = select.where(gte(keyAlias, BIND_MARKER))
            .and(lte(keyAlias, BIND_MARKER));

      } else if (rowRange.getStartKey() != null) {       
        where = select.where(gte(keyAlias, BIND_MARKER));

      } else if (rowRange.getEndKey() != null) {
        where = select.where(lte(keyAlias, BIND_MARKER));
      }

    } else if (tokenIsPresent) {
      String tokenOfKey ="token(" + keyAlias + ")";

      if (rowRange.getStartToken() != null && rowRange.getEndToken() != null) {

        where = select.where(gte(tokenOfKey, BIND_MARKER))
            .and(lte(tokenOfKey, BIND_MARKER));

      } else if (rowRange.getStartToken() != null) {
        where = select.where(gte(tokenOfKey, BIND_MARKER));

      } else if (rowRange.getEndToken() != null) {
        where = select.where(lte(tokenOfKey, BIND_MARKER));
      }
    } else {
      where = select.where();
    }

    if (rowRange.getCount() > 0) {
      // TODO: fix this
      //where.limit(rowRange.getCount());
    }
    return where;
  }

  /**
   * Private helper for constructing the bind values for the given row range. Note that the assumption here is that
   * we have a previously constructed prepared statement that we can bind these values with.
   *
   * @param keyAlias
   * @param select
   * @param rowRange
   * @return
   */
  private void bindWhereClauseForRowRange(List<Object> values, RowRange<?> rowRange) {

    boolean keyIsPresent = false;
    boolean tokenIsPresent = false;

    if (rowRange.getStartKey() != null || rowRange.getEndKey() != null) {
      keyIsPresent = true;
    }
    if (rowRange.getStartToken() != null || rowRange.getEndToken() != null) {
      tokenIsPresent = true;
    }

    if (keyIsPresent && tokenIsPresent) {
      throw new RuntimeException("Cannot provide both token and keys for range query");
    }

    if (keyIsPresent) {
      if (rowRange.getStartKey() != null) {
        values.add(rowRange.getStartKey());
      }
      if (rowRange.getEndKey() != null) {
        values.add(rowRange.getEndKey());
      }

    } else if (tokenIsPresent) {

      BigInteger startTokenB = rowRange.getStartToken() != null ? new BigInteger(rowRange.getStartToken()) : null;
      BigInteger endTokenB = rowRange.getEndToken() != null ? new BigInteger(rowRange.getEndToken()) : null;

      Long startToken = startTokenB.longValue();
      Long endToken = endTokenB.longValue();
     
      if (startToken != null && endToken != null) {
        if (startToken != null) {
          values.add(startToken);
        }
        if (endToken != null) {
          values.add(endToken);
        }
      }

      if (rowRange.getCount() > 0) {
        // TODO: fix this
        //where.limit(rowRange.getCount());
      }
      return;
    }
  }

  /**
   * Query generator for selecting all columns for the specified row range.
   *
   * 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)
   */
  private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectAllColumnsForRowRange = 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 addWhereClauseForRowRange(partitionKeyCol, select, rowSliceQuery.getRowSlice().getRange());
        }
      };
    }

    @Override
    public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) {

      List<Object> values = new ArrayList<Object>();
      bindWhereClauseForRowRange(values, rowSliceQuery.getRowSlice().getRange());
      return pStatement.bind(values.toArray(new Object[values.size()]));
    }
  };
 
  private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectColumnSetForRowRange = new QueryGenCache<CqlRowSliceQueryImpl<?,?>>(sessionRef) {

    @Override
    public Callable<RegularStatement> getQueryGen(final CqlRowSliceQueryImpl<?, ?> rowSliceQuery) {
      return new Callable<RegularStatement>() {

        @Override
        public RegularStatement call() throws Exception {
         
          // THIS IS A QUERY WHERE THE COLUMN NAME IS DYNAMIC  E.G TIME SERIES
          RowRange<?> range = rowSliceQuery.getRowSlice().getRange();
          Collection<?> cols = rowSliceQuery.getColumnSlice().getColumns();
          Object[] columns = cols.toArray(new Object[cols.size()]);

          Select select = selectAllColumnsFromKeyspaceAndCF();

          if (columns != null && columns.length > 0) {
            select.allowFiltering();
          }
          Where where = addWhereClauseForRowRange(partitionKeyCol, select, range);
          where.and(in(clusteringKeyCols.get(0).getName(), columns));

          return where;
        }
      };
    }

    @Override
    public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) {
     
      List<Object> values = new ArrayList<Object>();

      bindWhereClauseForRowRange(values, rowSliceQuery.getRowSlice().getRange());
      values.addAll(rowSliceQuery.getColumnSlice().getColumns());

      return pStatement.bind(values.toArray());
    }
  };

 
  /**
   * Query generator for selecting a specified column range with a specified row range.
   *
   * 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 similar column range for the row range)
   */
  private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectColumnRangeForRowRange = 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();
         
          CqlColumnSlice<?> columnSlice = rowSliceQuery.getColumnSlice();
         
          if (columnSlice != null && columnSlice.isRangeQuery()) {
            select.allowFiltering();
          }

          Where where = addWhereClauseForRowRange(partitionKeyCol, select, rowSliceQuery.getRowSlice().getRange());     
          where = addWhereClauseForColumnRange(where, columnSlice);
          return where;
        }
      };
    }

    @Override
    public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) {

      List<Object> values = new ArrayList<Object>();

      bindWhereClauseForRowRange(values, rowSliceQuery.getRowSlice().getRange());
      bindWhereClauseForColumnRange(values, rowSliceQuery.getColumnSlice());
     
      return pStatement.bind(values.toArray());
    }
  };
 
  /**
   * Query generator for selecting a specified composite column range with a specified row range.
   *
   * 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 similar composite column range for the row range)
   */
  private QueryGenCache<CqlRowSliceQueryImpl<?,?>> SelectCompositeColumnRangeForRowRange = 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();
          CompositeByteBufferRange compositeRange = rowSliceQuery.getCompositeRange();
          if (compositeRange != null) {
            select.allowFiltering();
          }

          Where where = addWhereClauseForRowRange(partitionKeyCol, select, rowSliceQuery.getRowSlice().getRange())
          where = addWhereClauseForCompositeColumnRange(where, compositeRange);
          return where;
        }
       
      };
    }

    @Override
    public BoundStatement bindValues(PreparedStatement pStatement, CqlRowSliceQueryImpl<?, ?> rowSliceQuery) {

      List<Object> values = new ArrayList<Object>();

      bindWhereClauseForRowRange(values, rowSliceQuery.getRowSlice().getRange());
      bindWhereClauseForCompositeColumnRange(values, rowSliceQuery.getCompositeRange());

      return pStatement.bind(values.toArray());
    }
  };
 
  /**
   * Main method used to generate the query for the specified row slice query.
   * Note that depending on the query signature, the caller may choose to enable/disable caching
   *
   * @param rowSliceQuery: The Astaynax query for which we need to generate a java driver query
   * @param useCaching: boolean condition indicating whether we should use a previously cached prepared stmt or not.
   *                    If false, then the cache is ignored and we generate the prepared stmt for this query
   *                    If true, then the cached prepared stmt is used. If the cache has not been inited,
   *                    then the prepared stmt is constructed for this query and subsequently cached
   *                   
   * @return BoundStatement: they statement for this Astyanax query
   */
  public BoundStatement getQueryStatement(CqlRowSliceQueryImpl<?,?> rowSliceQuery, boolean useCaching) {

    switch (rowSliceQuery.getColQueryType()) {

    case AllColumns:
      return SelectAllColumnsForRowRange.getBoundStatement(rowSliceQuery, useCaching);
    case ColumnSet:
      return SelectColumnSetForRowRange.getBoundStatement(rowSliceQuery, useCaching);
    case ColumnRange:
      if (isCompositeColumn) {
        return SelectCompositeColumnRangeForRowRange.getBoundStatement(rowSliceQuery, useCaching);
      } else {
        return SelectColumnRangeForRowRange.getBoundStatement(rowSliceQuery, useCaching);
      }
    default :
      throw new RuntimeException("RowSliceQuery with row range use case not supported.");
    }
  }
}
TOP

Related Classes of com.netflix.astyanax.cql.reads.CFRowRangeQueryGen

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.