protobufのMessageを比較する

# はじめに

protobufの`Message`型を比較する方法を調べたので、備忘メモを残しておきます。

以下の環境で確認しています。

```shell
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
$ uname -r
5.5.8
$ uname -m
x86_64
$ protoc --version
libprotoc 3.14.0
```

# `MessageDifferencer`

`Message`型には等価演算子(`==`)が定義されていません。
代わりに、[`MessageDifferencer`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer)という比較用のclassがあります。

主な比較用のmethodは下記の4つです。

1. [`MessageDifferencer::Equals()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Equals.details) : unknown fieldを含む比較
2. [`MessageDifferencer::Equivalent()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Equivalent.details) : unknown fieldを除く比較
3. [`MessageDifferencer::Compare()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Compare.details) : `MessageFieldComparison`などでロジックを設定可能な比較
4. `MessageDifferencer::CompareWithFields()` : fieldを指定して比較

この中でおそらく最も一般的に使うのは、`MessageDifferencer::Equivalent()`だと思います。
以降でそれぞれの違いについて説明します。

## 1. [`MessageDifferencer::Equals()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Equals.details)

引数で渡した２つの`Message`を比較し、**[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)も含めて完全に**一致する場合に`true`を返します。

[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)とは、protoファイルのmessageには定義されていないfieldを指します。
具体的には、messageの構造が変更されfieldが途中から削除されたケースで、
変更前に保存したデータを読み出したときなどに発生します。

ただ、fieldが削除されている以上、現時点ではそのfieldに意味はないでしょうから、
実際に[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)を比較したい場合はそう多くないと思います。

逆の言い方をすれば、わざわざ`MessageDifferencer::Equals()`を使うのは[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)を比較したい場合になりますが、注意すべき点があります。
[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)にも記載があるとおり、proto3の3.5より前のバージョンでは、parseの時点で[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)を無視します。
結果として、[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)の比較はできません。
`Equals()`が必要な場合は、protobufのバージョンにも注意しましょう。


## 2. [`MessageDifferencer::Equivalent()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Equivalent.details)

引数で渡した２つの`Message`のknown fieldsを比較し、一致する場合に`true`を返します。

微妙な言い回しですが、"equivalent"は「厳密に一致(equal)ではないが同じとみなせる」状態を表しているようです。

繰り返しになりますが、`Message`の比較で最も一般的に使うのはこの`Equivalent`になると思います。

## 3. [`MessageDifferencer::Compare()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer#MessageDifferencer.Compare.details)

`Equals()`, `Equivalent()`がclass methodだったのに対し、`Compare()`はinstance methodです。
`MessageDifferencer::set_message_field_comparison()`などを使って細かく挙動を変更できます。

以下のように、`Equals()`, `Equivalent()`の実装は、内部で`Compare()`を呼び出しています。

```c++:src/google/protobuf/util/message_differencer.cc
bool MessageDifferencer::Equals(const Message& message1,
                                const Message& message2) {
  MessageDifferencer differencer;

  return differencer.Compare(message1, message2);
}

bool MessageDifferencer::Equivalent(const Message& message1,
                                    const Message& message2) {
  MessageDifferencer differencer;
  differencer.set_message_field_comparison(MessageDifferencer::EQUIVALENT);

  return differencer.Compare(message1, message2);
}
```

## 4. `MessageDifferencer::CompareWithFields()`

`Comapre`と同じくinstance methodで、比較するfieldを限定できます。
fieldの指定は`FieldMask`。。。かと思いきや`FieldDescriptorArray`です。

下記のように`FieldMaskUtil::GetFieldDescriptors()`を使うことで
fieldを指すpathから`FieldDescriptorArray`を取得可能です。

```c++
FieldDescriptorArray fields;
FieldMaskUtil::GetFieldDescriptors(message1.GetDescriptor(),
                                   "field1", &fields);
MessageDifferencer differencer;
differencer.CompareWithFields(message_1, message_2, fields, fields);
```

`FieldMask`から`FieldDescriptorArray`を取得したい場合は、
`FieldMaskUtil::ToString()`が使えます。

```c++
FieldMask mask;
mask.add_paths("field1");
FieldDescriptorArray fields;
FieldMaskUtil::GetFieldDescriptors(message1.GetDescriptor(),
                                   FieldMaskUtil::ToString(mask), &fields);
MessageDifferencer differencer;
differencer.CompareWithFields(message_1, message_2, fields, fields);
```

# おまけ. [`FieldMaskUtil::TrimMessage()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.field_mask_util#FieldMaskUtil.TrimMessage.details)

[`FieldMaskUtil::TrimMessage()`](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.field_mask_util#FieldMaskUtil.TrimMessage.details)を使うと`FieldMask`で指定したfield以外を取り除くことができます。
その後で`Equivalent()`することでも、一部のfieldのみ比較することが可能です。

だいたいのケースで`CompareWithFields()`の方が空間・時間ともに有利だと思いますが、
大きな`Message`のうちごく一部だけ比較対象から外したい場合などでは、
`FieldDescriptorArray`を作成する処理のぶんだけこちらの方法が有利になるかもしれません。

ただし、`FieldMaskUtil::TrimMessage()`ではunknown fieldsが残りますのでその点はご注意ください。

```c++
FieldMask mask;
mask.add_paths("field1");
FieldMaskUtil::TrimMessage(mask, &message_1);
FieldMaskUtil::TrimMessage(mask, &message_2);
MessageDifferencer::Equivalent(message_1, message_2);
```

# 実験コード

今回の記事で使った実験コードは、以下に置いています。

[takeoverjp/protobuf-sandbox](https://github.com/takeoverjp/protobuf-sandbox/blob/master/compare.cc)

# 参考
[MessageDifferencer](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.message_differencer)
[Unknown Fields](https://developers.google.com/protocol-buffers/docs/proto3#unknowns)