背景
DynamoDB を使う為の、よく使うコマンドの操作例と、起きたエラーの対処などの記録
- GetCommand
- PutCommand
- QueryCommand
- ScanCommand
- UpdateCommand
利用例
前提
AWS SDK V3 に記載のある Client ファイルを使う
以下 2ファイルを利用
プロキシを利用する場合は、DynamoDBClient のところで設定しておく
// Create the DynamoDB service client module using ES6 syntax.
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { NodeHttpHandler } from '@aws-sdk/node-http-handler';
// import { HttpsProxyAgent } from 'hpagent';
// const httpsAgent = new HttpsProxyAgent({ proxy: 'http://10.20.30.40:8080' });
// Set the AWS Region.
export const REGION = "ap-northeast-1";
// Create an Amazon DynamoDB service client object.
export const ddbClient = new DynamoDBClient({
region: REGION,
// requestHandler: new NodeHttpHandler({
// httpAgent: httpsAgent,
// httpsAgent: httpsAgent
// })
});
// Create a service client module using ES6 syntax.
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { ddbClient } from "./ddbClient";
const marshallOptions = {
// Whether to automatically convert empty strings, blobs, and sets to `null`.
convertEmptyValues: false, // false, by default.
// Whether to remove undefined values while marshalling.
removeUndefinedValues: false, // false, by default.
// Whether to convert typeof object to map attribute.
convertClassInstanceToMap: false, // false, by default.
};
const unmarshallOptions = {
// Whether to return numbers as a string instead of converting them to native JavaScript numbers.
wrapNumbers: false, // false, by default.
};
const translateConfig = { marshallOptions, unmarshallOptions };
// Create the DynamoDB document client.
const ddbDocClient = DynamoDBDocumentClient.from(ddbClient, translateConfig);
export { ddbDocClient };
テーブル
Tips
Sort: 昇順・降順
以下のように ScanIndexForward 使えばOK。
const queryCommandInput = {
Limit: 1, // 取得数。like "TOP"
ScanIndexForward: false, // false: DESC, true: ASC
};
属性が格納されてるか?の確認方法
以下注意
- attribute_exists()
- id 入ってるか?で見ると、消した場合に、(empty) として、属性は存在しているので反応してしまう
- Artist/Enterpreneur みたいに、複数の Table を単一Tableで保存する場合に、特定の属性があるか?を見るために使うのかな
- size()
- 対象属性の内容が一定文字数以上、とかで調べられるので、こちらが良さそう
stream/db
- SDK.libClient で取得する場合は、低層APIを wrap してるので、気にしなくてOK
- Stream から取得した場合、以下のように型意識が必要
Number(dynamodb.Keys.ID.N ?? 0)
Boolean(dynamodb.Keys.IsVaild.BOOL)
- 途中で取得すると、string
PutCommand
Sort Key の属性名の "#" を入れてるけど、以下な感じで文字列化してやれば問題なく行けた。
同一キーに対しては上書きなので、特定の属性だけ上書きしたい場合には、UpdateCommand を
const putRecord = async (date: Date, postID: number) => {
const timestamp = date.getTime();
const params = {
TableName: "TestCompositeKey",
Item: {
uid: "user02",
"timestamp#post_id": `${timestamp}#${postID}`,
timestamp: timestamp,
timestampString: date.toLocaleString(),
post_id: postID,
},
};
const result = await ddbDocClient.send(new PutCommand(params));
}
取得する際に困ったときは以下
UpdateCommand
Put だと Key が同じ場合に完全に置換されてしまうので、
一部属性値の置換や追加が行いたい時はこちらを使う
const updateItem = async (content: any) => {
const baseParams = {
TableName: "TestCompositeKey",
Key: {
uid: content.PK,
"timestamp#post_id": content.SK,
},
ExpressionAttributeNames: {
"#data": "data",
},
ExpressionAttributeValues: {
":data": content.data,
},
UpdateExpression: `SET #data = :data ${(content.data2 ? ", #data2=:data2" : "")}`,
};
const mergeData2 = {
ExpressionAttributeNames: {
"#data2": "data2",
},
ExpressionAttributeValues: {
":data2": content.data2
},
};
const params = (content.data2) ?
_.merge(baseParams, mergeData2) :
baseParams;
return await ddbDocClient.send(new UpdateCommand(params));
};
上の例の詳細は以下で
これだけだと、動的といっても想定のプロパティによって切り替えるだけなので、以下で動的プロパティの場合を追記
動的に渡された項目だけの更新式を生成させる
GetCommand
PutCommand と大して変わらないので、特に問題無し
const getRecord = async (PK: string, compositeSK: string) => {
const params = {
TableName: "TestCompositeKey",
Key: {
uid: PK,
"timestamp#post_id": compositeSK,
},
}
return (await ddbDocClient.send(new GetCommand(params))).Item;
}
ScanCommand
基本全部取るのが Scan だけど、今回は Filter をかけて取得した例
以下、注意
フィルタ式は、Scan の完了後、結果が返される前に適用されます。そのため、Scan は、フィルタ式があるかどうかにかかわらず、同じ量の読み込みキャパシティーを消費します。
const scanRecords = async (startDate: Date, endDate: Date) => {
const params = {
TableName: "TestCompositeKey",
ExpressionAttributeNames: {
"#timestamp": "timestamp"
},
ExpressionAttributeValues: {
// ":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
FilterExpression: "#timestamp BETWEEN :sortkeyval1 AND :sortkeyval2"
}
const usersGotten = (await ddbDocClient.send(new ScanCommand(params))).Items ?? [];
return usersGotten;
}
属性が格納されてるか?で見落としがち
- attribute_exists()
- id 入ってるか?で見ると、消した場合に、(empty) として、属性は存在しているので反応してしまう
- Artist/Enterpreneur みたいに、複数の Table を単一Tableで保存する場合に、特定の属性があるか?を見るために使うのかな
- size()
- 対象属性の内容が一定文字数以上、とかで調べられるので、こちらが良さそう
size( #token ) >= :tokenLength"
- 対象属性の内容が一定文字数以上、とかで調べられるので、こちらが良さそう
QueryCommand
インデックスを設定した場合は、"IndexName" を利用する
const queryRecords = async (userID: string, startDate: Date, endDate: Date) => {
const paramsQuery = {
TableName: "TestCompositeKey",
// IndexName: "index_post_id",
ExpressionAttributeNames: {
"#PK": "uid",
"#SK": "timestamp#post_id"
},
ExpressionAttributeValues: {
":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
KeyConditionExpression: "#PK = :PK AND ( #SK BETWEEN :sortkeyval1 AND :sortkeyval2 )"
};
console.log("query params:", inspect(paramsQuery));
const queriedRecords = (await ddbDocClient.send(new QueryCommand(paramsQuery))).Items ?? [];
return queriedRecords;
}
使える演算子と、関数はこちら
error 対処
ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword:
timestamp のような予約語使う場合は、プレースホルダーを使えってやつ
const params = {
TableName: "TestCompositeKey",
ExpressionAttributeValues: {
// ":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
FilterExpression: "timestamp BETWEEN :sortkeyval1 AND :sortkeyval2"
}
const params = {
TableName: "TestCompositeKey",
ExpressionAttributeNames: {
"#timestamp": "timestamp"
},
ExpressionAttributeValues: {
// ":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
FilterExpression: "#timestamp BETWEEN :sortkeyval1 AND :sortkeyval2"
}
const usersGotten = (await ddbDocClient.send(new
ValidationException: Query condition missed key schema element: uid
キーが無いよ。
そう、Key と KeyConditionExpression は同時には使えない
const paramsQuery = {
TableName: "TestCompositeKey",
Key: { // これが効かない
uid: userID
},
ExpressionAttributeNames: {
"#SK": "timestamp#post_id"
},
ExpressionAttributeValues: {
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
KeyConditionExpression: "#SK BETWEEN :sortkeyval1 AND :sortkeyval2 "
};
const paramsQuery = {
TableName: "TestCompositeKey",
ExpressionAttributeNames: {
"#PK": "uid",
"#SK": "timestamp#post_id"
},
ExpressionAttributeValues: {
":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
KeyConditionExpression: "#PK = :PK AND ( #SK BETWEEN :sortkeyval1 AND :sortkeyval2 )"
};
ValidationException: One or more parameter values were invalid: Condition parameter type does not match schema type
Schema と違う型使ってるよって話
const paramsQuery = {
TableName: "TestCompositeKey",
ExpressionAttributeNames: {
"#PK": "uid",
"#SK": "timestamp#post_id"
},
ExpressionAttributeValues: {
":PK": userID,
":sortkeyval1": startDate.getTime(),
":sortkeyval2": endDate.getTime()
},
KeyConditionExpression: "#PK = :PK AND ( #SK BETWEEN :sortkeyval1 AND :sortkeyval2 )"
};
const paramsQuery = {
TableName: "TestCompositeKey",
ExpressionAttributeNames: {
"#PK": "uid",
"#SK": "timestamp#post_id"
},
ExpressionAttributeValues: {
":PK": userID,
":sortkeyval1": `${startDate.getTime()}`,
":sortkeyval2": `${endDate.getTime()}`
},
KeyConditionExpression: "#PK = :PK AND ( #SK BETWEEN :sortkeyval1 AND :sortkeyval2 )"
};
ValidationException: Value provided in ExpressionAttributeNames unused in expressions: keys: {#SK}
単に、未使用の ExpressionAttributeNames を残しておいちゃダメなので、消せってだけ
ValidationException: Value provided in ExpressionAttributeValues unused in expressions: keys: {:sortkeyval2, :sortkeyval1}
同様に、ExpressionAttributeValues も使わないのは消しましょう
ValidationException: Filter Expression can only contain non-primary key attributes: Primary key attribute:
PK/SK を filterExpression に定義したらダメです
ValidationException: KeyConditionExpressions must only contain one condition per key
PK/SK を、二回使ってはダメ
以下の場合、SK を二回使っているので error。対処法は BETWEEN を使えってこと
KeyConditionExpression: "#PK =:PK AND ( :sortkeyval1 <= #SK AND #SK < :sortkeyval2 )"
VS Code
エラーメッセージなどは、CTRL + Click で該当コードへ
CTRL + Click で、Source の該当箇所へ
※Enter が必要な場合も有り
template.yaml
lambda console でデバッグする際は、Minify/Sourcemap を false にする
- Minify
- コード見やすくしないと
- Sourcemap
- Lambda 上の js コードを修正しながら行うのであれば、false のが分かりやすい
Metadata: # Manage esbuild properties
BuildMethod: esbuild
BuildProperties:
Minify: false
Target: "es2020"
Sourcemap: false
あとがき
PartiQL や SQL との比較もしてみたいなぁ