【Node.js】LambdaからDynamoDBテーブルをScanする際に設定するFilterExpressionの書き方【後編】

# FilterExpressionを書けるようになる

https://qiita.com/neko_tech/items/58aad845e254ca8f53c4

前編にてNode.JSでDynamoDBのテーブルをScanする方法を説明しましたが、以降ではScanの条件式(FilterExpression)を自由自在に扱うための方法について解説していきます。

というのも、FilterExpressionのValueには何を書いても良いというわけではなく、書く際のルールがあります。AWSではこれを「フィルター式」と呼びます。
[比較演算子および関数リファレンス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)
リンク先にフィルター式の定義が載っていますが、バッカス・ナウア記法（英: Backus–Naur form）[^1]で書かれているためなじみがない方は「どゆこと？」となりかねません。

そこで、フィルター式の読み方と書き方を理解してFilterExpressionのValueをゼロから構築できるようにしましょう。

## condition
conditionとは、FilterExpressionのValueそのものです。つまり、FilterExpressionは
```
FilterExpression: condition
```
のように記載しなければなりません。

リンク先の記事にはconditionの定義が以下のように書かれています。
```
condition-expression ::=
      operand comparator operand
    | operand BETWEEN operand AND operand
    | operand IN ( operand (',' operand (, ...) ))
    | function
    | condition AND condition
    | condition OR condition
    | NOT condition
    | ( condition )
```

いきなり出てきた「::=」は「定義」を意味する記号で、左の項は右の項と置き換えられることを表します。
「|」は「または」を意味します。
例えば、「A ::= エー | エイ」は、このような意味です。
* Aはエーで置き換えられる
* Aはエイで置き換えられる

つまり、condition(condition-expression)は、「|」で区切られた7つのexpressionで置き換えることができます。
※8つ目の(condition)は「conditionをconditionで置き換えられるという意味。当たり前すぎるので無視

フィルター式に当てはめると、
```
FilterExpression: condition
```
これを、以下のように書き換えることができるということです。
```
FilterExpression: operand comparator operand
FilterExpression: operand BETWEEN operand AND operand
FilterExpression: operand IN ( operand (',' operand (, ...) ))
FilterExpression: function
FilterExpression: condition AND condition
FilterExpression: condition OR condition
FilterExpression: NOT condition
```

## operand
値や変数です。FilterExpressionでは値や変数を直書きすることはできないのでバインドさせる必要があります。
例えば、料理名(dishName)に「丼」を含む料理を検索したい場合、以下のように書くとエラーが出ます。
```
FilterExpression: "contains(dishName, '丼')"
```
エラーログ
```
{
  "errorType": "ValidationException",
  "errorMessage": "Value provided in ExpressionAttributeNames unused in expressions: keys: {#dish_name}",
}
```
```
{
  "errorType": "ValidationException",
  "errorMessage": "Invalid FilterExpression: Syntax error; token: \"'\", near: \", '丼\"",
}
```

なぜエラーが出るかというと、DynamoDBのテーブルの属性(列名)は「#○○」の形で指定し、値は「：○○」の形で指定しなければならないからです。正しくは以下のようにバインド変数を用いて書きます。
```
FilterExpression: "contains(#dish_name, :dishName)", 
ExpressionAttributeNames: {
    "#dish_name": "dish_name",
},
ExpressionAttributeValues: {
    ":dishName": {"S": '丼'}
}
```
値の直書きだけはできないということだけ覚えておいてください。
## comparator, function
comparator(比較演算子)は以下のように定義されています。
```
comparator ::=
    =
    | <>
    | <
    | <=
    | >
    | >=
```

function(関数)の定義は以下の通りです。
```
function ::=
    attribute_exists (path)
    | attribute_not_exists (path)
    | attribute_type (path, type)
    | begins_with (path, substr)
    | contains (path, operand)
    | size (path)
```
比較演算子や関数に特質すべきところはありません。
## 実際にフィルター式を書いてみる
以上の要素を用いてフィルター式を書くことができます。
例えば、「料理名(dish_name)」に「丼」を含み、「金額(dish_price)」が「300円以下」のものを検索したいときは、
```
FilterExpression: "contains(#dish_name, :dishName) AND #dish_Price <= :dishPrice", 
ExpressionAttributeNames: {
    "#dish_name": "dish_name",
    "#dish_price": "dish_price"
},
ExpressionAttributeValues: {
    ":dishName": {"S": '丼'}
    "dishPrice": {"N": '300'}
}
```
このように書けば良いと分かります。

これは、
```
FilterExpression: condition
    ∴condition ::= condition AND condition
=> FilterExpression: condition AND condition
    ∴condition ::= function
    ∴condition ::= operand comparator operand
=> FilterExpression: function AND operand comparator operand
    ∴function ::= contains (path, operand)
    ∴comparator ::=　<=
=> FilterExpression: contains (path, operand) AND operand <= operand
```
とフィルタ式を変形すれば作れる式なのできちんと動きます。

一方で、
```
FilterExpression: "#dish_name contains :dishName AND <= :dishPrice"
```
のように定義を無視して書いてしまうとエラーが出ます。

以上の知識でFilterExpressionを書けるようになります。
良きAWSライフをお過ごしください。


[^1]:バッカス・ナウア記法（英: Backus–Naur form）とは、文脈自由文法を定義するのに用いられるメタ言語のことで、一般にBNFやBN記法と略される。(Wikipediaより)