Tips

オブジェクトフィールドの内容を間接的に書き換えた場合は明示的にフィールドのフラグを更新することが必要

日付2017/12/06
ID17-005
バージョン4D v16 R4
プラットフォームWindows, Mac

16 R4より,オブジェクト(変数またはフィールド)に対してオブジェクト記法が使用できるようになりました。ドット・ノーテーションとも呼ばれる,オブジェクト記法は,メソッドが直感的に読みやすくなるだけでなく,コンパイルモードでの速度も向上します。

オブジェクト記法はとても便利ですが,注意しなければならない点があります。オブジェクト型には「参照」という特殊性があり,テキストやBLOBやピクチャのような従来のデータ型とは振る舞いが異なるという点です。これは,オブジェクト型のフィールドを更新する場合,特に重要です。

4Dのランゲージでは,何らかの抽象的なオブジェクトに対する内部的な識別子を便宜上「参照型」として扱うことがあります。代表的な参照型には,DocRef(時間型)・メニュー(テキストまたは数値)・リスト(数値)・DOM(テキスト)などが挙げられます。通常,変数やフィールドを別の変数やフィールドに代入すれば,値のコピーが作られますが,参照型の「値」は,対象そのものではなく,その識別子に過ぎないため,値を別の変数やフィールドに代入しただけでは対象のコピーが作られません。そのような意味で,参照型はポインターに似ています。

オブジェクト型フィールドは,JSONデータ(値)が保存できるという点で,テキストやBLOBのような通常のデータ型のようです。オブジェクト型フィールドにレコードを保存すれば,JSONの内容がUTF-8エンコーディングでデータファイルに保存されます。

オブジェクト型(フィールドに限らない)を別のオブジェクト型に代入した場合,JSONデータ(値)ではなく,参照のコピーが作られます。つまり,オブジェクト型は,参照型のような側面も持っています。たとえば,あるオブジェクトを配列に挿入した場合,配列に挿入されるのはそのオブジェクトに対する参照なので,元のオブジェクトを更新すれば,配列に挿入されたオブジェクトも一緒に変わります。同じように,あるオブジェクトを別のオブジェクト型の属性(プロパティ)として代入した場合,代入された属性は引き続き元のオブジェクトを参照しており,どちらからも更新することができます。JSONデータ(値)のコピーを代入するためには,単純に代入演算子(:=)を使用するのではなく,OB Copyで明示的に作成したオブジェクトのコピーを代入することが必要です。

フィールドに値が代入されると,その更新フラグが立てられます。このフラグは,どのような手段で値が代入されたとしても立てられ,代入された値が変わったかどうかを問わないので,更新されたフィールドは,どのような方法で値を更新したとしても,レコード保存時にキャッシュやデータファイルに書き込まれることが保証されています。一方,フラグが立っていないフィールドは,SAVE RECORDコマンドや「次レコード」や「前レコード」などの標準アクションでレコードを「保存」したとしても,キャッシュやデータファイルに書き込まれません。

そこでオブジェクト型に特有の問題が生じます。他のフィールドタイプとは異なり,オブジェクト型だけは間接的に更新できるために生じる問題です。

たとえば,フィールドに

{
"obj":{"foo":"bar"}
}

のようなJSONが保存されているとします。

$obj:=OB Get([MyTable]MyField;"obj";Is object)
OB SET($obj;"foo";"xxx")

といった具合に,サブオブジェクトの参照を介することにより,フィールドそのものに値を代入することなく,つまり,フィールドのフラグを立てずに,値を更新することができてしまいます。

同じ理由により,オブジェクト記法の

[MyTable]MyField.obj.foo:="xxx"

は,一見,フィールドに対する代入のようにも読めますが,実際には,フィールドではなく,そのフィールドが参照している別のオブジェクトに対する代入に相当するので,フィールドの更新フラグは立ちません。そのため,SAVE RECORDを実行しても,新しい値が保存されません。

このようにオブジェクト型フィールドを更新した場合,

[MyTable]MyField:=[MyTable]MyField

と明示的な代入文を実行し,フィールドの更新フラグを立てることが必要です。

追記

開発中のv17では,既存のオブジェクト型フィールドを保存する場合の内部的な仕組みが最適化され,更新されたプロパティのインデックスキーだけが再計算されるようになりました。具体的には,下記のようなコードでもレコードが更新されるようになり,明示的な自己代入でフラグを立てるよりもずっと高速に処理が完了します。例として,250個のプロパティ(トップレベルの数)を持つオブジェクトのプロパティを1個だけ更新した場合の速度は19倍(1900%)向上しています。

QUERY([MyTable];somequery)
[MyTable]MyField.prop1.prop2.prop3.x := 100
SAVE RECORD([MyTable])




本記事は4D-JUGで公開された記事「オブジェクトフィールドの内容を間接的に書き換えた場合は明示的にフィールドのフラグを更新することが必要」の再録になります。