メタデータ

Datastore では一部のメタデータにプログラムからアクセスできます。これによりメタプログラミングやバックエンドの管理機能の実装が可能となり、一貫性のあるキャッシュ保存が簡単になります。これは、アプリケーション用にカスタム Datastore ビューアを作成する場合などに役立ちます。使用できるメタデータとしては、アプリケーションが使用するエンティティ グループ、名前空間、エンティティの種類、プロパティに関する情報のほか、各プロパティのプロパティ表現などが挙げられます。

アプリケーションに関する一部のメタデータは、Cloud Console の Datastore ダッシュボードでも提供されています。ただし、ここに表示されるデータは、いくつかの重要な側面において関数から返されるデータとは異なります。

  • 鮮度: API を使用してメタデータを読み取ると最新のデータが取得されますが、ダッシュボードのデータが更新されるのは 1 日 1 回だけです。
  • 内容: ダッシュボードの一部のメタデータは API 経由では使用できません。また、その逆の場合もあります。
  • 速度:メタデータの取得とクエリは、Datastore の取得とクエリと同じ方法で課金されます。名前空間、種類、プロパティに関する情報をフェッチするメタデータ クエリは、実行速度が遅いのが一般的です。目安としては、N 個のエンティティを返すメタデータ クエリにかかる時間は、それぞれが単一のエンティティを返す N 個の通常のクエリにかかる時間とほぼ同じです。また、プロパティ表現クエリ(キーのみではないプロパティ クエリ)は、キーのみのプロパティ クエリよりも遅くなります。エンティティ グループのメタデータに対するメタデータ取得は、通常のエンティティを取得する場合よりもある程度は高速になります。

エンティティ グループのメタデータ

Cloud Datastore では、エンティティ グループの「バージョン」にアクセスできます。バージョンは正数であり、エンティティ グループに変更が加えられるたびに増加します。

エンティティ グループのバージョンを取得するには、真に正の __version__ プロパティを含む特別な疑似エンティティに対して get() を呼び出します。疑似エンティティのキーは、Entities.createEntityGroupKey() メソッドを使って作成できます。

private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) {
  try {
    return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey)));
  } catch (EntityNotFoundException e) {
    // No entity group information, return a value strictly smaller than any
    // possible version
    return 0;
  }
}

private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) {
  Entity entity1 = new Entity("Simple");
  Key key1 = ds.put(entity1);
  Key entityGroupKey = Entities.createEntityGroupKey(key1);

  // Print entity1's entity group version
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Write to a different entity group
  Entity entity2 = new Entity("Simple");
  ds.put(entity2);

  // Will print the same version, as entity1's entity group has not changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));

  // Change entity1's entity group by adding a new child entity
  Entity entity3 = new Entity("Simple", entity1.getKey());
  ds.put(entity3);

  // Will print a higher version, as entity1's entity group has changed
  writer.println("version " + getEntityGroupVersion(ds, null, key1));
}

従来の動作

エンティティ グループのバージョンの従来の動作では、エンティティ グループのバージョンはエンティティ グループに変更が加えられたときにのみ増加します。たとえば、エンティティ グループ メタデータの従来の動作を使用して、エンティティ グループに対する複雑な祖先クエリのキャッシュ整合性を確保できます。

この例では、クエリ結果(一致する結果の数)をキャッシュに保存し、エンティティ グループのバージョンの従来の動作を利用して、キャッシュに保存された値が最新であればそれを使用します。

// A simple class for tracking consistent entity group counts.
private static class EntityGroupCount implements Serializable {

  long version; // Version of the entity group whose count we are tracking
  int count;

  EntityGroupCount(long version, int count) {
    this.version = version;
    this.count = count;
  }

  // Display count of entities in an entity group, with consistent caching
  void showEntityGroupCount(
      DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) {
    EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey);
    // Reuses getEntityGroupVersion method from the previous example.
    if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) {
      // Cached value matched current entity group version, use that
      writer.println(egCount.count + " entities (cached)");
    } else {
      // Need to actually count entities. Using a transaction to get a consistent count
      // and entity group version.
      Transaction tx = ds.beginTransaction();
      PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey));
      int count = pq.countEntities(FetchOptions.Builder.withLimit(5000));
      cache.put(
          entityGroupKey,
          new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count));
      tx.rollback();
      writer.println(count + " entities");
    }
  }
}

書き込みが行われたことのないエンティティ グループの場合、__entity_group__ エンティティが存在しないことがあります。

メタデータ クエリ

Java クラス Entitiescom.google.appengine.api.datastore パッケージで定義されており、メタデータ クエリ用に予約された特別なエンティティの種類が 3 つ用意されています。これらの種類は、Entities クラスの静的定数で示されます。

静的定数 エンティティの種類
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

これらの種類は、アプリケーションにすでに存在する可能性がある同じ名前の他のエンティティとは競合しません。このような特殊な種類に対してクエリを実行することで、目的のメタデータを含むエンティティを取得できます。

