embulk-input-dynamodb 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|