技術

DynamoDBのupdate構文がわかりづらい

DyanamoDBを触る度に思うのですが、相変わらずupdateの書き方難しいです。

なんでこんなにわかりづらくなっているのでしょうか。もっと何とかならなかったのでしょうか。毎回、忘れてググっている気がするのでまとめておきます。

DynamoDBのupdate構文 – TyepScript –

DynamoDBのupdateは、UpdateExpressionでカラム、値とも変数を割り当てたupdate構文を書き、ExpressionAttributeNamesでUpdateExpressionで定義したカラム用の変数にカラム名をセット、ExpressionAttributeValuesでUpdateExpressionで定義した値用の変数に値をセットするような書き方になっています。

TypeScriptの場合、以下の様に書きます。

const ddbClient = new DocumentClient({ region: region })

const params: updateItemInput = {
    TableName: this.tableName,
    Key: { pkey: pkey, skey: skey },
    UpdateExpression: 'SET #columnName = :columnValue, #updatedAt = :updatedAt'
    ExpressionAttributeNames: { '#columnName': 'columnName', '#updatedAt': 'updatedAt' },
    ExpressionAttributeValues: { ':columnValue': columnValue, ':updatedAt': getNowJst() },
}
const result = await ddbClient.update(params).promise()
}

DynamoDBのupdate構文 – Python-

Pythonの場合はvalueの所に、データ型に対応する文字列を入れた辞書にしないといけないので、さらに複雑になっています。

now = datetime.get_now_str()
response: dict = DynamoDbImpl.client.update_item(
    TableName=DynamoDbImpl.table_name,
    Key={"pkey": {"S": pkey}, "skey": {"S": skey}},
    UpdateExpression="set #updatedAt = :updatedAt, #column = :value",
    ExpressionAttributeNames={"#updatedAt": "updatedAt", "#column": column},
    ExpressionAttributeValues={":updatedAt": {"S": now}, ":value": {"S": value}},
)

Pythonで不特定数のカラムを更新したい場合

TypeScriptならともかく、Pythonの場合はデータ型の文字列も入れないといけないので、更新カラムを柔軟に変えたい場合、ややこしいことになります。以下の様な書き方になります。

from typing import Generic, TypeVar
import boto3

client = boto3.client("dynamodb")
table_name = "xxx"

T = TypeVar("T")

def get_dynamo_data_type_char(value: Generic[T]) -> str:
    if type(value) is str:
        return "S"
    if type(value) is int:
        return "N"
    if type(value) is bool:
        return "BOOL"
    if type(value) is list:
        return "L"

def list_to_dynamo_list(list_str: list[str]):
    """
    Convert to list[str] to DynamoDB list
    DynamoDB list format: [{"S": "value"},{"S": "value"}]

    ex. ["hello", "world"] => [{"S":"hello"},{"S":"world"}]
    """
    dynamo_list = []
    for ls in list_str:
        dynamo_list.append({"S": ls})
    return dynamo_list

def update(self, pkey: str, skey: str, update_item: dict) -> None:
    update_expression = "set #updatedAt = :updatedAt"
    expression_attribute_names = {"#updatedAt": "updatedAt"}
    expression_attribute_values = {":updatedAt": {"S": "2022-07-18 19:00:00"}}

    for i, v in enumerate(update_item):
        update_expression += f", #{v} = :value{str(i)}"
        expression_attribute_names[f"#{v}"] = v

        # if value is list, must be changed for dynamo db format, like [{"S": value}]
        # if value is number, must be changed in str
        if type(update_item[v]) is list:
            value = list_to_dynamo_list(update_item[v])
        elif type(update_item[v]) is int:
            value = str(update_item[v])
        else:
            value = update_item[v]

        expression_attribute_values[f":value{str(i)}"] = {
            get_dynamo_data_type_char(update_item[v]): update_item[v]
        }

    client.update_item(
        TableName=table_name,
        Key={"pkey": {"S": pkey}, "skey": {"S": skey}},
        UpdateExpression=update_expression,
        ExpressionAttributeNames=expression_attribute_names,
        ExpressionAttributeValues=expression_attribute_values,
    )

パッと見、何やっているかわからないコードになってしまいます。が、こんな感じでしか書けないと思います。

まとめ

以上、DynamoDBのupdate構文についてでした。DynamoDBでupdate書かないといけなくなる度にうんざりしていたので、不満も含めて、カッとなって書いてしまいました😇