Metadata

Datastore offre un accès automatisé à certaines de ses métadonnées de façon à permettre la métaprogrammation, à mettre en œuvre des fonctions d'administration de backend, à simplifier la mise en cache cohérente et à des fins semblables. Par exemple, vous pouvez vous en servir pour créer une visionneuse Datastore personnalisée pour votre application. Les métadonnées disponibles incluent des informations sur les groupes d'entités, les espaces de noms, les genres d'entités et les propriétés qu'emploie l'application, ainsi que sur les représentations de chaque propriété.

Le tableau de bord Datastore de la console Google Cloud fournit également des métadonnées sur votre application, mais les données affichées diffèrent nettement à certains égards de celles renvoyées par ces fonctions.

  • Actualisation. La lecture des métadonnées à l'aide de l'API permet d'obtenir les données actuelles, tandis que les données du tableau de bord ne sont actualisées qu'une fois par jour.
  • Contenus. Certaines métadonnées du tableau de bord ne sont pas disponibles via les API, et vice versa.
  • Rapidité. Les opérations d'obtention et de requête de métadonnées sont facturées de la même manière que celles appliquées à un datastore. L'exécution de requêtes de métadonnées qui extraient des informations sur les espaces de noms, les genres et les propriétés est habituellement lente. En règle générale, partez du principe qu'une requête de métadonnées renvoyant N entités va prendre à peu près le même temps que N requêtes ordinaires renvoyant chacune une seule entité. De plus, les requêtes de représentation de propriété (requêtes de propriété ne contenant pas que des clés) sont plus lentes que les requêtes de propriété ne contenant que des clés. L'obtention des métadonnées d'un groupe d'entités est légèrement plus rapide que pour les métadonnées d'une entité standard.

Métadonnées de groupe d'entités

Cloud Datastore donne accès à la "version" d'un groupe d'entités, un nombre strictement positif dont l'augmentation est garantie à chaque modification du groupe d'entités.

Les versions de groupe d'entités s'obtiennent en appelant la fonction get() sur une pseudo-entité spéciale contenant une propriété __version__ strictement positive. La clé de la pseudo-entité peut être créée à l'aide de la méthode 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));
}

Ancien comportement

Lorsque vous conservez l'ancien comportement d'une version de groupe d'entités, celle-ci n'augmente que si des modifications sont apportées au groupe d'entités. L'ancien comportement des métadonnées d'un groupe d'entités peut servir, par exemple, à assurer la cohérence des résultats mis en cache générés par une requête ascendante complexe au sein d'un groupe d'entités.

Cet exemple met en cache les résultats (un nombre de résultats) d'une requête et sollicite l'ancien comportement des versions de groupe d'entités pour utiliser la valeur mise en cache si elle est actuelle :

// 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");
    }
  }
}

Les entités __entity_group__ peuvent ne pas exister pour les groupes d'entités sur lesquels aucune écriture n'a été effectuée.

Requêtes de métadonnées

La classe Java Entities, définie dans le package com.google.appengine.api.datastore, fournit trois genres d'entités spéciaux réservés aux requêtes de métadonnées. Ils sont désignés par des constantes statiques de la classe Entities :

Constante statique Genre d'entité
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

Ces genres n'entrent pas en conflit avec d'autres du même nom pouvant déjà exister dans l'application. En exécutant des requêtes sur ces genres spéciaux, vous pouvez récupérer des entités contenant les métadonnées souhaitées.

Les entités renvoyées par les requêtes de métadonnées sont générées de manière dynamique, en fonction de l'état actuel de Datastore. Bien que vous puissiez créer des objets Entity locaux de genres __namespace__, __kind__ ou __property__, toute tentative de stockage dans Datastore renverra une erreur IllegalArgumentException.

Le moyen le plus simple d'émettre des requêtes de métadonnées consiste à utiliser l'API Datastore de bas niveau. L'exemple suivant imprime les noms de tous les espaces de noms dans une application :

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());
    }
  }
}

Requêtes d'espace de noms

Si votre application utilise l'API Namespaces, vous pouvez exécuter une requête d'espace de noms pour rechercher tous les espaces de noms utilisés dans les entités de l'application. Cela vous permet par exemple d'effectuer des tâches d'administration sur plusieurs espaces de noms.

