embulk-input-dynamodb 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/master.yml +34 -0
- data/.github/workflows/test.yml +30 -0
- data/.scalafmt.conf +5 -0
- data/CHANGELOG.md +49 -0
- data/README.md +204 -54
- data/build.gradle +53 -44
- data/example/config-deprecated.yml +20 -0
- data/example/config-query-as-json.yml +18 -0
- data/example/config-query.yml +22 -0
- data/example/config-scan.yml +18 -0
- data/example/prepare_dynamodb_table.sh +67 -0
- data/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/gradle/wrapper/gradle-wrapper.properties +1 -2
- data/gradlew +67 -48
- data/gradlew.bat +20 -10
- data/{test/run_dynamodb_local.sh → run_dynamodb_local.sh} +2 -1
- data/settings.gradle +1 -0
- data/src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala +73 -0
- data/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala +76 -25
- data/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala +132 -32
- data/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala +44 -0
- data/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala +37 -0
- data/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala +240 -0
- data/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala +35 -0
- data/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala +79 -0
- data/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala +61 -0
- data/src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala +72 -0
- data/src/main/scala/org/embulk/input/dynamodb/{Filter.scala → deprecated/Filter.scala} +3 -3
- data/src/main/scala/org/embulk/input/dynamodb/{FilterConfig.scala → deprecated/FilterConfig.scala} +13 -13
- data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/AbstractOperation.scala +36 -18
- data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/QueryOperation.scala +21 -13
- data/src/main/scala/org/embulk/input/dynamodb/{ope → deprecated/ope}/ScanOperation.scala +20 -13
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala +154 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala +245 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueType.scala +33 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemColumnVisitor.scala +50 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemConsumer.scala +40 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemIterator.scala +19 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala +64 -0
- data/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala +135 -0
- data/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala +169 -0
- data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala +59 -0
- data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala +72 -0
- data/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala +93 -0
- data/src/main/scala/org/embulk/input/dynamodb/operation/EmbulkDynamodbOperation.scala +15 -0
- data/src/main/scala/org/embulk/input/dynamodb/package.scala +4 -9
- data/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala +245 -101
- data/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala +150 -97
- data/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationTest.scala +188 -0
- data/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationTest.scala +181 -0
- data/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala +85 -0
- metadata +73 -49
- data/circle.yml +0 -16
- data/config/checkstyle/checkstyle.xml +0 -128
- data/config/checkstyle/default.xml +0 -108
- data/src/main/scala/org/embulk/input/dynamodb/AttributeValueHelper.scala +0 -41
- data/src/main/scala/org/embulk/input/dynamodb/AwsCredentials.scala +0 -63
- data/src/main/scala/org/embulk/input/dynamodb/DynamoDBClient.scala +0 -23
- data/src/test/resources/yaml/authMethodBasic.yml +0 -21
- data/src/test/resources/yaml/authMethodBasic_Error.yml +0 -19
- data/src/test/resources/yaml/authMethodEnv.yml +0 -19
- data/src/test/resources/yaml/authMethodProfile.yml +0 -20
- data/src/test/resources/yaml/dynamodb-local-query.yml +0 -25
- data/src/test/resources/yaml/dynamodb-local-scan.yml +0 -23
- data/src/test/resources/yaml/notSetAuthMethod.yml +0 -20
- data/src/test/scala/org/embulk/input/dynamodb/ope/QueryOperationTest.scala +0 -83
- data/src/test/scala/org/embulk/input/dynamodb/ope/ScanOperationTest.scala +0 -83
- data/test/create_table.sh +0 -16
- data/test/put_items.sh +0 -25
@@ -0,0 +1,33 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
sealed abstract class DynamodbAttributeValueType
|
4
|
+
|
5
|
+
object DynamodbAttributeValueType {
|
6
|
+
final case object S extends DynamodbAttributeValueType
|
7
|
+
final case object N extends DynamodbAttributeValueType
|
8
|
+
final case object B extends DynamodbAttributeValueType
|
9
|
+
final case object SS extends DynamodbAttributeValueType
|
10
|
+
final case object NS extends DynamodbAttributeValueType
|
11
|
+
final case object BS extends DynamodbAttributeValueType
|
12
|
+
final case object M extends DynamodbAttributeValueType
|
13
|
+
final case object L extends DynamodbAttributeValueType
|
14
|
+
final case object NULL extends DynamodbAttributeValueType
|
15
|
+
final case object BOOL extends DynamodbAttributeValueType
|
16
|
+
final case object UNKNOWN extends DynamodbAttributeValueType
|
17
|
+
|
18
|
+
def apply(typeName: String): DynamodbAttributeValueType = {
|
19
|
+
typeName match {
|
20
|
+
case "S" => S
|
21
|
+
case "N" => N
|
22
|
+
case "B" => B
|
23
|
+
case "SS" => SS
|
24
|
+
case "NS" => NS
|
25
|
+
case "BS" => BS
|
26
|
+
case "M" => M
|
27
|
+
case "L" => L
|
28
|
+
case "NULL" => NULL
|
29
|
+
case "BOOL" => BOOL
|
30
|
+
case _ => UNKNOWN
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
import org.embulk.spi.{Column, ColumnVisitor, PageBuilder}
|
4
|
+
|
5
|
+
case class DynamodbItemColumnVisitor(
|
6
|
+
itemReader: DynamodbItemReader,
|
7
|
+
pageBuilder: PageBuilder
|
8
|
+
) extends ColumnVisitor {
|
9
|
+
|
10
|
+
override def booleanColumn(column: Column): Unit = {
|
11
|
+
itemReader.getBoolean(column) match {
|
12
|
+
case Some(v) => pageBuilder.setBoolean(column, v)
|
13
|
+
case None => pageBuilder.setNull(column)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
override def longColumn(column: Column): Unit = {
|
18
|
+
itemReader.getLong(column) match {
|
19
|
+
case Some(v) => pageBuilder.setLong(column, v)
|
20
|
+
case None => pageBuilder.setNull(column)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
override def doubleColumn(column: Column): Unit =
|
25
|
+
itemReader.getDouble(column) match {
|
26
|
+
case Some(v) => pageBuilder.setDouble(column, v)
|
27
|
+
case None => pageBuilder.setNull(column)
|
28
|
+
}
|
29
|
+
|
30
|
+
override def stringColumn(column: Column): Unit = {
|
31
|
+
itemReader.getString(column) match {
|
32
|
+
case Some(v) => pageBuilder.setString(column, v)
|
33
|
+
case None => pageBuilder.setNull(column)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
override def timestampColumn(column: Column): Unit = {
|
38
|
+
itemReader.getTimestamp(column) match {
|
39
|
+
case Some(v) => pageBuilder.setTimestamp(column, v)
|
40
|
+
case None => pageBuilder.setNull(column)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
override def jsonColumn(column: Column): Unit =
|
45
|
+
itemReader.getJson(column) match {
|
46
|
+
case Some(v) => pageBuilder.setJson(column, v)
|
47
|
+
case None => pageBuilder.setNull(column)
|
48
|
+
}
|
49
|
+
|
50
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
import com.amazonaws.services.dynamodbv2.model.AttributeValue
|
4
|
+
import org.embulk.spi.PageBuilder
|
5
|
+
|
6
|
+
object DynamodbItemConsumer {
|
7
|
+
|
8
|
+
def consumeItemsAsJson(
|
9
|
+
schema: DynamodbItemSchema,
|
10
|
+
pageBuilder: PageBuilder
|
11
|
+
): Seq[Map[String, AttributeValue]] => Unit = {
|
12
|
+
val column = schema.getEmbulkSchema.getColumn(0)
|
13
|
+
items: Seq[Map[String, AttributeValue]] =>
|
14
|
+
items.foreach { item =>
|
15
|
+
val transformable = DynamodbAttributeValueEmbulkTypeTransformable(
|
16
|
+
DynamodbAttributeValue(item)
|
17
|
+
)
|
18
|
+
transformable.asMessagePack match {
|
19
|
+
case Some(v) => pageBuilder.setJson(column, v)
|
20
|
+
case None => pageBuilder.setNull(column)
|
21
|
+
}
|
22
|
+
pageBuilder.addRecord()
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
def consumeItemsByEmbulkSchema(
|
27
|
+
schema: DynamodbItemSchema,
|
28
|
+
pageBuilder: PageBuilder
|
29
|
+
): Seq[Map[String, AttributeValue]] => Unit = {
|
30
|
+
items: Seq[Map[String, AttributeValue]] =>
|
31
|
+
val itemReader = DynamodbItemReader(schema, DynamodbItemIterator(items))
|
32
|
+
val visitor = DynamodbItemColumnVisitor(itemReader, pageBuilder)
|
33
|
+
|
34
|
+
while (itemReader.nextItem) {
|
35
|
+
schema.visitColumns(visitor)
|
36
|
+
pageBuilder.addRecord()
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
import com.amazonaws.services.dynamodbv2.model.AttributeValue
|
4
|
+
|
5
|
+
object DynamodbItemIterator {
|
6
|
+
|
7
|
+
def apply(data: Seq[Map[String, AttributeValue]]): DynamodbItemIterator =
|
8
|
+
new DynamodbItemIterator {
|
9
|
+
|
10
|
+
private val delegated: Iterator[Map[String, AttributeValue]] =
|
11
|
+
data.iterator
|
12
|
+
override def hasNext: Boolean = delegated.hasNext
|
13
|
+
|
14
|
+
override def next(): Map[String, DynamodbAttributeValue] =
|
15
|
+
delegated.next().map(x => (x._1, DynamodbAttributeValue(x._2)))
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
trait DynamodbItemIterator extends Iterator[Map[String, DynamodbAttributeValue]]
|
@@ -0,0 +1,64 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
import org.embulk.spi.Column
|
4
|
+
import org.embulk.spi.time.Timestamp
|
5
|
+
import org.msgpack.value.Value
|
6
|
+
|
7
|
+
case class DynamodbItemReader(
|
8
|
+
private val schema: DynamodbItemSchema,
|
9
|
+
private val ite: DynamodbItemIterator
|
10
|
+
) {
|
11
|
+
private var currentItem: Map[String, DynamodbAttributeValue] = _
|
12
|
+
|
13
|
+
def nextItem: Boolean = {
|
14
|
+
ite.nextOption() match {
|
15
|
+
case Some(v) =>
|
16
|
+
currentItem = v
|
17
|
+
true
|
18
|
+
case None => false
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
def getSchema: DynamodbItemSchema = schema
|
23
|
+
|
24
|
+
def getTransformable(
|
25
|
+
name: String,
|
26
|
+
value: DynamodbAttributeValue
|
27
|
+
): DynamodbAttributeValueEmbulkTypeTransformable = {
|
28
|
+
DynamodbAttributeValueEmbulkTypeTransformable(
|
29
|
+
value,
|
30
|
+
typeEnforcer = schema.getAttributeType(name),
|
31
|
+
timestampParser = schema.getTimestampParser(name)
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
def getBoolean(column: Column): Option[Boolean] =
|
36
|
+
currentItem
|
37
|
+
.get(column.getName)
|
38
|
+
.flatMap(v => getTransformable(column.getName, v).asBoolean)
|
39
|
+
|
40
|
+
def getString(column: Column): Option[String] =
|
41
|
+
currentItem
|
42
|
+
.get(column.getName)
|
43
|
+
.flatMap(v => getTransformable(column.getName, v).asString)
|
44
|
+
|
45
|
+
def getLong(column: Column): Option[Long] =
|
46
|
+
currentItem
|
47
|
+
.get(column.getName)
|
48
|
+
.flatMap(v => getTransformable(column.getName, v).asLong)
|
49
|
+
|
50
|
+
def getDouble(column: Column): Option[Double] =
|
51
|
+
currentItem
|
52
|
+
.get(column.getName)
|
53
|
+
.flatMap(v => getTransformable(column.getName, v).asDouble)
|
54
|
+
|
55
|
+
def getTimestamp(column: Column): Option[Timestamp] =
|
56
|
+
currentItem
|
57
|
+
.get(column.getName)
|
58
|
+
.flatMap(v => getTransformable(column.getName, v).asTimestamp)
|
59
|
+
|
60
|
+
def getJson(column: Column): Option[Value] =
|
61
|
+
currentItem
|
62
|
+
.get(column.getName)
|
63
|
+
.flatMap(v => getTransformable(column.getName, v).asMessagePack)
|
64
|
+
}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
package org.embulk.input.dynamodb.item
|
2
|
+
|
3
|
+
import java.util.{Optional, List => JList}
|
4
|
+
|
5
|
+
import com.amazonaws.services.dynamodbv2.model.AttributeValue
|
6
|
+
import com.fasterxml.jackson.annotation.{JsonCreator, JsonValue}
|
7
|
+
import org.embulk.config.{Config, ConfigDefault, Task => EmbulkTask}
|
8
|
+
import org.embulk.spi.{Column, PageBuilder, Schema}
|
9
|
+
import org.embulk.spi.`type`.{Type, Types}
|
10
|
+
import org.embulk.spi.time.TimestampParser
|
11
|
+
|
12
|
+
import scala.jdk.CollectionConverters._
|
13
|
+
import scala.util.chaining._
|
14
|
+
import scala.util.Try
|
15
|
+
|
16
|
+
object DynamodbItemSchema {
|
17
|
+
|
18
|
+
trait ColumnTask
|
19
|
+
extends EmbulkTask
|
20
|
+
with TimestampParser.TimestampColumnOption {
|
21
|
+
|
22
|
+
@Config("name")
|
23
|
+
def getName: String
|
24
|
+
|
25
|
+
@Config("type")
|
26
|
+
def getType: Type
|
27
|
+
|
28
|
+
@Config("attribute_type")
|
29
|
+
@ConfigDefault("null")
|
30
|
+
def getAttributeType: Optional[String]
|
31
|
+
}
|
32
|
+
|
33
|
+
@deprecated(
|
34
|
+
message = "for DeprecatedDynamodbInputPlugin",
|
35
|
+
since = "0.3.0"
|
36
|
+
)
|
37
|
+
case class SchemaConfigCompat(columnTasks: Seq[ColumnTask]) {
|
38
|
+
@JsonCreator
|
39
|
+
def this(columnTasks: JList[ColumnTask]) =
|
40
|
+
this(columnTasks.asScala.toSeq)
|
41
|
+
|
42
|
+
@JsonValue
|
43
|
+
def getColumnTasks: JList[ColumnTask] = columnTasks.asJava
|
44
|
+
|
45
|
+
def toSchema: Schema =
|
46
|
+
Schema
|
47
|
+
.builder()
|
48
|
+
.tap { b =>
|
49
|
+
columnTasks.foreach { t =>
|
50
|
+
b.add(t.getName, t.getType)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
.build()
|
54
|
+
|
55
|
+
def isEmpty: Boolean = columnTasks.isEmpty
|
56
|
+
}
|
57
|
+
|
58
|
+
trait Task extends EmbulkTask with TimestampParser.Task {
|
59
|
+
|
60
|
+
@Config("json_column_name")
|
61
|
+
@ConfigDefault("\"record\"")
|
62
|
+
def getJsonColumnName: String
|
63
|
+
|
64
|
+
@Config("columns")
|
65
|
+
@ConfigDefault("[]")
|
66
|
+
def getColumns: SchemaConfigCompat
|
67
|
+
}
|
68
|
+
|
69
|
+
}
|
70
|
+
|
71
|
+
case class DynamodbItemSchema(task: DynamodbItemSchema.Task) {
|
72
|
+
|
73
|
+
// TODO: build in this class after removing SchemaConfigCompat.
|
74
|
+
private lazy val embulkSchema: Schema =
|
75
|
+
if (!isItemAsJson) task.getColumns.toSchema
|
76
|
+
else
|
77
|
+
Schema
|
78
|
+
.builder()
|
79
|
+
.add(task.getJsonColumnName, Types.JSON)
|
80
|
+
.build()
|
81
|
+
|
82
|
+
private lazy val timestampParsers: Map[String, TimestampParser] =
|
83
|
+
task.getColumns.columnTasks.map { columnTask =>
|
84
|
+
columnTask.getName -> TimestampParser.of(task, columnTask)
|
85
|
+
}.toMap
|
86
|
+
|
87
|
+
private lazy val attributeTypes: Map[String, DynamodbAttributeValueType] =
|
88
|
+
task.getColumns.columnTasks
|
89
|
+
.filter(_.getAttributeType.isPresent)
|
90
|
+
.map { columnTask =>
|
91
|
+
columnTask.getName -> DynamodbAttributeValueType(
|
92
|
+
columnTask.getAttributeType.get()
|
93
|
+
)
|
94
|
+
}
|
95
|
+
.toMap
|
96
|
+
|
97
|
+
private lazy val embulkColumns: Map[String, Column] =
|
98
|
+
getEmbulkSchema.getColumns.asScala
|
99
|
+
.map(column => column.getName -> column)
|
100
|
+
.toMap
|
101
|
+
|
102
|
+
def getEmbulkSchema: Schema = embulkSchema
|
103
|
+
|
104
|
+
def getTimestampParser(column: Column): Option[TimestampParser] =
|
105
|
+
timestampParsers.get(column.getName)
|
106
|
+
|
107
|
+
def getTimestampParser(columnName: String): Option[TimestampParser] =
|
108
|
+
getEmbulkColumn(columnName).flatMap(getTimestampParser)
|
109
|
+
|
110
|
+
def getAttributeType(column: Column): Option[DynamodbAttributeValueType] =
|
111
|
+
attributeTypes.get(column.getName)
|
112
|
+
|
113
|
+
def getAttributeType(
|
114
|
+
columnName: String
|
115
|
+
): Option[DynamodbAttributeValueType] =
|
116
|
+
getEmbulkColumn(columnName).flatMap(getAttributeType)
|
117
|
+
|
118
|
+
def getEmbulkColumn(columnName: String): Option[Column] =
|
119
|
+
embulkColumns.get(columnName)
|
120
|
+
|
121
|
+
def getEmbulkColumn(columnIndex: Int): Option[Column] =
|
122
|
+
Try(getEmbulkSchema.getColumn(columnIndex)).toOption
|
123
|
+
|
124
|
+
def isItemAsJson: Boolean = task.getColumns.isEmpty
|
125
|
+
|
126
|
+
def visitColumns(visitor: DynamodbItemColumnVisitor): Unit =
|
127
|
+
getEmbulkSchema.visitColumns(visitor)
|
128
|
+
|
129
|
+
def getItemsConsumer(
|
130
|
+
pageBuilder: PageBuilder
|
131
|
+
): Seq[Map[String, AttributeValue]] => Unit = {
|
132
|
+
if (isItemAsJson) DynamodbItemConsumer.consumeItemsAsJson(this, pageBuilder)
|
133
|
+
else DynamodbItemConsumer.consumeItemsByEmbulkSchema(this, pageBuilder)
|
134
|
+
}
|
135
|
+
}
|
@@ -0,0 +1,169 @@
|
|
1
|
+
package org.embulk.input.dynamodb.operation
|
2
|
+
|
3
|
+
import java.lang.{
|
4
|
+
Boolean => JBoolean,
|
5
|
+
Integer => JInteger,
|
6
|
+
Long => JLong,
|
7
|
+
String => JString
|
8
|
+
}
|
9
|
+
import java.util.{Optional, Map => JMap}
|
10
|
+
|
11
|
+
import com.amazonaws.services.dynamodbv2.model.{
|
12
|
+
AttributeValue,
|
13
|
+
ReturnConsumedCapacity,
|
14
|
+
Select
|
15
|
+
}
|
16
|
+
import org.embulk.config.{
|
17
|
+
Config,
|
18
|
+
ConfigDefault,
|
19
|
+
ConfigException,
|
20
|
+
Task => EmbulkTask
|
21
|
+
}
|
22
|
+
import org.embulk.input.dynamodb.item.DynamodbAttributeValue
|
23
|
+
|
24
|
+
import scala.jdk.CollectionConverters._
|
25
|
+
import scala.language.reflectiveCalls
|
26
|
+
|
27
|
+
object AbstractDynamodbOperation {
|
28
|
+
|
29
|
+
trait Task extends EmbulkTask {
|
30
|
+
|
31
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.ReadConsistency
|
32
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.ReadConsistency
|
33
|
+
@Config("consistent_read")
|
34
|
+
@ConfigDefault("false")
|
35
|
+
def getConsistentRead: Boolean
|
36
|
+
|
37
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.Pagination
|
38
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.Pagination.html
|
39
|
+
@Config("exclusive_start_key")
|
40
|
+
@ConfigDefault("{}")
|
41
|
+
def getExclusiveStartKey: JMap[String, DynamodbAttributeValue.Task]
|
42
|
+
|
43
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html
|
44
|
+
@Config("expression_attribute_names")
|
45
|
+
@ConfigDefault("{}")
|
46
|
+
def getExpressionAttributeNames: JMap[String, String]
|
47
|
+
|
48
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeValues.html
|
49
|
+
@Config("expression_attribute_values")
|
50
|
+
@ConfigDefault("{}")
|
51
|
+
def getExpressionAttributeValues: JMap[String, DynamodbAttributeValue.Task]
|
52
|
+
|
53
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression
|
54
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression
|
55
|
+
@Config("filter_expression")
|
56
|
+
@ConfigDefault("null")
|
57
|
+
def getFilterExpression: Optional[String]
|
58
|
+
|
59
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html
|
60
|
+
@Config("index_name")
|
61
|
+
@ConfigDefault("null")
|
62
|
+
def getIndexName: Optional[String]
|
63
|
+
|
64
|
+
// NOTE: Use batch_size for Query/Scan limit per 1 Query/Scan.
|
65
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.Limit
|
66
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.Limit
|
67
|
+
@Config("batch_size")
|
68
|
+
@ConfigDefault("null")
|
69
|
+
def getBatchSize: Optional[Int]
|
70
|
+
|
71
|
+
// NOTE: This limit is total records limit, not the limit of Query/Scan request.
|
72
|
+
@Config("limit")
|
73
|
+
@ConfigDefault("null")
|
74
|
+
def getLimit: Optional[JLong]
|
75
|
+
|
76
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
|
77
|
+
@Config("projection_expression")
|
78
|
+
@ConfigDefault("null")
|
79
|
+
def getProjectionExpression: Optional[String]
|
80
|
+
|
81
|
+
// TODO: just reporting ?
|
82
|
+
@Config("return_consumed_capacity")
|
83
|
+
@ConfigDefault("null")
|
84
|
+
def getReturnConsumedCapacity: Optional[ReturnConsumedCapacity]
|
85
|
+
|
86
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-Select
|
87
|
+
// ref. https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-Select
|
88
|
+
@Config("select")
|
89
|
+
@ConfigDefault("null")
|
90
|
+
def getSelect: Optional[Select]
|
91
|
+
|
92
|
+
def getTableName: String
|
93
|
+
def setTableName(tableName: String): Unit
|
94
|
+
}
|
95
|
+
|
96
|
+
type RequestBuilderMethods = {
|
97
|
+
def setConsistentRead(v: JBoolean): Unit
|
98
|
+
def setExclusiveStartKey(v: JMap[JString, AttributeValue]): Unit
|
99
|
+
def setExpressionAttributeNames(v: JMap[JString, JString]): Unit
|
100
|
+
def setExpressionAttributeValues(v: JMap[JString, AttributeValue]): Unit
|
101
|
+
def setFilterExpression(v: JString): Unit
|
102
|
+
def setIndexName(v: JString): Unit
|
103
|
+
def setLimit(v: JInteger): Unit
|
104
|
+
def setProjectionExpression(v: JString): Unit
|
105
|
+
def setReturnConsumedCapacity(v: ReturnConsumedCapacity): Unit
|
106
|
+
def setSelect(v: Select): Unit
|
107
|
+
def setTableName(v: JString): Unit
|
108
|
+
}
|
109
|
+
|
110
|
+
}
|
111
|
+
|
112
|
+
abstract class AbstractDynamodbOperation(
|
113
|
+
task: AbstractDynamodbOperation.Task
|
114
|
+
) extends EmbulkDynamodbOperation {
|
115
|
+
|
116
|
+
protected def calculateLoadableRecords(loadedRecords: Long): Option[Long] = {
|
117
|
+
if (!task.getLimit.isPresent) return None
|
118
|
+
val loadableRecords = task.getLimit.get() - loadedRecords
|
119
|
+
if (loadableRecords <= 0) Option(0L)
|
120
|
+
else Option(loadableRecords)
|
121
|
+
}
|
122
|
+
|
123
|
+
protected def configureRequest[
|
124
|
+
A <: AbstractDynamodbOperation.RequestBuilderMethods
|
125
|
+
](
|
126
|
+
req: A,
|
127
|
+
lastEvaluatedKey: Option[Map[String, AttributeValue]]
|
128
|
+
): Unit = {
|
129
|
+
def attributeValueTaskToAttributeValue(
|
130
|
+
x: (String, DynamodbAttributeValue.Task)
|
131
|
+
): (String, AttributeValue) = {
|
132
|
+
(x._1, DynamodbAttributeValue(x._2).getOriginal)
|
133
|
+
}
|
134
|
+
|
135
|
+
req.setConsistentRead(task.getConsistentRead)
|
136
|
+
lastEvaluatedKey match {
|
137
|
+
case Some(v) => req.setExclusiveStartKey(v.asJava)
|
138
|
+
case None =>
|
139
|
+
if (!task.getExclusiveStartKey.isEmpty)
|
140
|
+
req.setExclusiveStartKey(
|
141
|
+
task.getExclusiveStartKey.asScala
|
142
|
+
.map(attributeValueTaskToAttributeValue)
|
143
|
+
.asJava
|
144
|
+
)
|
145
|
+
}
|
146
|
+
|
147
|
+
if (!task.getExpressionAttributeNames.isEmpty)
|
148
|
+
req.setExpressionAttributeNames(task.getExpressionAttributeNames)
|
149
|
+
if (!task.getExpressionAttributeValues.isEmpty)
|
150
|
+
req.setExpressionAttributeValues(
|
151
|
+
task.getExpressionAttributeValues.asScala
|
152
|
+
.map(attributeValueTaskToAttributeValue)
|
153
|
+
.asJava
|
154
|
+
)
|
155
|
+
task.getFilterExpression.ifPresent(req.setFilterExpression)
|
156
|
+
task.getIndexName.ifPresent(req.setIndexName)
|
157
|
+
task.getBatchSize.ifPresent { v =>
|
158
|
+
if (v <= 0)
|
159
|
+
throw new ConfigException(
|
160
|
+
"\"batch_size\" must be greater than or equal to 1."
|
161
|
+
)
|
162
|
+
req.setLimit(JInteger.valueOf(v)) // Note: Use BatchSize for the limit per a request.
|
163
|
+
}
|
164
|
+
task.getProjectionExpression.ifPresent(req.setProjectionExpression)
|
165
|
+
task.getReturnConsumedCapacity.ifPresent(req.setReturnConsumedCapacity)
|
166
|
+
task.getSelect.ifPresent(req.setSelect)
|
167
|
+
req.setTableName(task.getTableName)
|
168
|
+
}
|
169
|
+
}
|