Metadatos

Datastore proporciona acceso mediante programación a algunos de sus metadatos para admitir la metaprogramación, implementar funciones administrativas de backend, simplificar el almacenamiento en caché coherente y otros fines similares. Por ejemplo, puedes usarlo para crear un visor de Datastore personalizado para tu aplicación. Los metadatos disponibles incluyen información sobre los grupos de entidades, los espacios de nombres, los tipos de entidades y las propiedades que usa tu aplicación, así como las representaciones de propiedades de cada propiedad.

El panel de control de Datastore de la consola Google Cloud también proporciona algunos metadatos sobre tu aplicación, pero los datos que se muestran allí difieren en algunos aspectos importantes de los que devuelven estas funciones.

  • Actualización. Al leer los metadatos mediante la API, se obtienen los datos actuales, mientras que los datos del panel de control solo se actualizan una vez al día.
  • Contenido. Algunos metadatos del panel de control no están disponibles a través de las APIs, y viceversa.
  • Velocidad. Las operaciones de obtención y consulta de metadatos se facturan de la misma forma que las operaciones de obtención y consulta de Datastore. Las consultas de metadatos que obtienen información sobre espacios de nombres, tipos y propiedades suelen tardar en ejecutarse. Por lo general, una consulta de metadatos que devuelva N entidades tardará aproximadamente lo mismo que N consultas normales que devuelvan una sola entidad. Además, las consultas de representación de propiedades (consultas de propiedades que no son solo de claves) son más lentas que las consultas de propiedades que son solo de claves. Las obtenciones de metadatos de grupos de entidades son algo más rápidas que las de entidades normales.

Metadatos de grupos de entidades

Cloud Datastore proporciona acceso a la "versión" de un grupo de entidades, un número estrictamente positivo que aumenta en cada cambio del grupo de entidades.

Las versiones de grupos de entidades se obtienen llamando a get() en una pseudoentidad especial que contiene una propiedad __version__ estrictamente positiva. La clave de la seudoentidad se puede crear con el método 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));
}

Comportamiento antiguo

En el comportamiento de la versión antigua del grupo de entidades, la versión del grupo de entidades solo aumenta cuando se producen cambios en el grupo de entidades. El comportamiento de los metadatos de grupos de entidades antiguos se puede usar, por ejemplo, para mantener una caché coherente de una consulta de ancestros compleja en un grupo de entidades.

En este ejemplo se almacenan en caché los resultados de una consulta (un recuento de los resultados coincidentes) y se usa el comportamiento antiguo de las versiones de grupos de entidades para usar el valor almacenado en caché si está actualizado:

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

Es posible que no existan entidades __entity_group__ para grupos de entidades en los que nunca se haya escrito.

consultas de metadatos

La clase Java Entities, definida en el paquete com.google.appengine.api.datastore, proporciona tres tipos de entidades especiales que se reservan para las consultas de metadatos. Se denotan mediante constantes estáticas de la clase Entities:

Constante estática Tipo de entidad
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

Estos tipos no entrarán en conflicto con otros del mismo nombre que ya puedan existir en tu aplicación. Si consultas estos tipos especiales, puedes recuperar las entidades que contengan los metadatos que quieras.

Las entidades devueltas por las consultas de metadatos se generan de forma dinámica en función del estado actual de Datastore. Aunque puedes crear objetos locales Entity de los tipos __namespace__, __kind__ o __property__, cualquier intento de almacenarlos en Datastore fallará y se mostrará un error IllegalArgumentException.

La forma más sencilla de enviar consultas de metadatos es con la API Datastore de nivel inferior. En el siguiente ejemplo se imprimen los nombres de todos los espacios de nombres de una aplicación:

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

Consultas de espacio de nombres

Si tu aplicación usa la API Namespaces, puedes usar una consulta de espacio de nombres para encontrar todos los espacios de nombres que se usan en las entidades de la aplicación. Esto te permite realizar actividades como funciones administrativas en varios espacios de nombres.

Las consultas de espacios de nombres devuelven entidades del tipo especial __namespace__ cuya clave name es el nombre de un espacio de nombres. (La excepción es el espacio de nombres predeterminado, designado por la cadena vacía "". Como la cadena vacía no es un nombre de clave válido, este espacio de nombres se identifica con el ID numérico 1). Las consultas de este tipo solo admiten filtros de intervalos de la pseudopropiedad especial __key__, cuyo valor es la clave de la entidad. Los resultados se pueden ordenar por el valor __key__ en orden ascendente (pero no descendente). Como las entidades __namespace__ no tienen propiedades, tanto las consultas de solo claves como las que no son de solo claves devuelven la misma información.

