Skip to content

Commit 7755c45

Browse files
authored
Fix a bug where DurationAttributeConverter was considering any number past the decimal point as a nanosecond during deserialization (#5725)
1 parent afd2253 commit 7755c45

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "DynamoDB Enhanced Client",
4+
"contributor": "",
5+
"description": "Fix a bug where DurationAttributeConverter was considering any number past the decimal point as a nanosecond during deserialization"
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/DurationAttributeConverter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,25 @@ public Duration convertNumber(String value) {
104104
String[] splitOnDecimal = ConverterUtils.splitNumberOnDecimal(value);
105105

106106
long seconds = Long.parseLong(splitOnDecimal[0]);
107-
int nanoAdjustment = Integer.parseInt(splitOnDecimal[1]);
107+
int nanoAdjustment = Integer.parseInt(padRight(splitOnDecimal[1]));
108108

109109
if (seconds < 0) {
110110
nanoAdjustment = -nanoAdjustment;
111111
}
112112

113113
return Duration.ofSeconds(seconds, nanoAdjustment);
114114
}
115+
116+
private String padRight(String s) {
117+
if (s.length() >= 9) {
118+
return s;
119+
}
120+
StringBuilder padding = new StringBuilder(s);
121+
for (int i = 0; i < 9 - s.length(); i++) {
122+
padding.append("0");
123+
}
124+
125+
return padding.toString();
126+
}
115127
}
116128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute;
17+
18+
import static java.time.temporal.ChronoUnit.MICROS;
19+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
20+
21+
import java.time.Duration;
22+
import java.util.stream.Stream;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
28+
29+
class DurationAttributeConverterTest {
30+
31+
private DurationAttributeConverter converter;
32+
33+
@BeforeEach
34+
void init() {
35+
this.converter = DurationAttributeConverter.create();
36+
}
37+
38+
@ParameterizedTest
39+
@MethodSource("durations")
40+
void testConvertTo(String value, Duration expected) {
41+
Duration converted = converter.transformTo(AttributeValue.builder()
42+
.n(value)
43+
.build());
44+
assertThat(converted).isEqualByComparingTo(expected);
45+
}
46+
47+
@ParameterizedTest
48+
@MethodSource("durations")
49+
void testConvertFrom(String expected, Duration value) {
50+
AttributeValue converted = converter.transformFrom(value);
51+
String actual = converted.n();
52+
assertThat(actual).isEqualTo(expected);
53+
}
54+
55+
@ParameterizedTest
56+
@MethodSource("noPadding")
57+
void testConvertTo_NoPadding(String value, Duration expected) {
58+
Duration converted = converter.transformTo(AttributeValue.builder()
59+
.n(value)
60+
.build());
61+
assertThat(converted).isEqualByComparingTo(expected);
62+
}
63+
64+
static Stream<Arguments> noPadding() {
65+
return Stream.of(
66+
Arguments.of("0.123456789", Duration.ofNanos(123_456_789)),
67+
Arguments.of("0.12345678", Duration.ofNanos(123_456_780)),
68+
Arguments.of("0.1234567", Duration.ofNanos(123_456_700)),
69+
Arguments.of("0.123456", Duration.of(123_456, MICROS)),
70+
Arguments.of("0.12345", Duration.of(123_450, MICROS)),
71+
Arguments.of("0.1234", Duration.of(123_400, MICROS)),
72+
Arguments.of("0.123", Duration.ofMillis(123)),
73+
Arguments.of("0.12", Duration.ofMillis(120)),
74+
Arguments.of("0.1", Duration.ofMillis(100)),
75+
Arguments.of("0.001", Duration.ofMillis(1)),
76+
Arguments.of("0.000001", Duration.of(1, MICROS)),
77+
Arguments.of("0.001", Duration.ofMillis(1))
78+
);
79+
}
80+
81+
static Stream<Arguments> durations() {
82+
return Stream.of(
83+
Arguments.of("0", Duration.ofSeconds(0)),
84+
85+
Arguments.of("0.123456789", Duration.ofNanos(123_456_789)),
86+
Arguments.of("0.123456780", Duration.ofNanos(123_456_780)),
87+
Arguments.of("0.123456700", Duration.ofNanos(123_456_700)),
88+
Arguments.of("0.123456000", Duration.of(123_456, MICROS)),
89+
Arguments.of("0.123450000", Duration.of(123_450, MICROS)),
90+
Arguments.of("0.123400000", Duration.of(123_400, MICROS)),
91+
Arguments.of("0.123000000", Duration.ofMillis(123)),
92+
Arguments.of("0.120000000", Duration.ofMillis(120)),
93+
Arguments.of("0.100000000", Duration.ofMillis(100)),
94+
95+
Arguments.of("0.123456789", Duration.ofNanos(123_456_789)),
96+
Arguments.of("0.012345678", Duration.ofNanos(12_345_678)),
97+
Arguments.of("0.001234567", Duration.ofNanos(1_234_567)),
98+
Arguments.of("0.000123456", Duration.ofNanos(123_456)),
99+
Arguments.of("0.000012345", Duration.ofNanos(12_345)),
100+
Arguments.of("0.000001234", Duration.ofNanos(1_234)),
101+
Arguments.of("0.000000123", Duration.ofNanos(123)),
102+
Arguments.of("0.000000012", Duration.ofNanos(12)),
103+
Arguments.of("0.000000001", Duration.ofNanos(1)),
104+
105+
Arguments.of("12345678", Duration.ofSeconds(12345678)),
106+
Arguments.of("86400", Duration.ofDays(1)),
107+
Arguments.of("9", Duration.ofSeconds(9)),
108+
Arguments.of("-9", Duration.ofSeconds(-9)),
109+
Arguments.of("0.001000000", Duration.ofMillis(1)),
110+
Arguments.of("0.000000001", Duration.ofNanos(1)));
111+
}
112+
113+
}

0 commit comments

Comments
 (0)