static async Task <(DynamoDbScoreDataV1 data, string hash)> GetAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new GetItemRequest() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdMainPrefix + score), }, }; var response = await client.GetItemAsync(request); var data = response.Item[DynamoDbScorePropertyNames.Data]; if (data is null) { throw new InvalidOperationException("not found."); } DynamoDbScoreDataV1.TryMapFromAttributeValue(data, out var result); var hash = response.Item[DynamoDbScorePropertyNames.DataHash].S; return(result, hash); }
static async Task <DynamoDbScore> GetAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new GetItemRequest() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdMainPrefix + score), }, }; var response = await client.GetItemAsync(request); if (!response.IsItemSet) { throw new NotFoundScoreException("Not found score."); } var dynamoDbScore = new DynamoDbScore(response.Item); return(dynamoDbScore); }
static async Task <(string itemIdText, Guid itemId, long size)[]> GetItemAndSizeListAsync(IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId) { var partitionKey = ScoreItemDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new QueryRequest() { TableName = tableName, ExpressionAttributeNames = new Dictionary <string, string>() { ["#owner"] = ScoreItemDatabasePropertyNames.OwnerId, ["#item"] = ScoreItemDatabasePropertyNames.ItemId, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":owner"] = new AttributeValue(partitionKey), [":score"] = new AttributeValue(score), }, KeyConditionExpression = "#owner = :owner and begins_with(#item, :score)", }; try { var response = await client.QueryAsync(request); return(response.Items .Where(x => x[ScoreItemDatabasePropertyNames.ItemId].S != ScoreItemDatabaseConstant.ItemIdSummary) .Select(GetItemAndSizeAsync).ToArray()); } catch (Exception ex) { Console.WriteLine(ex.Message); throw; }
static async Task UpdateAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, int[] removeIndices, string newHash, string oldHash, DateTimeOffset now ) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var updateAt = ScoreDatabaseUtils.ConvertToUnixTimeMilli(now); var request = new UpdateItemRequest() { Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdMainPrefix + score), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#updateAt"] = DynamoDbScorePropertyNames.UpdateAt, ["#hash"] = DynamoDbScorePropertyNames.DataHash, ["#data"] = DynamoDbScorePropertyNames.Data, ["#pages"] = DynamoDbScorePropertyNames.DataPropertyNames.Pages, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":newHash"] = new AttributeValue(newHash), [":oldHash"] = new AttributeValue(oldHash), [":updateAt"] = new AttributeValue(updateAt), }, ConditionExpression = "#hash = :oldHash", UpdateExpression = $"SET #updateAt = :updateAt, #hash = :newHash REMOVE {string.Join(", ", removeIndices.Select(i=>$"#data.#pages[{i}]"))}", TableName = tableName, }; try { await client.UpdateItemAsync(request); } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }
static async Task DeleteItemRelations(IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, HashSet <string> items) { var partitionKey = DynamoDbScoreItemRelationUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); const int chunkSize = 25; var chunkList = items.Select((x, index) => (x, index)) .GroupBy(x => x.index / chunkSize) .Select(x => x.Select(y => y.x).ToArray()) .ToArray(); foreach (var ids in chunkList) { await DeleteItemRelations25Async(client, tableName, partitionKey, score, ids); }
CreateDynamoDbValue(ScoreItemDatabaseItemDataBase itemData, DateTimeOffset now) { var items = new Dictionary <string, AttributeValue>(); var partitionKey = ScoreItemDatabaseUtils.ConvertToPartitionKey(itemData.OwnerId); var score = ScoreDatabaseUtils.ConvertToBase64(itemData.ScoreId); var item = ScoreDatabaseUtils.ConvertToBase64(itemData.ItemId); var at = ScoreDatabaseUtils.ConvertToUnixTimeMilli(now); items[ScoreItemDatabasePropertyNames.OwnerId] = new AttributeValue(partitionKey); items[ScoreItemDatabasePropertyNames.ItemId] = new AttributeValue(score + item); items[ScoreItemDatabasePropertyNames.ObjName] = new AttributeValue(itemData.ObjName); items[ScoreItemDatabasePropertyNames.Size] = new AttributeValue { N = itemData.Size.ToString() }; items[ScoreItemDatabasePropertyNames.At] = new AttributeValue(at); var totalSize = itemData.Size; if (itemData is ScoreItemDatabaseItemDataImage itemDataImage) { items[ScoreItemDatabasePropertyNames.Type] = new AttributeValue(ScoreItemDatabaseConstant.TypeImage); items[ScoreItemDatabasePropertyNames.OrgName] = new AttributeValue(itemDataImage.OrgName); items[ScoreItemDatabasePropertyNames.Thumbnail] = new AttributeValue { M = new Dictionary <string, AttributeValue> { [ScoreItemDatabasePropertyNames.ThumbnailPropertyNames.ObjName] = new AttributeValue(itemDataImage.Thumbnail.ObjName), [ScoreItemDatabasePropertyNames.ThumbnailPropertyNames.Size] = new AttributeValue { N = itemDataImage.Thumbnail.Size.ToString() } } }; totalSize += itemDataImage.Thumbnail.Size; } items[ScoreItemDatabasePropertyNames.TotalSize] = new AttributeValue { N = totalSize.ToString() }; return(items, partitionKey, score, item, totalSize); }
static async Task <Dictionary <string, string> > GetAnnotationsAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId) { var partitionKey = DynamoDbScoreDataUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new QueryRequest() { TableName = tableName, ExpressionAttributeNames = new Dictionary <string, string>() { ["#owner"] = DynamoDbScoreDataPropertyNames.OwnerId, ["#data"] = DynamoDbScoreDataPropertyNames.DataId, ["#content"] = DynamoDbScoreDataPropertyNames.Content, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":owner"] = new AttributeValue(partitionKey), [":annScore"] = new AttributeValue(DynamoDbScoreDataConstant.PrefixAnnotation + score), }, KeyConditionExpression = "#owner = :owner and begins_with(#data, :annScore)", ProjectionExpression = "#data, #content", }; try { var response = await client.QueryAsync(request); var result = new Dictionary <string, string>(); var substringStartIndex = DynamoDbScoreDataConstant.PrefixAnnotation.Length + score.Length; foreach (var item in response.Items) { var hashValue = item[DynamoDbScoreDataPropertyNames.DataId]; var hash = hashValue.S.Substring(substringStartIndex); var contentValue = item[DynamoDbScoreDataPropertyNames.Content]; result[hash] = contentValue.S; } return(result); } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }
public async Task DeleteItemRelationsAsync(Guid ownerId, Guid snapshotId, Guid[] itemIds) { const int chunkSize = 25; var partitionKey = DynamoDbScoreItemRelationUtils.ConvertToPartitionKey(ownerId); var snapshot = ScoreDatabaseUtils.ConvertToBase64(snapshotId); var chunkList = itemIds .Select((x, index) => (x: ScoreDatabaseUtils.ConvertToBase64(x) + snapshot, index)) .GroupBy(x => x.index / chunkSize) .Select(x => x.Select(y => y.x).ToArray()) .ToArray(); foreach (var ids in chunkList) { await DeleteData25Async(_dynamoDbClient, ScoreItemRelationTableName, partitionKey, ids); }
static async Task RemoveDataAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, HashSet <string> removeHashSet) { const int chunkSize = 25; var partitionKey = DynamoDbScoreDataUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var chunkList = removeHashSet.Select((h, index) => (h, index)) .GroupBy(x => x.index / chunkSize) .Select(x => x.Select(x => x.h).ToArray()) .ToArray(); foreach (var hashList in chunkList) { await RemoveData25Async(client, tableName, partitionKey, score, hashList); }
static async Task <(bool success, string description)> TryGetDescriptionAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, string descriptionHash) { var partitionKey = DynamoDbScoreDataUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new GetItemRequest() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScoreDataPropertyNames.OwnerId] = new AttributeValue(partitionKey), [DynamoDbScoreDataPropertyNames.DataId] = new AttributeValue(DynamoDbScoreDataConstant.PrefixDescription + score + descriptionHash), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#content"] = DynamoDbScoreDataPropertyNames.Content, }, ProjectionExpression = "#content", }; try { var response = await client.GetItemAsync(request); if (!response.IsItemSet) { return(false, ""); } var description = response.Item[DynamoDbScoreDataPropertyNames.Content].S; return(true, description); } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }
public async Task AddPagesAsync(Guid ownerId, Guid scoreId, List <NewScorePage> pages) { if (pages.Count == 0) { throw new ArgumentException(nameof(pages)); } var(data, oldHash) = await GetAsync(_dynamoDbClient, ScoreTableName, ownerId, scoreId); data.Page ??= new List <DynamoDbScorePageV1>(); var newPages = new List <DynamoDbScorePageV1>(); var newItemRelationSet = new HashSet <string>(); var pageId = data.Page.Count == 0 ? 0 : data.Page.Select(x => x.Id).Max() + 1; foreach (var page in pages) { var itemId = ScoreDatabaseUtils.ConvertToBase64(page.ItemId); var p = new DynamoDbScorePageV1() { Id = pageId++, ItemId = itemId, Page = page.Page, ObjectName = page.ObjectName, }; newPages.Add(p); data.Page.Add(p); newItemRelationSet.Add(itemId); } var newHash = data.CalcDataHash(); var now = ScoreDatabaseUtils.UnixTimeMillisecondsNow(); // TODO ページの追加上限値判定を追加 await UpdateAsync(_dynamoDbClient, ScoreTableName, ownerId, scoreId, newPages, newHash, oldHash, now); await PutItemRelations(_dynamoDbClient, ScoreItemRelationTableName, ownerId, scoreId, newItemRelationSet);
public async Task ReplacePagesAsync(Guid ownerId, Guid scoreId, List <PatchScorePage> pages) { if (pages.Count == 0) { throw new ArgumentException(nameof(pages)); } var(data, oldHash) = await GetAsync(_dynamoDbClient, ScoreTableName, ownerId, scoreId); data.Page ??= new List <DynamoDbScorePageV1>(); var removeRelationItemSet = new HashSet <string>(); var newRelationItemSet = new HashSet <string>(); foreach (var pageV1 in data.Page) { removeRelationItemSet.Add(pageV1.ItemId); } // Key id, Value index var pageIndices = new Dictionary <long, int>(); foreach (var(databaseScoreDataPageV1, index) in data.Page.Select((x, index) => (x, index))) { pageIndices[databaseScoreDataPageV1.Id] = index; } var replacingPages = new List <(DynamoDbScorePageV1 data, int targetIndex)>(); foreach (var page in pages) { var id = page.TargetPageId; if (!pageIndices.TryGetValue(id, out var index)) { throw new InvalidOperationException(); } var itemId = ScoreDatabaseUtils.ConvertToBase64(page.ItemId); var p = new DynamoDbScorePageV1() { Id = id, ItemId = itemId, Page = page.Page, ObjectName = page.ObjectName, }; replacingPages.Add((p, index)); data.Page[index] = p; newRelationItemSet.Add(itemId); } foreach (var pageV1 in data.Page) { removeRelationItemSet.Remove(pageV1.ItemId); } var newHash = data.CalcDataHash(); var now = ScoreDatabaseUtils.UnixTimeMillisecondsNow(); await UpdateAsync(_dynamoDbClient, ScoreTableName, ownerId, scoreId, replacingPages, newHash, oldHash, now); await PutItemRelations(_dynamoDbClient, ScoreItemRelationTableName, ownerId, scoreId, newRelationItemSet); await DeleteItemRelations(_dynamoDbClient, ScoreItemRelationTableName, ownerId, scoreId, removeRelationItemSet);
static async Task <ScoreSnapshotSummary[]> GetAsync(IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var request = new QueryRequest() { TableName = tableName, ExpressionAttributeNames = new Dictionary <string, string>() { ["#owner"] = DynamoDbScorePropertyNames.PartitionKey, ["#score"] = DynamoDbScorePropertyNames.SortKey, ["#snapshotName"] = DynamoDbScorePropertyNames.SnapshotName, ["#createAt"] = DynamoDbScorePropertyNames.CreateAt, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":owner"] = new AttributeValue(partitionKey), [":snapPrefix"] = new AttributeValue(ScoreDatabaseConstant.ScoreIdSnapPrefix + score), }, KeyConditionExpression = "#owner = :owner and begins_with(#score, :snapPrefix)", ProjectionExpression = "#score, #snapshotName, #createAt", }; try { var response = await client.QueryAsync(request); var subStartIndex = (ScoreDatabaseConstant.ScoreIdSnapPrefix + score).Length; return(response.Items .Select(x => ( score: x[DynamoDbScorePropertyNames.SortKey].S, name: x[DynamoDbScorePropertyNames.SnapshotName].S, createAt: x[DynamoDbScorePropertyNames.CreateAt].S) ) .Select(x => new ScoreSnapshotSummary() { Id = ScoreDatabaseUtils.ConvertToGuid(x.score.Substring(subStartIndex)), Name = x.name, CreateAt = ScoreDatabaseUtils.ConvertFromUnixTimeMilli(x.createAt), } ) .ToArray()); } catch (InternalServerErrorException ex) { Console.WriteLine(ex.Message); throw; } catch (ProvisionedThroughputExceededException ex) { Console.WriteLine(ex.Message); throw; } catch (RequestLimitExceededException ex) { Console.WriteLine(ex.Message); throw; } catch (ResourceNotFoundException ex) { Console.WriteLine(ex.Message); throw; } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }
static async Task DeleteItemAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, Guid snapshotId ) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var snapshot = ScoreDatabaseUtils.ConvertToBase64(snapshotId); var actions = new List <TransactWriteItem>() { new TransactWriteItem() { Delete = new Delete() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdSnapPrefix + score + snapshot), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#score"] = DynamoDbScorePropertyNames.SortKey, }, ConditionExpression = "attribute_exists(#score)", }, }, new TransactWriteItem() { Update = new Update() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdMainPrefix + score), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#snapshotCount"] = DynamoDbScorePropertyNames.SnapshotCount, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":increment"] = new AttributeValue() { N = "-1" }, }, UpdateExpression = "ADD #snapshotCount :increment", } }, }; try { await client.TransactWriteItemsAsync(new TransactWriteItemsRequest() { TransactItems = actions, ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL, }); } catch (TransactionCanceledException ex) { var deleteReason = ex.CancellationReasons[0]; if (deleteReason.Code == "ConditionalCheckFailed") { throw new NotFoundSnapshotException(ex); } throw; } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }
/// <summary> /// 楽譜のアイテムデータのパーティションキー /// </summary> /// <param name="ownerId"></param> /// <returns></returns> public static string ConvertToPartitionKey(Guid ownerId) => ScoreItemDatabaseConstant.PartitionKeyPrefix + ScoreDatabaseUtils.ConvertToBase64(ownerId);
static async Task UpdateAsync( IAmazonDynamoDB client, string tableName, Guid ownerId, Guid scoreId, Guid snapshotId, string snapshotName, DateTimeOffset now, int maxSnapshotCount ) { var partitionKey = ScoreDatabaseUtils.ConvertToPartitionKey(ownerId); var score = ScoreDatabaseUtils.ConvertToBase64(scoreId); var snapshot = ScoreDatabaseUtils.ConvertToBase64(snapshotId); var at = ScoreDatabaseUtils.ConvertToUnixTimeMilli(now); var actions = new List <TransactWriteItem>() { new TransactWriteItem() { Update = new Update() { TableName = tableName, Key = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdMainPrefix + score), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#snapshotCount"] = DynamoDbScorePropertyNames.SnapshotCount, }, ExpressionAttributeValues = new Dictionary <string, AttributeValue>() { [":increment"] = new AttributeValue() { N = "1" }, [":countMax"] = new AttributeValue() { N = maxSnapshotCount.ToString(), }, }, ConditionExpression = "#snapshotCount < :countMax", UpdateExpression = "ADD #snapshotCount :increment" } }, new TransactWriteItem() { Put = new Put() { TableName = tableName, Item = new Dictionary <string, AttributeValue>() { [DynamoDbScorePropertyNames.PartitionKey] = new AttributeValue(partitionKey), [DynamoDbScorePropertyNames.SortKey] = new AttributeValue(ScoreDatabaseConstant.ScoreIdSnapPrefix + score + snapshot), [DynamoDbScorePropertyNames.CreateAt] = new AttributeValue(at), [DynamoDbScorePropertyNames.UpdateAt] = new AttributeValue(at), [DynamoDbScorePropertyNames.SnapshotName] = new AttributeValue(snapshotName), }, ExpressionAttributeNames = new Dictionary <string, string>() { ["#score"] = DynamoDbScorePropertyNames.SortKey, }, ConditionExpression = "attribute_not_exists(#score)", } } }; try { await client.TransactWriteItemsAsync(new TransactWriteItemsRequest() { TransactItems = actions, ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL }); } catch (TransactionCanceledException ex) { var updateReason = ex.CancellationReasons[0]; if (updateReason.Code == "ConditionalCheckFailed") { throw new CreatedSnapshotException(CreatedSnapshotExceptionCodes.ExceededUpperLimit, ex); } throw; } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }