Skip to content

Commit ab105c8

Browse files
Merge remote-tracking branch 'origin/main-staging' into local-staged-release
2 parents 40eed7b + 7005936 commit ab105c8

File tree

10 files changed

+170
-18
lines changed

10 files changed

+170
-18
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "DynamoDBv2",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Fixed an issue where for a Nullable DateTime property decorated with StoreAsEpochLong attribute, null value was incorrectly stored as -62135596800 in DynamoDB."
8+
]
9+
}
10+
]
11+
}

sdk/src/Services/DynamoDBv2/Custom/AWSConfigs.DynamoDB.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ public partial class PropertyConfig
338338
/// </summary>
339339
public bool StoreAsEpoch { get; set; }
340340

341+
/// <summary>
342+
/// Whether this property should be stored as epoch seconds integer (with support for dates AFTER 2038).
343+
/// </summary>
344+
public bool StoreAsEpochLong { get; set; }
345+
341346
/// <summary>
342347
/// Initializes a PropertyConfig object for a specific property
343348
/// </summary>
@@ -356,6 +361,7 @@ internal PropertyConfig(PropertyConfigElement prop)
356361
Version = prop.Version.GetValueOrDefault(false);
357362
Converter = prop.Converter;
358363
StoreAsEpoch = prop.StoreAsEpoch.GetValueOrDefault(false);
364+
StoreAsEpochLong = prop.StoreAsEpochLong.GetValueOrDefault(false);
359365
}
360366
#endif
361367
}
@@ -582,6 +588,7 @@ internal class PropertyConfigElement : SerializableConfigurationElement
582588
private const string versionKey = "version";
583589
private const string converterKey = "converter";
584590
private const string storeAsEpochKey = "storeAsEpoch";
591+
private const string storeAsEpochLongKey = "storeAsEpochLong";
585592

586593
[ConfigurationProperty(nameKey, IsRequired = true)]
587594
public string Name
@@ -625,6 +632,13 @@ public bool? StoreAsEpoch
625632
get { return (bool?)this[storeAsEpochKey]; }
626633
set { this[storeAsEpochKey] = value; }
627634
}
635+
636+
[ConfigurationProperty(storeAsEpochLongKey)]
637+
public bool? StoreAsEpochLong
638+
{
639+
get { return (bool?)this[storeAsEpochLongKey]; }
640+
set { this[storeAsEpochLongKey] = value; }
641+
}
628642
}
629643

630644
/// <summary>

sdk/src/Services/DynamoDBv2/Custom/DataModel/InternalModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ private static void PopulateConfigFromMappings(ItemStorageConfig config, Diction
932932
propertyStorage.IsIgnored = propertyConfig.Ignore;
933933
propertyStorage.IsVersion = propertyConfig.Version;
934934
propertyStorage.StoreAsEpoch = propertyConfig.StoreAsEpoch;
935+
propertyStorage.StoreAsEpochLong = propertyConfig.StoreAsEpochLong;
935936
}
936937
}
937938
}

sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Document.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ internal static DynamoDBEntry DateTimeToEpochSecondsLong(DynamoDBEntry entry, st
315315
{
316316
try
317317
{
318+
var primitiveValue = entry.AsPrimitive();
319+
320+
// entry.AsPrimitive() could return null for UnconvertedDynamoDBEntry. UnconvertedDynamoDBEntry will always have a value due to check in it's constructor. Hence, we can process it further for epoch conversion.
321+
if (primitiveValue != null
322+
&& primitiveValue.Value == null)
323+
return entry;
324+
318325
var dateTime = entry.AsDateTime();
319326
string epochSecondsAsString = AWSSDKUtils.ConvertToUnixEpochSecondsString(dateTime);
320327
entry = new Primitive(epochSecondsAsString, saveAsNumeric: true);

sdk/src/Services/DynamoDBv2/Custom/DocumentModel/ExpectedState.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap()
197197
/// <returns></returns>
198198
public Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap(DynamoDBEntryConversion conversion)
199199
{
200-
return ToExpectedAttributeMap(conversion, epochAttributes: null, isEmptyStringValueEnabled: false);
200+
return ToExpectedAttributeMap(conversion, epochAttributes: null, epochLongAttributes: null, isEmptyStringValueEnabled: false);
201201
}
202202

203203
/// <summary>
@@ -207,11 +207,11 @@ public Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap(DynamoD
207207
/// <returns></returns>
208208
public Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap(Table table)
209209
{
210-
return ToExpectedAttributeMap(table.Conversion, table.StoreAsEpoch, table.IsEmptyStringValueEnabled);
210+
return ToExpectedAttributeMap(table.Conversion, table.StoreAsEpoch, table.StoreAsEpochLong, table.IsEmptyStringValueEnabled);
211211
}
212212

213213
private Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap(DynamoDBEntryConversion conversion,
214-
IEnumerable<string> epochAttributes, bool isEmptyStringValueEnabled)
214+
IEnumerable<string> epochAttributes, IEnumerable<string> epochLongAttributes, bool isEmptyStringValueEnabled)
215215
{
216216
Dictionary<string, ExpectedAttributeValue> ret = new Dictionary<string, ExpectedAttributeValue>();
217217

@@ -226,6 +226,11 @@ private Dictionary<string, ExpectedAttributeValue> ToExpectedAttributeMap(Dynamo
226226
var values = expectedValue.Values.Select(p => Document.DateTimeToEpochSeconds(p, attributeName)).ToList();
227227
eav = ExpectedValue.ToExpectedAttributeValue(expectedValue.Exists, values, expectedValue.Comparison, conversion, isEmptyStringValueEnabled);
228228
}
229+
else if(epochLongAttributes != null && epochLongAttributes.Contains(attributeName))
230+
{
231+
var values = expectedValue.Values.Select(p => Document.DateTimeToEpochSecondsLong(p, attributeName)).ToList();
232+
eav = ExpectedValue.ToExpectedAttributeValue(expectedValue.Exists, values, expectedValue.Comparison, conversion, isEmptyStringValueEnabled);
233+
}
229234
else
230235
{
231236
eav = expectedValue.ToExpectedAttributeValue(conversion, isEmptyStringValueEnabled);

sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Expression.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ internal static Dictionary<string, AttributeValue> ConvertToAttributeValues(
236236
if (table.StoreAsEpoch.Contains(attributeName))
237237
entry = Document.DateTimeToEpochSeconds(entry, attributeName);
238238

239+
if (table.StoreAsEpochLong.Contains(attributeName))
240+
entry = Document.DateTimeToEpochSecondsLong(entry, attributeName);
241+
239242
var attributeConversionConfig = new DynamoDBEntry.AttributeConversionConfig(table.Conversion, table.IsEmptyStringValueEnabled);
240243
convertedValues[attributeName] = entry.ConvertToAttributeValue(attributeConversionConfig);
241244
}

sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Filter.cs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConv
115115
/// <returns></returns>
116116
public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConvertToEpochSeconds, string attributeName)
117117
{
118-
return ToCondition(conversion, shouldConvertToEpochSeconds, attributeName, false);
118+
return ToCondition(conversion, shouldConvertToEpochSeconds, false, attributeName, false);
119119
}
120120

121121
/// <summary>
@@ -128,6 +128,46 @@ public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConv
128128
/// <returns></returns>
129129
public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConvertToEpochSeconds,
130130
string attributeName, bool isEmptyStringValueEnabled)
131+
{
132+
return ToCondition(conversion, shouldConvertToEpochSeconds, false, attributeName, isEmptyStringValueEnabled);
133+
}
134+
135+
/// <summary>
136+
/// Converts the FilterCondition to the Amazon.DynamoDBv2.Model.Condition object.
137+
/// </summary>
138+
/// <param name="conversion"></param>
139+
/// <param name="shouldConvertToEpochSeconds"></param>
140+
/// <param name="shouldConvertToEpochSecondsLong"></param>
141+
/// <returns></returns>
142+
public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConvertToEpochSeconds, bool shouldConvertToEpochSecondsLong)
143+
{
144+
return ToCondition(conversion, shouldConvertToEpochSeconds: false, attributeName: null, isEmptyStringValueEnabled: shouldConvertToEpochSeconds);
145+
}
146+
147+
/// <summary>
148+
/// Converts the FilterCondition to the Amazon.DynamoDBv2.Model.Condition object.
149+
/// </summary>
150+
/// <param name="conversion"></param>
151+
/// <param name="shouldConvertToEpochSeconds"></param>
152+
/// <param name="shouldConvertToEpochSecondsLong"></param>
153+
/// <param name="attributeName"></param>
154+
/// <returns></returns>
155+
public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConvertToEpochSeconds, bool shouldConvertToEpochSecondsLong, string attributeName)
156+
{
157+
return ToCondition(conversion, shouldConvertToEpochSeconds, attributeName, false);
158+
}
159+
160+
/// <summary>
161+
/// Converts the FilterCondition to the Amazon.DynamoDBv2.Model.Condition object.
162+
/// </summary>
163+
/// <param name="conversion"></param>
164+
/// <param name="shouldConvertToEpochSeconds"></param>
165+
/// <param name="shouldConvertToEpochSecondsLong"></param>
166+
/// <param name="attributeName"></param>
167+
/// <param name="isEmptyStringValueEnabled"></param>
168+
/// <returns></returns>
169+
public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConvertToEpochSeconds, bool shouldConvertToEpochSecondsLong,
170+
string attributeName, bool isEmptyStringValueEnabled)
131171
{
132172
var attributeValues = AttributeValues;
133173
if (attributeValues == null)
@@ -138,7 +178,10 @@ public Condition ToCondition(DynamoDBEntryConversion conversion, bool shouldConv
138178
var entry = DynamoDBEntries[i];
139179
if (shouldConvertToEpochSeconds)
140180
entry = Document.DateTimeToEpochSeconds(entry, attributeName);
141-
181+
182+
if (shouldConvertToEpochSecondsLong)
183+
entry = Document.DateTimeToEpochSecondsLong(entry, attributeName);
184+
142185
var attributeConversionConfig = new DynamoDBEntry.AttributeConversionConfig(conversion, isEmptyStringValueEnabled);
143186
var attributeValue = entry.ConvertToAttributeValue(attributeConversionConfig);
144187
attributeValues.Add(attributeValue);
@@ -192,7 +235,7 @@ public Dictionary<string, Condition> ToConditions()
192235
/// <returns>Map from attribute name to condition</returns>
193236
public Dictionary<string, Condition> ToConditions(DynamoDBEntryConversion conversion)
194237
{
195-
return ToConditions(conversion, epochAttributes: null, isEmptyStringValueEnabled: false);
238+
return ToConditions(conversion, epochAttributes: null, epochLongAttributes: null, isEmptyStringValueEnabled: false);
196239
}
197240

198241
/// <summary>
@@ -203,7 +246,7 @@ public Dictionary<string, Condition> ToConditions(DynamoDBEntryConversion conver
203246
/// <returns>Map from attribute name to condition</returns>
204247
public Dictionary<string, Condition> ToConditions(DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled)
205248
{
206-
return ToConditions(conversion, epochAttributes: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled);
249+
return ToConditions(conversion, epochAttributes: null, epochLongAttributes: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled);
207250
}
208251

209252
/// <summary>
@@ -213,19 +256,20 @@ public Dictionary<string, Condition> ToConditions(DynamoDBEntryConversion conver
213256
/// <returns>Map from attribute name to condition</returns>
214257
public Dictionary<string, Condition> ToConditions(Table table)
215258
{
216-
return ToConditions(table.Conversion, table.StoreAsEpoch, table.IsEmptyStringValueEnabled);
259+
return ToConditions(table.Conversion, table.StoreAsEpoch, table.StoreAsEpochLong, table.IsEmptyStringValueEnabled);
217260
}
218261

219262
private Dictionary<string, Condition> ToConditions(DynamoDBEntryConversion conversion,
220-
IEnumerable<string> epochAttributes, bool isEmptyStringValueEnabled)
263+
IEnumerable<string> epochAttributes, IEnumerable<string> epochLongAttributes, bool isEmptyStringValueEnabled)
221264
{
222265
var dic = new Dictionary<string, Condition>();
223266
foreach (var kvp in Conditions)
224267
{
225268
string name = kvp.Key;
226269
FilterCondition fc = kvp.Value;
227270
bool convertToEpochSeconds = epochAttributes != null && epochAttributes.Contains(name);
228-
Condition condition = fc.ToCondition(conversion, convertToEpochSeconds, name, isEmptyStringValueEnabled);
271+
bool convertToEpochSecondsLong = epochLongAttributes != null && epochLongAttributes.Contains(name);
272+
Condition condition = fc.ToCondition(conversion, convertToEpochSeconds, convertToEpochSecondsLong, name, isEmptyStringValueEnabled);
229273

230274
dic[name] = condition;
231275
}

sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ internal Key MakeKey(Primitive hashKey, Primitive rangeKey)
258258
if (this.StoreAsEpoch.Contains(hashKeyName))
259259
hashKey = KeyDateTimeToEpochSeconds(hashKey, hashKeyName);
260260

261+
if (this.StoreAsEpochLong.Contains(hashKeyName))
262+
hashKey = KeyDateTimeToEpochSecondsLong(hashKey, hashKeyName);
263+
261264
if (hashKeyDescription.Type != hashKey.Type)
262265
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
263266
"Schema for table {0}, hash key {1}, is inconsistent with specified hash key value.", TableName, hashKeyName));
@@ -279,6 +282,9 @@ internal Key MakeKey(Primitive hashKey, Primitive rangeKey)
279282
if (this.StoreAsEpoch.Contains(rangeKeyName))
280283
rangeKey = KeyDateTimeToEpochSeconds(rangeKey, rangeKeyName);
281284

285+
if (this.StoreAsEpochLong.Contains(rangeKeyName))
286+
rangeKey = KeyDateTimeToEpochSecondsLong(rangeKey, rangeKeyName);
287+
282288
if (rangeKeyDescription.Type != rangeKey.Type)
283289
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
284290
"Schema for table {0}, range key {1}, is inconsistent with specified range key value.", TableName, hashKeyName));
@@ -297,6 +303,13 @@ private static Primitive KeyDateTimeToEpochSeconds(Primitive key, string attribu
297303
return converted;
298304
}
299305

306+
private static Primitive KeyDateTimeToEpochSecondsLong(Primitive key, string attributeName)
307+
{
308+
DynamoDBEntry entry = Document.DateTimeToEpochSecondsLong(key, attributeName);
309+
Primitive converted = entry as Primitive;
310+
return converted;
311+
}
312+
300313
internal void UserAgentRequestEventHandlerSync(object sender, RequestEventArgs args)
301314
{
302315
UserAgentRequestEventHandler(sender, args, false);
@@ -469,7 +482,7 @@ public static Table LoadTable(IAmazonDynamoDB ddbClient, TableConfig config)
469482
/// <returns><see cref="DynamoDBEntryType"/> if it can be determined for the given property, otherwise an exception will be thrown</returns>
470483
private static DynamoDBEntryType GetPrimitiveEntryTypeForProperty(PropertyStorage property, DynamoDBFlatConfig flatConfig)
471484
{
472-
if (property.StoreAsEpoch)
485+
if (property.StoreAsEpoch || property.StoreAsEpochLong)
473486
{
474487
return DynamoDBEntryType.Numeric;
475488
}

0 commit comments

Comments
 (0)