Les requêtes d'espace de noms renvoient des entités du genre spécial __namespace__, dont le nom de clé correspond à celui d'un espace de noms. (L'espace de noms par défaut désigné par la chaîne vide "" constitue une exception : comme la chaîne vide n'est pas un nom de clé valide, cet espace de noms est associé à l'ID numérique 1 à la place.) Les requêtes de ce type n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété spéciale __key__, dont la valeur est la clé de l'entité. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Comme les entités __namespace__ n'ont pas de propriétés, les requêtes renvoient les mêmes informations, qu'elles contiennent exclusivement des clés ou non.

L'exemple suivant renvoie une liste des espaces de noms d'une application dans la plage comprise entre deux noms spécifiés, start et end :

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;
}

Requêtes de genre

Les requêtes de genre renvoient des entités du genre __kind__, dont le nom de clé correspond à celui d'un genre d'entité. Les requêtes de ce type sont implicitement restreintes à l'espace de noms actuel et n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété __key__. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Comme les entités __kind__ n'ont pas de propriétés, les requêtes renvoient les mêmes informations, qu'elles contiennent exclusivement des clés ou non.

L'exemple suivant renvoie tous les genres dont le nom commence par une lettre minuscule :

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());
  }
}

Requêtes de propriété

Les requêtes de propriété renvoient des entités du genre __property__ indiquant les propriétés associées à un genre d'entité. L'entité représentant la propriété p du genre k est conçue comme suit :

  • La clé de l'entité est du genre __property__ et porte le nom p.
  • La clé de l'entité parente est du genre __kind__ et porte le nom K.

Une requête de propriété se comporte différemment selon qu'il s'agit d'une requête ne contenant que des clés ou d'une requête ne contenant pas que des clés (représentation de propriétés), comme détaillé dans les sous-sections ci-dessous.

Requêtes de propriété ne contenant que des clés

Les requêtes de propriété ne contenant que des clés renvoient une clé pour chaque propriété indexée d'un genre d'entité spécifié. (Les propriétés non indexées ne sont pas incluses.) L'exemple suivant imprime les noms de tous les genres d'entités d'une application et les propriétés associées à chacun d'eux :

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());
  }
}

Les requêtes de ce type sont implicitement restreintes à l'espace de noms actuel et n'acceptent le filtrage que pour les plages définies pour la pseudo-propriété __key__, lorsque les clés désignent des entités __kind__ ou __property__. Les résultats peuvent être triés par ordre croissant (mais pas décroissant) de valeur __key__. Le filtrage est appliqué aux paires genre-propriété, triées d'abord par genre et ensuite par propriété. Supposons, par exemple, qu'une entité ait les propriétés suivantes :

  • genre Account avec des propriétés
    • balance
    • company
  • genre Employee avec des propriétés
    • name
    • ssn
  • genre Invoice avec des propriétés
    • date
    • amount
  • genre Manager avec des propriétés
    • name
    • title
  • genre Product avec des propriétés
    • description
    • price

La requête qui doit renvoyer les données de propriété ressemblerait à ceci :

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());
  }
}

La requête ci-dessus renverrait les éléments suivants :

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

Notez que les résultats n'incluent pas la propriété name du genre Employee, ni la propriété title du genre Manager, ni aucune propriété du genre Account ou Product, car elles ne sont pas comprises dans la plage spécifiée pour la requête.

Les requêtes de propriété acceptent également le filtrage en fonction des ancêtres sur une clé __kind__ ou __property__, afin de limiter les résultats à un seul genre ou à une seule propriété. Vous pouvez, par exemple, suivre cette approche pour obtenir les propriétés associées à un genre d'entité donné, comme dans l'exemple suivant :

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;
}

Requêtes de propriété ne contenant pas que des clés (représentation de propriété)

Les requêtes de propriété ne contenant pas que des clés, appelées requêtes de représentation de propriété, renvoient des informations supplémentaires sur les représentations utilisées par chaque paire genre-propriété. (Les propriétés non indexées ne sont pas incluses.) L'entité renvoyée pour une propriété P de genre K a la même clé qu'une requête correspondante ne contenant que des clés, mais également une propriété property_representation supplémentaire qui renvoie les représentations de la propriété. La valeur de cette propriété correspond à une instance de la classe java.util.Collection<String> contenant une chaîne pour chaque représentation de la propriété P trouvée dans une entité de genre K.

Notez que les représentations ne sont pas identiques aux classes de propriétés. Plusieurs classes de propriétés peuvent correspondre à la même représentation. Par exemple, les classes java.lang.String et com.google.appengine.api.datastore.PhoneNumber utilisent toutes deux la représentation STRING.

Le tableau suivant établit une correspondance entre les classes de propriétés et leurs représentations :

Classe de propriété Représentation
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> Représentation de T

L'exemple suivant trouve toutes les représentations d'une propriété spécifiée pour un genre d'entité donné :

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");
}