En el siguiente ejemplo se devuelve una lista de los espacios de nombres de una aplicación en el intervalo entre dos nombres especificados, start y 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;
}

Consultas de tipo

Las consultas de tipo devuelven entidades de tipo __kind__ cuya clave es el nombre de un tipo de entidad. Las consultas de este tipo se restringen implícitamente al espacio de nombres actual y solo admiten filtros de intervalos de la pseudopropiedad __key__. Los resultados se pueden ordenar de forma ascendente (pero no descendente) por el valor __key__. Como las entidades __kind__ no tienen propiedades, las consultas que solo devuelven claves y las que no devuelven la misma información.

En el siguiente ejemplo se imprimen todos los tipos cuyos nombres empiezan por una letra minúscula:

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

Consultas de propiedades

Las consultas de propiedades devuelven entidades de tipo __property__ que denotan las propiedades asociadas a un tipo de entidad. La entidad que representa la propiedad P de tipo K se crea de la siguiente manera:

  • La clave de la entidad tiene el tipo __property__ y el nombre de clave P.
  • La clave de la entidad superior tiene el tipo __kind__ y el nombre de clave K.

El comportamiento de una consulta de propiedad depende de si es una consulta solo de claves o una consulta que no es solo de claves (representación de propiedad), como se detalla en las subsecciones siguientes.

Consultas de propiedades: solo claves

Las consultas de propiedades solo con claves devuelven una clave por cada propiedad indexada de un tipo de entidad especificado. Las propiedades sin indexar no se incluyen. En el siguiente ejemplo se imprimen los nombres de todos los tipos de entidades de una aplicación y las propiedades asociadas a cada uno:

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

Las consultas de este tipo se restringen implícitamente al espacio de nombres actual y solo admiten filtros de intervalos de la pseudopropiedad __key__, donde las claves denotan entidades __kind__ o __property__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) __key__. El filtrado se aplica a los pares de tipo y propiedad, ordenados primero por tipo y, después, por propiedad. Por ejemplo, supongamos que tiene una entidad con estas propiedades:

  • tipo Account con propiedades
    • balance
    • company
  • tipo Employee con propiedades
    • name
    • ssn
  • tipo Invoice con propiedades
    • date
    • amount
  • tipo Manager con propiedades
    • name
    • title
  • tipo Product con propiedades
    • description
    • price

La consulta para devolver los datos de la propiedad sería la siguiente:

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 consulta anterior devolvería lo siguiente:

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

Ten en cuenta que los resultados no incluyen la propiedad name del tipo Employee ni la propiedad title del tipo Manager, ni ninguna propiedad de los tipos Account y Product, porque están fuera del intervalo especificado en la consulta.

Las consultas de propiedades también admiten el filtrado de ancestros en una clave __kind__ o __property__ para limitar los resultados de la consulta a un solo tipo o propiedad. Por ejemplo, puedes usarlo para obtener las propiedades asociadas a un tipo de entidad determinado, como en el siguiente ejemplo:

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

Consultas de propiedades: no solo de claves (representación de la propiedad)

Las consultas de propiedades que no son solo de claves, conocidas como consultas de representación de propiedades, devuelven información adicional sobre las representaciones utilizadas por cada par de tipo y propiedad. Las propiedades sin indexar no se incluyen. La entidad devuelta para la propiedad P de tipo K tiene la misma clave que la de una consulta de solo claves correspondiente, así como una propiedad property_representation adicional que devuelve las representaciones de la propiedad. El valor de esta propiedad es una instancia de la clase java.util.Collection<String> que contiene una cadena por cada representación de la propiedad P que se encuentra en cualquier entidad de tipo K.

Ten en cuenta que las representaciones no son lo mismo que las clases de propiedad. Varias clases de propiedad pueden asignarse a la misma representación. Por ejemplo, java.lang.String y com.google.appengine.api.datastore.PhoneNumber usan la representación STRING.

En la siguiente tabla se muestra la asignación de clases de propiedad a sus representaciones:

Clase Property Representación
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> Representación de T

En el siguiente ejemplo se buscan todas las representaciones de una propiedad especificada de un tipo de entidad determinado:

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