メタデータ クエリによって返されるエンティティは、Datastore の現在の状態に基づいて動的に生成されます。種類が __namespace____kind____property__ のローカル Entity オブジェクトを作成することはできますが、Datastore に保存しようとすると IllegalArgumentException となり、失敗します。

メタデータ クエリを実行する最も簡単な方法は、低水準 Datastore API を使用することです。たとえば、次のコードは、アプリケーションのすべての名前空間の名前を出力します。

void printAllNamespaces(DatastoreService ds, PrintWriter writer) {
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);

  for (Entity e : ds.prepare(q).asIterable()) {
    // A nonzero numeric id denotes the default namespace;
    // see <a href="#Namespace_Queries">Namespace Queries</a>, below
    if (e.getKey().getId() != 0) {
      writer.println("<default>");
    } else {
      writer.println(e.getKey().getName());
    }
  }
}

名前空間クエリ

アプリケーションで Namespaces API を使う場合、名前空間クエリを使用すると、アプリケーションのエンティティで使用されているすべての名前空間を検索できます。これにより、複数の名前空間にまたがる管理機能などのアクティビティを実行できます。

名前クエリは、キー名が名前空間の名前である特別な種類 __namespace__ のエンティティを返します。(空の文字列 "" で示されるデフォルトの名前空間は例外です。空の文字列は有効なキー名ではないため、この名前空間には数値 ID 1 が代わりにキーとして設定されます)。このタイプのクエリは、特殊な疑似プロパティ __key__ を対象とした範囲のフィルタリングだけをサポートします。このプロパティの値はエンティティのキーです。結果は __key__ 値の昇順で並べ替えることができます(降順にすることはできません)。__namespace__ エンティティはプロパティを持たないため、キーのみのクエリでもキーのみではないクエリでも同じ情報が返されます。

次の例は、アプリケーションの名前空間のうち、指定された 2 つの名前、startend の間にある名前空間のリストを返します。

List<String> getNamespaces(DatastoreService ds, String start, String end) {

  // Start with unrestricted namespace query
  Query q = new Query(Entities.NAMESPACE_METADATA_KIND);
  List<Filter> subFilters = new ArrayList();
  // Limit to specified range, if any
  if (start != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.GREATER_THAN_OR_EQUAL,
            Entities.createNamespaceKey(start)));
  }
  if (end != null) {
    subFilters.add(
        new FilterPredicate(
            Entity.KEY_RESERVED_PROPERTY,
            FilterOperator.LESS_THAN_OR_EQUAL,
            Entities.createNamespaceKey(end)));
  }

  q.setFilter(CompositeFilterOperator.and(subFilters));

  // Initialize result list
  List<String> results = new ArrayList<String>();

  // Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(Entities.getNamespaceFromNamespaceKey(e.getKey()));
  }

  // Return result list
  return results;
}

種類クエリ

種類クエリは、キー名がエンティティの種類の名前である __kind__ という種類のエンティティを返します。このタイプのクエリは暗黙的に現在の名前空間に限定され、__key__ 疑似プロパティを対象とした範囲のフィルタリングだけをサポートします。結果は __key__ 値の昇順で並べ替えることができます(降順にすることはできません)。__kind__ エンティティはプロパティを持たないため、キーのみのクエリでもキーのみではないクエリでも同じ情報が返されます。

次の例は、名前が小文字で始まるすべての種類を出力します。

void printLowercaseKinds(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted kind query
  Query q = new Query(Entities.KIND_METADATA_KIND);

  List<Filter> subFils = new ArrayList();

  // Limit to lowercase initial letters
  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.GREATER_THAN_OR_EQUAL,
          Entities.createKindKey("a")));

  String endChar = Character.toString((char) ('z' + 1)); // Character after 'z'

  subFils.add(
      new FilterPredicate(
          Entity.KEY_RESERVED_PROPERTY,
          FilterOperator.LESS_THAN,
          Entities.createKindKey(endChar)));

  q.setFilter(CompositeFilterOperator.and(subFils));

  // Print heading
  writer.println("Lowercase kinds:");

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println("  " + e.getKey().getName());
  }
}

プロパティ クエリ

プロパティ クエリは、種類が __property__ であるエンティティを返します。これは、エンティティの種類に関連付けられたプロパティを示しますプロパティが P、種類が K で表現されるエンティティは次のように構築されます。

  • エンティティのキーの種類は __property__ で、キー名は P です。
  • 親エンティティのキーの種類は __kind__ で、キー名は K です。

プロパティ クエリの動作は、それがキーのみのクエリかキーのみではない(プロパティ表現)クエリかによって異なります。詳細については以降の説明をご覧ください。

プロパティ クエリ: キーのみ

キーのみのプロパティ クエリは、指定したエンティティの種類のインデックス付けされた各プロパティのキーを返します(インデックス付けされていないプロパティは含まれません)。次の例は、アプリケーションのエンティティの種類すべての名前と、それぞれに関連付けられているプロパティを出力します。

void printProperties(DatastoreService ds, PrintWriter writer) {

  // Create unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

このタイプのクエリは暗黙的に現在の名前空間に限定され、疑似プロパティ __key__ を対象とした範囲のフィルタリングだけをサポートします。この場合、キーは __kind__ または __property__ エンティティを示します。結果は __key__ 値の昇順で並べ替えることができます(降順にすることはできません)。フィルタリングは種類とプロパティの(この順に並んだ)ペアに適用されます。たとえば、次のようなプロパティを持つエンティティがあるとします。

  • プロパティを含む種類 Account
    • balance
    • company
  • プロパティを含む種類 Employee
    • name
    • ssn
  • プロパティを含む種類 Invoice
    • date
    • amount
  • プロパティを含む種類 Manager
    • name
    • title
  • プロパティを含む種類 Product
    • description
    • price

このプロパティ データを返すクエリは次のようになります。

void printPropertyRange(DatastoreService ds, PrintWriter writer) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit range
  q.setFilter(
      CompositeFilterOperator.and(
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.GREATER_THAN_OR_EQUAL,
              Entities.createPropertyKey("Employee", "salary")),
          new FilterPredicate(
              Entity.KEY_RESERVED_PROPERTY,
              Query.FilterOperator.LESS_THAN_OR_EQUAL,
              Entities.createPropertyKey("Manager", "salary"))));
  q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING);

  // Print query results
  for (Entity e : ds.prepare(q).asIterable()) {
    writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName());
  }
}

上のクエリは、次のデータを返します。

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

結果には種類 Employeename プロパティ、種類 Managertitle プロパティは含まれません。また、種類 Account と種類 Product のプロパティはすべて含まれません。これらのプロパティは、クエリに指定された範囲に含まれないためです。

プロパティ クエリは __kind__ キーまたは __property__ キーによる祖先フィルタリングもサポートしています。これにより、クエリの結果が単一の種類またはプロパティに限定されます。これを利用して、次の例に示すように特定のエンティティの種類に関連付けられたプロパティを取得できます。

List<String> propertiesOfKind(DatastoreService ds, String kind) {

  // Start with unrestricted keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly();

  // Limit to specified kind
  q.setAncestor(Entities.createKindKey(kind));

  // Initialize result list
  ArrayList<String> results = new ArrayList<String>();

  //Build list of query results
  for (Entity e : ds.prepare(q).asIterable()) {
    results.add(e.getKey().getName());
  }

  // Return result list
  return results;
}

プロパティ クエリ: キーのみではない(プロパティ表現)

キーのみではないプロパティ クエリは「プロパティ表現クエリ」と呼ばれ、種類とプロパティの各ペアで使用される表現に関する追加情報を返します(インデックス付けされていないプロパティは含まれません)。種類が K のプロパティ P に対して返されるエンティティには、対応するキーのみのクエリで返されるものと同じキーに加えて、プロパティの表現を返す追加の property_representation プロパティが存在します。このプロパティの値はクラス java.util.Collection<String> のインスタンスです。このインスタンスには、種類が K のエンティティで検出されたプロパティ P の表現ごとに 1 つの文字列が含まれます。

表現はプロパティ クラスとは異なり、複数のプロパティ クラスが同じ表現にマップされる場合があります(たとえば、java.lang.Stringcom.google.appengine.api.datastore.PhoneNumber の両方で STRING 表現が使用されています)。

次の表に、プロパティ クラスからその表現へのマッピングを示します。

プロパティ クラス 表現
java.lang.Byte INT64
java.lang.Short INT64
java.lang.Integer INT64
java.lang.Long INT64
java.lang.Float DOUBLE
java.lang.Double DOUBLE
java.lang.Boolean BOOLEAN
java.lang.String STRING
com.google.appengine.api.datastore.ShortBlob STRING
java.util.Date INT64
com.google.appengine.api.datastore.GeoPt POINT
com.google.appengine.api.datastore.PostalAddress STRING
com.google.appengine.api.datastore.PhoneNumber STRING
com.google.appengine.api.datastore.Email STRING
com.google.appengine.api.users.User USER
com.google.appengine.api.datastore.IMHandle STRING
com.google.appengine.api.datastore.Link STRING
com.google.appengine.api.datastore.Category STRING
com.google.appengine.api.datastore.Rating INT64
com.google.appengine.api.datastore.Key REFERENCE
com.google.appengine.api.blobstore.BlobKey STRING
java.util.Collection<T> T の表現

次の例は、あるエンティティの種類の指定したプロパティのすべての表現を検出します。

Collection<String> representationsOfProperty(DatastoreService ds, String kind, String property) {

  // Start with unrestricted non-keys-only property query
  Query q = new Query(Entities.PROPERTY_METADATA_KIND);

  // Limit to specified kind and property
  q.setFilter(
      new FilterPredicate(
          "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property)));

  // Get query result
  Entity propInfo = ds.prepare(q).asSingleEntity();

  // Return collection of property representations
  return (Collection<String>) propInfo.getProperty("property_representation");
}