Metadata

Datastore proporciona acceso programático a algunos de los metadatos a fin de admitir la metaprogramación, implementar funciones administrativas de backend, simplificar el almacenamiento en caché coherente y propósitos similares. Puedes usarlo, por ejemplo, si deseas compilar un visualizador de Datastore personalizado para la aplicación. Los metadatos disponibles incluyen información sobre los grupos de entidades, los espacios de nombres, los tipos de entidad y las propiedades que usa tu aplicación, así como las representaciones de propiedad para cada propiedad.

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

  • Actualidad: Cuando se leen los metadatos con la API, se obtienen los datos actuales, mientras que los datos del panel se actualizan solo una vez por día.
  • Contenido: Algunos metadatos del panel no están disponibles mediante las API y viceversa.
  • Velocidad: Las consultas y las solicitudes GET de metadatos se facturan de la misma forma que las de Datastore. En general, las consultas de metadatos que recuperan información sobre espacios de nombres, tipos y propiedades son de ejecución lenta. Como regla general, puedes esperar que una consulta de metadatos que muestra N entidades demore el mismo tiempo que N consultas ordinarias que muestran una sola entidad cada una. Además, las consultas de representación de propiedad (consultas de propiedad que no son de solo claves) son más lentas que las consultas de propiedad de solo claves. La obtención de metadatos de grupos de entidad es algo más rápida que la de una entidad normal.

Metadatos de grupo de entidades

Cloud Datastore brinda acceso a la “versión” de un grupo de entidades, un número estrictamente positivo que aumenta con cada cambio que se realice en el grupo de entidades.

Para obtener versiones de grupos de entidades, se debe llamar a get() en una seudoentidad especial que contiene una propiedad __version__ estrictamente positiva. La clave de la seudoentidad se puede crear mediante 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 heredado

Según el comportamiento heredado de la versión del grupo de entidades, esa versión aumenta solo cuando se incorporan cambios en el grupo de entidades. El comportamiento heredado de los metadatos del grupo de entidades se podría usar, por ejemplo, para mantener una caché coherente de una consulta principal compleja en un grupo de entidades.

En este ejemplo, se almacenan en caché los resultados de una consulta (conteo de resultados coincidentes) y se usa el comportamiento heredado de las versiones de grupos de entidades para emplear el valor almacenado en caché si es actual:

// 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 las entidades __entity_group__ no existan para grupos de entidades en los que nunca se escribió.

Consultas de metadatos

La clase Entities de Java, definida en el paquete com.google.appengine.api.datastore, proporciona tres tipos de entidad 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 que tengan el mismo nombre y que ya existan en tu aplicación. Si consultas estos tipos especiales, puedes recuperar entidades que contengan los metadatos deseados.

Las entidades que muestran las consultas de metadatos se generan de forma dinámica, según el estado actual de Datastore. Aunque puedes crear objetos Entity locales de tipos __namespace__, __kind__ o __property__, cualquier intento de almacenarlos en Datastore fallará con una IllegalArgumentException.

La manera más fácil de generar consultas de metadatos es con la API de Datastore de bajo nivel. El ejemplo a continuación imprime los nombres de todos los espacios de nombre en un 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 la aplicación usa la API de espacios de nombres, puedes usar una consulta de espacio de nombres para encontrar todos los 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 espacio de nombres muestran entidades de la categoría especial __namespace__ cuyo nombre de clave es el nombre de un espacio de nombres. Una excepción a esto es el espacio de nombres predeterminado que designa la string vacía ""; debido a que la string vacía no es un nombre de clave válido, este espacio de nombres recibe una clave con el ID numérico 1 en su lugar. Las consultas de este tipo solo admiten filtros para rangos superiores a la seudopropiedad especial __key__, cuyo valor es la clave de la entidad. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Debido a que las entidades __namespace__ no tienen propiedades, las consultas de solo claves y las que no son de solo claves muestran la misma información.

En el siguiente ejemplo, se muestra una lista de los espacios de nombres de una aplicación en el rango 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 tipos

Las consultas de categorías muestran entidades de categoría __kind__ cuyo nombre de clave es el nombre de una categoría de entidad. Las consultas de este tipo están restringidas de forma implícita al espacio de nombres actual y admiten el filtrado solo para rangos sobre la seudopropiedad __key__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Debido a que las entidades __kind__ no tienen propiedades, las consultas de solo claves y las que no son de solo claves muestran la misma información.

Los ejemplos a continuación muestran todos los tipos cuyos nombres comienzan con 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 muestran entidades de tipo __property__ que denotan las propiedades asociadas con un tipo de entidad. La entidad que representa la propiedad P del tipo K está compuesta por los siguientes elementos:

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

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

Consultas de propiedades: de solo claves

Las consultas de propiedad de solo claves muestran una clave para cada propiedad indexada de un tipo de entidad especificado. (No se incluyen las propiedades no indexadas). En el siguiente ejemplo, se muestran los nombres de todas las categorías de entidades de una aplicación y las propiedades asociadas a cada una:

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 de forma implícita al espacio de nombres actual y solo admiten filtros para rangos superiores a la seudopropiedad __key__, en los que las claves denotan entidades __kind__ o __property__. Los resultados se pueden ordenar por valor ascendente (pero no descendente) de __key__. Los filtros se aplican a los pares tipo-propiedad, ordenados primero por tipo y después por propiedad. Por ejemplo, imagina que tienes una entidad con las siguientes 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 que muestra los datos de la propiedad se verá de la siguiente manera:

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 mostraría lo siguiente:

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

Observa que los resultados no incluyen la propiedad name de tipo Employee, la propiedad title de tipo Manager, ni ninguna propiedad de tipos Account y Product, porque quedan fuera del rango especificado para la consulta.

Las consultas de propiedades también admiten el filtrado principal en una clave __kind__ o __property__ para limitar los resultados de las consultas a un solo tipo o propiedad. Por ejemplo, puedes aprovechar esa posibilidad para obtener las propiedades asociadas a un tipo de entidad determinado, como se ilustra a continuación:

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: de no solo claves (representación de propiedad)

Las consultas de propiedades de no solo claves, conocidas como consultas de representación de propiedad, muestran información adicional sobre las representaciones que usa cada par tipo-propiedad. (no se incluyen las propiedades no indexadas). La entidad que se muestra para la propiedad P de tipo K tiene la misma clave que la de una consulta de solo claves correspondiente, junto con una propiedad property_representation adicional que muestra las representaciones de la propiedad. El valor de esta propiedad es una instancia de la clase java.util.Collection<String> que contiene una string para cada representación de propiedad P que se encuentra en cualquier entidad de la categoría K.

Ten en cuenta que las representaciones no son las mismas que las clases de propiedad; varias clases de propiedad se pueden asignar a la misma representación (por ejemplo, java.lang.String y com.google.appengine.api.datastore.PhoneNumber usan la representación STRING).

La siguiente tabla muestra las asignaciones entre las clases de propiedad y sus representaciones:

Clase de propiedad 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 encuentran todas las representaciones de la propiedad especificada para un tipo de entidad dado:

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