ferenc-csaky commented on code in PR #253:
URL:
https://github.com/apache/flink-connector-aws/pull/253#discussion_r3855594487
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/DynamoDbDynamicSink.java:
##########
@@ -142,6 +149,7 @@ public static class DynamoDbDynamicTableSinkBuilder
private Properties dynamoDbClientProperties;
private DataType physicalDataType;
private Set<String> overwriteByPartitionKeys;
+ private List<String> primaryKey = Collections.emptyList();
Review Comment:
Do not see the relevance of the default value. The factory will set this
properly, which is why it's omitted for all the other fields. I suggest to
comply with that.
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/DynamoDbDynamicSink.java:
##########
@@ -77,6 +81,7 @@ protected DynamoDbDynamicSink(
this.dynamoDbClientProperties = dynamoDbClientProperties;
this.physicalDataType = physicalDataType;
this.overwriteByPartitionKeys = overwriteByPartitionKeys;
+ this.primaryKey = primaryKey == null ? Collections.emptyList() :
primaryKey;
Review Comment:
Do not see the relevance of the null check here. This ctor is only called
from the builder, which is only called from the factory, which prevalidates
these fields, which I guess is why it's omitted for all the other fields. I
suggest to comply with that.
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/DynamoDbDynamicSinkFactory.java:
##########
@@ -48,14 +52,53 @@ public DynamicTableSink createDynamicTableSink(Context
context) {
DynamoDbConfiguration dynamoDbConfiguration =
new DynamoDbConfiguration(catalogTable.getOptions(),
factoryHelper.getOptions());
+ List<String> primaryKey =
+ catalogTable
+ .getResolvedSchema()
+ .getPrimaryKey()
+ .map(UniqueConstraint::getColumns)
+ .orElse(Collections.emptyList());
+
+ if (primaryKey.size() > 2) {
+ throw new ValidationException(
+ String.format(
+ "The DynamoDB sink supports a PRIMARY KEY of at
most two columns (a "
+ + "partition key and an optional sort
key), but %d columns were "
+ + "declared: %s. Please declare a PRIMARY
KEY that matches the "
+ + "DynamoDB table's key schema.",
+ primaryKey.size(), primaryKey));
+ }
+
+ List<String> declaredPartitionKeys = catalogTable.getPartitionKeys();
+
+ // When both are declared they must match; otherwise a CDC batch could
keep an upsert and a
+ // delete that map to the same DynamoDB key, which DynamoDB rejects as
duplicates.
+ if (!declaredPartitionKeys.isEmpty()
+ && !primaryKey.isEmpty()
+ && !new HashSet<>(declaredPartitionKeys).equals(new
HashSet<>(primaryKey))) {
+ throw new ValidationException(
+ String.format(
+ "When both PARTITIONED BY and PRIMARY KEY are
specified for a DynamoDB "
+ + "table they must reference the same
columns, but PARTITIONED "
+ + "BY was %s and PRIMARY KEY was %s.
Either align them or "
+ + "specify only the PRIMARY KEY.",
+ declaredPartitionKeys, primaryKey));
+ }
+
+ Set<String> overwriteByPartitionKeys = new
HashSet<>(declaredPartitionKeys);
+ if (overwriteByPartitionKeys.isEmpty()) {
+ overwriteByPartitionKeys = new HashSet<>(primaryKey);
+ }
Review Comment:
I think it makes sense to extract these into 2 separate private methods that
deals with the validation and only returns the value, so the general property
flow is more clean.
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/DynamoDbDynamicSinkFactory.java:
##########
@@ -48,14 +52,53 @@ public DynamicTableSink createDynamicTableSink(Context
context) {
DynamoDbConfiguration dynamoDbConfiguration =
new DynamoDbConfiguration(catalogTable.getOptions(),
factoryHelper.getOptions());
+ List<String> primaryKey =
+ catalogTable
+ .getResolvedSchema()
+ .getPrimaryKey()
+ .map(UniqueConstraint::getColumns)
+ .orElse(Collections.emptyList());
Review Comment:
nit: Collections.emptyList() -> List.of()
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/RowDataToAttributeValueConverter.java:
##########
@@ -51,15 +53,32 @@ public class RowDataToAttributeValueConverter {
private final DataType physicalDataType;
private final TableSchema<RowData> tableSchema;
+
+ /**
+ * Ordered primary key attribute names. Following DynamoDB's primary key
definition, the first
+ * element is the partition key and the optional second element is the
sort key. Used to build
+ * the key of a {@code DeleteRequest}, which must contain only the primary
key attributes.
+ */
+ private final List<String> primaryKey;
+
private boolean ignoreNulls = false;
public RowDataToAttributeValueConverter(DataType physicalDataType) {
- this.physicalDataType = physicalDataType;
- this.tableSchema = createTableSchema();
+ this(physicalDataType, Collections.emptyList(), false);
}
public RowDataToAttributeValueConverter(DataType physicalDataType, boolean
ignoreNulls) {
+ this(physicalDataType, Collections.emptyList(), ignoreNulls);
+ }
+
+ public RowDataToAttributeValueConverter(DataType physicalDataType,
List<String> primaryKey) {
+ this(physicalDataType, primaryKey, false);
+ }
+
+ public RowDataToAttributeValueConverter(
+ DataType physicalDataType, List<String> primaryKey, boolean
ignoreNulls) {
this.physicalDataType = physicalDataType;
+ this.primaryKey = primaryKey == null ? Collections.emptyList() :
primaryKey;
Review Comment:
Same as my other comments about the null-check
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/RowDataElementConverter.java:
##########
@@ -37,39 +40,55 @@ public class RowDataElementConverter implements
ElementConverter<RowData, Dynamo
private boolean ignoreNulls = false;
private final DataType physicalDataType;
+ private final List<String> primaryKey;
private transient RowDataToAttributeValueConverter
rowDataToAttributeValueConverter;
public RowDataElementConverter(DataType physicalDataType) {
- this.physicalDataType = physicalDataType;
- this.rowDataToAttributeValueConverter =
- new RowDataToAttributeValueConverter(physicalDataType);
+ this(physicalDataType, Collections.emptyList(), false);
}
public RowDataElementConverter(DataType physicalDataType, boolean
ignoreNulls) {
- this.ignoreNulls = ignoreNulls;
+ this(physicalDataType, Collections.emptyList(), ignoreNulls);
+ }
+
+ public RowDataElementConverter(DataType physicalDataType, List<String>
primaryKey) {
+ this(physicalDataType, primaryKey, false);
+ }
+
+ public RowDataElementConverter(
+ DataType physicalDataType, List<String> primaryKey, boolean
ignoreNulls) {
this.physicalDataType = physicalDataType;
+ this.primaryKey = primaryKey == null ? Collections.emptyList() :
primaryKey;
Review Comment:
Since this already gets the safe primaryKeys, technically the null-check can
be omitted, so I suggest to do so, as no extra validation is done for the other
fields too.
##########
flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/table/RowDataToAttributeValueConverter.java:
##########
@@ -68,6 +87,26 @@ public Map<String, AttributeValue> convertRowData(RowData
row) {
return tableSchema.itemToMap(row, ignoreNulls);
}
+ /**
+ * Builds a map containing only the primary key attributes of the given
row. This is used for
+ * {@code DELETE} requests, where DynamoDB requires the request to contain
only the primary key
+ * (partition key and, if present, sort key) rather than the whole item.
+ *
+ * @param row the row to extract the primary key from
+ * @return a map of the primary key attribute names to their {@link
AttributeValue}s
+ */
+ public Map<String, AttributeValue> convertRowDataToKey(RowData row) {
+ Map<String, AttributeValue> item = tableSchema.itemToMap(row,
ignoreNulls);
+ Map<String, AttributeValue> key = new LinkedHashMap<>();
+ for (String keyAttributeName : primaryKey) {
+ AttributeValue value = item.get(keyAttributeName);
+ if (value != null) {
Review Comment:
If for some reason `value` is null here, shouldn't we throw an exception
here instead of swallowing it and possibly return an empty or invalid map here?
I guess it will fail on the DynamoDB side anyways, but we can probably
pinpoint the anomaly a bit more accurate if we throw an exception here,
probably `IllegalArgumentException` or something like that.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]