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

awscala.dynamodbv2.ResultPager.scala Maven / Gradle / Ivy

package awscala.dynamodbv2

import java.util

import com.amazonaws.services.dynamodbv2.model
import com.amazonaws.services.dynamodbv2.model.{ QueryRequest, ScanRequest }
import com.amazonaws.services.{ dynamodbv2 => aws }

import scala.jdk.CollectionConverters._

/**
 * The ResultPager allows iteration over the results from a DynamoDB query/scan as a single stream of items,
 * handling the necessary paging details in the background.
 *
 * DynamoDB paginates the result of query/scan operations. The data returned from a Query or Scan operation is limited
 * to 1 MB; this means that if the result set exceeds 1 MB of data, you'll need to perform another Query or Scan
 * operation to retrieve the next 1 MB of data. In addition, the limit parameter controls the number of items that you
 * want DynamoDB to process in a single request before returning results.
 * See http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#Pagination
 *
 * Each response from DynamoDB indicates if the processing has reached the end of the dataset, or if another request is
 * needed in order to continue scanning where the last request finished.
 *
 * When the items from a page is exhausted, the ResultPager will issue a new query/scan for the next page of results,
 * until processing reaches the end of the dataset, or the client stops iterating over the result (as the return value
 * is a Stream[Item])
 *
 * @tparam TReq
 * @tparam TRes
 */
sealed trait ResultPager[TReq, TRes] extends Iterator[Item] {
  def table: Table
  def operation: TReq => TRes
  def request: TReq

  var items: Seq[Item] = _
  var pageNo = 0
  var lastKey: java.util.Map[String, aws.model.AttributeValue] = _
  var index = 0
  nextPage(request)

  def getItems(result: TRes): java.util.List[java.util.Map[String, aws.model.AttributeValue]]
  def invokeCallback(result: TRes): Unit
  def getLastEvaluatedKey(result: TRes): java.util.Map[String, aws.model.AttributeValue]
  def withExclusiveStartKey(request: TReq, lastKey: java.util.Map[String, aws.model.AttributeValue]): TReq
  def getCount(result: TRes): Int

  private def nextPage(request: TReq): Unit = {
    val result = operation(request)

    request match {
      case req: aws.model.QueryRequest =>
        if (req.getSelect == aws.model.Select.COUNT.toString)
          items = Seq(Item(table, Seq(Attribute("Count", AttributeValue(new AttributeValue(n = Some(getCount(result).toString)))))))
        else {
          invokeCallback(result)
          items = getItems(result).asScala.map(i => Item(table, i)).toSeq
        }
      case req: aws.model.ScanRequest =>
        if (req.getSelect == aws.model.Select.COUNT.toString)
          items = Seq(Item(table, Seq(Attribute("Count", AttributeValue(new AttributeValue(n = Some(getCount(result).toString)))))))
        else {
          invokeCallback(result)
          items = getItems(result).asScala.map(i => Item(table, i)).toSeq
        }
    }

    lastKey = getLastEvaluatedKey(result)
    index = 0
    pageNo += 1
  }

  override def next(): Item = {
    val item: Item = items(index)
    index += 1
    item
  }

  override def hasNext: Boolean = {
    if (index < items.size) {
      true
    } else if (lastKey == null) {
      false
    } else {
      do {
        nextPage(withExclusiveStartKey(request, lastKey))
      } while (lastKey != null && items.isEmpty) // there are potentially more matching data, but this page didn't contain any
      items.nonEmpty
    }
  }
}

// a pager specialized for query request/results
class QueryResultPager(val table: Table, val operation: aws.model.QueryRequest => aws.model.QueryResult, val request: aws.model.QueryRequest, pageStatsCallback: PageStats => Unit)
  extends ResultPager[aws.model.QueryRequest, aws.model.QueryResult] {
  override def getItems(result: aws.model.QueryResult): util.List[util.Map[String, model.AttributeValue]] = result.getItems
  override def getCount(result: aws.model.QueryResult): Int = result.getCount
  override def getLastEvaluatedKey(result: aws.model.QueryResult): util.Map[String, model.AttributeValue] = result.getLastEvaluatedKey
  override def withExclusiveStartKey(request: aws.model.QueryRequest, lastKey: java.util.Map[String, aws.model.AttributeValue]): QueryRequest = request.withExclusiveStartKey(lastKey)
  override def invokeCallback(result: aws.model.QueryResult): Unit = {
    Option(pageStatsCallback).foreach(fun => fun(PageStats(pageNo, result.getLastEvaluatedKey == null, request.getLimit, result.getScannedCount, result.getCount, result.getConsumedCapacity)))
  }
}

// a pager specialized for scan request/results
class ScanResultPager(val table: Table, val operation: aws.model.ScanRequest => aws.model.ScanResult, val request: aws.model.ScanRequest, pageStatsCallback: PageStats => Unit)
  extends ResultPager[aws.model.ScanRequest, aws.model.ScanResult] {
  override def getItems(result: aws.model.ScanResult): util.List[util.Map[String, model.AttributeValue]] = result.getItems
  override def getCount(result: aws.model.ScanResult): Int = result.getCount
  override def getLastEvaluatedKey(result: aws.model.ScanResult): util.Map[String, model.AttributeValue] = result.getLastEvaluatedKey
  override def withExclusiveStartKey(request: aws.model.ScanRequest, lastKey: java.util.Map[String, aws.model.AttributeValue]): ScanRequest = request.withExclusiveStartKey(lastKey)
  override def invokeCallback(result: aws.model.ScanResult): Unit = {
    Option(pageStatsCallback).foreach(fun => fun(PageStats(pageNo, result.getLastEvaluatedKey == null, request.getLimit, result.getScannedCount, result.getCount, result.getConsumedCapacity)))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy