2011年6月20日月曜日

Amazon SimpleDBってなんじゃ?(その2 PHP SDK編)

前回、SimpleDBをscratchpadから使用してみましたが、実際にはプログラムからSDKを通じて使用するケースがほとんどだと思うので、今回はPHPのSDKを使用してアイテム(レコード)のCRUD(作成、読込、更新、削除)をしてみたいと思います。

準備

今回はPHPのSDKを使用するので、ここからSDKをダウンロードしてロードしやすい場所に配置します。
$ mkdir src
$ cd src
$ wget http://pear.amazonwebservices.com/get/sdk-latest.zip
$ unzip sdk-latest.zip
$ cd sdk-1.3.4
$ mkdir /var/www/html/api
$ mv sdk-1.3.4 /var/www/html/api/
$ cd /var/www/html/api
$ ln -s sdk-1.3.4 sdk
SDKのドキュメンテーションを参考にいろいろなクエリを試して見ます。


ドメイン(テーブル)の新規作成

パラメータは  前回、ドメイン(テーブル)を作成した際と同様ドメイン名です。
<?php
  
  //ダウンロードしたSDKディレクトリのsdl.class.phpをロードします。
  require_once '../../sdk/sdk.class.php';
  //アクセス用のキーを定義します。
  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  //キーをもとにSDKのSDBアクセサクラスを初期化します。
  $sdb = new AmazonSDB($access_key, $secret_key);
  //ドメイン作成します。
  $data = $sdb->createDomain("member");
  //戻り値から成否を判断し、成功なら戻り値のダンプを返します。
  if($data->isOK()){
    print_r($data);
  }
  else{
    print_r("失敗〜");
  }
?>
成功した場合、戻り値のオブジェクトのisOK()がtrueで返ります。
戻り値のダンプは以下のとおりです。
CFResponse Object
(
    [header] => Array
        (
            [content-type] => text/xml
            [transfer-encoding] => chunked
            [content-encoding] => gzip
            [vary] => Accept-Encoding
            [date] => Mon, 20 Jun 2011 08:09:44 GMT
            [server] => Amazon SimpleDB
            [_info] => Array
                (
                    [url] => https://sdb.amazonaws.com/
                    [content_type] => text/xml
                    [http_code] => 200
                    [header_size] => 205
                    [request_size] => 691
                    [filetime] => -1
                    [ssl_verify_result] => 0
                    [redirect_count] => 0
                    [total_time] => 2.144966
                    [namelookup_time] => 0.208893
                    [connect_time] => 0.403431
                    [pretransfer_time] => 0.804195
                    [size_upload] => 0
                    [size_download] => 193
                    [speed_download] => 89
                    [speed_upload] => 0
                    [download_content_length] => 0
                    [upload_content_length] => 0
                    [starttransfer_time] => 2.144638
                    [redirect_time] => 0
                    [method] => POST
                )

            [x-aws-stringtosign] => 略
            [x-aws-request-headers] => Array
                (
                    [Content-Type] => application/x-www-form-urlencoded; charset=utf-8
                )

            [x-aws-body] =略
        )

    [body] => CFSimpleXML Object
        (
            [@attributes] => Array
                (
                    [ns] => http://sdb.amazonaws.com/doc/2009-04-15/
                )

            [ResponseMetadata] => CFSimpleXML Object
                (
                    [RequestId] => 673f1239-14be-ff54-9115-8ede692b7dc9
                    [BoxUsage] => 0.0055590278
                )

        )

    [status] => 200
)
このようにアクションの戻り値はヘッダ、ボディ、ステータス部に分かれて構成されたXMLのオブジェクトとして返され、実際のレスポンスデータはボディ部に含まれます。
このアクションはドメイン作成でデータの取得ではないため、返却値はなくメタデータのみが返されます。
また、戻り値のisOKメソッドでアクションの成否の判断ができます。


アイテム(レコード)の追加

SimpleDBではアイテムの追加と更新は同じメソッドを利用します。
アイテムの追加は、ドメイン名(テーブル名)とアイテム名(プライマリキー)と各属性(カラム名)とその値の連想配列をパラメータとします。戻り値はドメインの作成と同様更新アクションなので、返却値はなくメタデータのみになります。
<?php

  require_once '../../sdk/sdk.class.php';

  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  $sdb = new AmazonSDB($access_key, $secret_key);
  
  //1件ずつのインサート
  $data1 = $sdb->put_attributes('member',//ドメイン名
                                     'memorycraft',//アイテム名(プライマリキー)
                                     array(//各属性名と値の連想配列
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );

  /*
   * 複数件同時にインサート
   * ・ドメイン名
   * ・アイテム名(プライマリキー)をキーにした属性と値の連想配列
  */
  $data2 = $sdb->batchPutAttributes('shop', //ドメイン名
                                            array('David' => array('country' => 'USA',
                                                               'location' => 'Seattle'
                                                              ),
                                                 'Satoru' => array('country' => 'Japan',
                                                              'location' => 'Saitama'
                                                              )
                                            )//アイテム名をキーにした属性と値の連想配列
                                   );

    if($data1->isOK() && $data2->isOK()){
      print_r($data1);
      print_r($data2);
    }
    else{
      print_r("失敗〜");
    }

?>
値のセットは、1件の場合はput_attributeメソッドで、複数アイテム同時にセットする場合はbatchPutAttributesメソッドを使用します。

最後の引数としてreplace引数をtrue/falseで追加すると、指定したアイテム名が該当するアイテムが既に存在した場合の挙動をコントロールできます。replace引数はデフォルトfalseです。
//普通のインサート
$data1 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );
//値を変えて再投入
$data2 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Chiba',
                                     )
                                );
この場合、2回目のput_attributeの時点では既にmemorycraftというアイテムは存在しているので、location属性はTokyoという値を上書きせずに更にChibaという値も持つことになります。SELECTするとlocation属性の値としてTokyo, Chibaの2つが返ることがわかります。

また、最初から複数値をセットすることもできます。
$data = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => array('Tokyo', 'Chiba'),
                                     )
                                );

このようにデフォルトの状態では既存アイテムに対して複数値のセットになってしまうことを避けたい場合は、put_attributeの第4引数であるreplace引数をtrueにセットします。
//普通のインサート
$data1 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Tokyo',
                                     )
                                );
//値を変えて再投入
$data2 = $sdb->put_attributes('member',
                                     'memorycraft',
                                     array(
                                       'country' => 'Japan',
                                       'location' => 'Chiba',
                                     ),
                                     true//replace引数をtrueに
                                );
この場合、locationは複数値にならずTokyoがChibaに変更されます。


アイテム(レコード)の検索

アイテムの検索はRDBでいうところのSELECTにあたります。
戻り値には検索結果としての返却値が含まれています。
>?php
  require_once '../../sdk/sdk.class.php';

  $access_key = "xxxxxxxxxxxxxxxxxx";
  $secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  $sdb = new AmazonSDB($access_key, $secret_key);
  $sql = "select * from member where country = 'Japan'";//country属性がJapanのものを検索
  $data = $sdb->select($sql, array("ConsistentRead" => "true"));

    if($data->isOK()){
      print_r($data);
    }
    else{
      print_r("失敗〜");
    }

?<

この場合、戻り値$dataのダンプは以下のようになります。
CFResponse Object
(
    [header] => Array
        (
            [content-type] => text/xml
            [transfer-encoding] => chunked
            [content-encoding] => gzip
            [vary] => Accept-Encoding
            [date] => Mon, 20 Jun 2011 11:19:32 GMT
            [server] => Amazon SimpleDB
            [_info] => Array
                (
                    [url] => https://sdb.amazonaws.com/
                    [content_type] => text/xml
                    [http_code] => 200
                    [header_size] => 205
                    [request_size] => 753
                    [filetime] => -1
                    [ssl_verify_result] => 0
                    [redirect_count] => 0
                    [total_time] => 0.808396
                    [namelookup_time] => 0.002231
                    [connect_time] => 0.195101
                    [pretransfer_time] => 0.595389
                    [size_upload] => 0
                    [size_download] => 285
                    [speed_download] => 352
                    [speed_upload] => 0
                    [download_content_length] => 0
                    [upload_content_length] => 0
                    [starttransfer_time] => 0.808132
                    [redirect_time] => 0
                    [method] => POST
                )

            [x-aws-stringtosign] => 略
            [x-aws-request-headers] => Array
                (
                    [Content-Type] => application/x-www-form-urlencoded; charset=utf-8
                )

            [x-aws-body] => 略
        )

    [body] => CFSimpleXML Object
        (
            [@attributes] => Array
                (
                    [ns] => http://sdb.amazonaws.com/doc/2009-04-15/
                )

            [SelectResult] => CFSimpleXML Object
                (
                    [Item] => Array
                        (
                            [0] => CFSimpleXML Object
                                (
                                    [Name] => memorycraft
                                    [Attribute] => Array
                                        (
                                            [0] => CFSimpleXML Object
                                                (
                                                    [Name] => location
                                                    [Value] => Tokyo
                                                )

                                            [1] => CFSimpleXML Object
                                                (
                                                    [Name] => country
                                                    [Value] => Japan
                                                )

                                        )

                                )

                            [1] => CFSimpleXML Object
                                (
                                    [Name] => Satoru
                                    [Attribute] => Array
                                        (
                                            [0] => CFSimpleXML Object
                                                (
                                                    [Name] => location
                                                    [Value] => Saitama
                                                )

                                            [1] => CFSimpleXML Object
                                                (
                                                    [Name] => country
                                                    [Value] => Japan
                                                )

                                        )

                                )

                        )

                )

            [ResponseMetadata] => CFSimpleXML Object
                (
                    [RequestId] => d2c6f796-5d0d-cc2f-3b63-e461e108d38e
                    [BoxUsage] => 0.0000320033
                )

        )

    [status] => 200
)
このように、body要素のSearchResultの中に検索結果が出力されてきます。
この場合はアイテム(レコード)が2件取得されたので、Item要素はCFSimpleXML Objectの配列になりますが、1件の場合は配列ではなくCFSimpleXML Objectになります。

これらはXMLオブジェクトなどを含んでいるため結果セットのパースや操作がやや面倒です。
そのため、こちらのサンプルのようにレスポンスを連想配列セットに再構築する汎用メソッドやライブラリなどを用意しておくと便利です。
$data = $sdb->select($sql, array("ConsistentRead" => "true"));//クエリ実行
$data = reorganize_data($data->body->Item());//戻り値を再構築
print_r($data["rows"][0]["country"]);//1番目のアイテムの国を出力

また、SimpleDBはデフォルトでは非アトミックなため、SELECTなどでその前の更新内容の伝播が遅れていても読み込まれる場合があります。それを防ぐためには、このサンプルのように第2引数のオプション配列でConsistentReadを有効にするように指定します。ポイントはTRUEを文字列として渡すことです。ブール値で渡すとエラーが発生します。
$data = $sdb->select($sql, array("ConsistentRead" => true));//エラーが発生
$data = $sdb->select($sql, array("ConsistentRead" => "true"));//正しい指定のしかた

アイテム名(プライマリキー)を直接参照、指定する際はitemName()という関数を使用します。
$sql = "select * from member where itemName() = 'memorycraft'";

そのほかSELECTで使用できるクエリ構文に関してはSimpleDBのデベロッパーガイドが参考になります。


続き(アイテムの更新、削除)はまた次回~