Metadados

O Datastore fornece acesso programático a alguns de seus metadados para oferecer suporte à metaprogramação, implementar funções administrativas de back-end, simplificar o armazenamento em cache consistente e finalidades semelhantes. É possível usá-lo, por exemplo, para criar um visualizador personalizado do Datastore para seu aplicativo. Nos metadados disponíveis, há informações sobre grupos de entidades, namespaces, tipos de entidade e propriedades utilizadas pelo aplicativo, além de representações de cada propriedade.

O Painel do Datastore, no console do Google Cloud, também fornece alguns metadados sobre seu aplicativo, mas os dados exibidos nele diferem, em alguns aspectos importantes, dos retornados por essas funções.

  • Atualização. A leitura de metadados pela API fornece dados atuais, enquanto os dados do painel são atualizados somente uma vez por dia.
  • Conteúdo. Alguns metadados no painel não estão disponíveis através das APIs e vice-versa.
  • Velocidade. Consultas e gets de metadados são faturados da mesma maneira que consultas e gets do Datastore. As consultas de metadados que buscam informações sobre namespaces, tipos e propriedades geralmente demoram a ser executadas. Como regra geral, uma consulta de metadados que retorna N entidades costuma levar mais ou menos o mesmo tempo que consultas N comuns, cada uma retornando uma única entidade. Além disso, as consultas de representação de propriedade, que são de propriedade sem chave, são mais lentas do que as consultas de propriedade apenas de chaves. Os resultados de metadados do grupo de entidades são um pouco mais rápidos do que uma entidade normal.

Metadados do grupo de entidades

No Cloud Datastore é possível ter acesso à "versão" de um grupo de entidades, um número estritamente positivo que tem garantia de aumentar a cada alteração no grupo de entidades.

As versões de grupo de entidades são recebidas chamando get() em uma pseudoentidade especial que contém uma propriedade __version__ estritamente positiva. A chave da pseudoentidade pode ser criada usando o 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));
}

Comportamento legado

No comportamento legado da versão do grupo de entidades, a versão aumenta apenas em alterações no grupo de entidades. O comportamento legado dos metadados do grupo de entidades poderia ser usado, por exemplo, para manter um cache consistente de uma consulta de ancestral complexa em um grupo de entidades.

Neste exemplo, armazenamos em cache os resultados da consulta (uma contagem de resultados correspondentes) e usamos o comportamento legado das versões do grupo de entidades para utilizar o valor em cache se ele for atual:

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

As entidades __entity_group__ podem não existir para grupos de entidades que nunca foram gravados.

Consultas de metadados

A classe Java Entities, definida no pacote com.google.appengine.api.datastore, fornece três tipos de entidades especiais que são reservadas para consultas de metadados. Eles são indicados por constantes estáticas da classe Entities:

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

Esses tipos não entrarão em conflito com outros de nomes iguais que já podem existir no aplicativo. Ao consultar esses tipos especiais, você pode recuperar entidades que contenham os metadados desejados.

As entidades retornadas por consultas de metadados são geradas dinamicamente, com base no estado atual do Datastore. Embora seja possível criar objetos Entity locais dos tipos __namespace__, __kind__ ou __property__, qualquer tentativa de armazená-los no Datastore falhará com um IllegalArgumentException .

A maneira mais fácil de enviar consultas de metadados é com a API Datastore de baixo nível. O exemplo a seguir grava os nomes de todos os namespaces em um aplicativo:

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 namespace

Se o aplicativo opta pela API Namespaces, é possível utilizar uma consulta de namespace para encontrar todos os namespaces usados nas entidades do aplicativo. Isso permite que você execute atividades, como funções administrativas, em vários namespaces.

As consultas de namespace retornam entidades do tipo especial __namespace__, cujo nome de chave é o nome de um namespace. Uma exceção é o namespace padrão designado pela string vazia "": como a string vazia não é um nome de chave válido, esse namespace é identificado pelo ID numérico 1. As consultas desse tipo são compatíveis apenas com a filtragem de intervalos na pseudopropriedade especial __key__, cujo valor é a chave da entidade. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). Como as entidades __namespace__ não têm propriedades, as consultas apenas de chaves e sem chave retornam as mesmas informações.

O exemplo a seguir retorna uma lista de namespaces de um aplicativo no intervalo entre dois nomes especificados, start e 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

Consultas de tipo retornam entidades do tipo __kind__, cujo nome de chave é o nome de um tipo de entidade. As consultas desse tipo são implicitamente restritas ao namespace atual e são compatíveis apenas com a filtragem para intervalos acima da pseudopropriedade __key__. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). Como as entidades __kind__ não têm propriedades, as consultas apenas de chave e sem chave retornam as mesmas informações.

O exemplo a seguir imprime todos os tipos com nomes que começam com uma 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 propriedade

As consultas de propriedade retornam entidades do tipo __property__ que denotam as propriedades associadas a um tipo de entidade. A entidade que representa a propriedade P do tipo K é criada da seguinte forma:

  • A chave da entidade tem o tipo __property__ e o nome de chave P.
  • A chave da entidade pai tem o tipo __kind__ e o nome da chave K.

O comportamento de uma consulta de propriedade depende do fato de ela ser uma consulta apenas de chaves ou apenas sem chave (representação de propriedade), conforme detalhado nas subseções a seguir.

Consultas de propriedade: somente com chaves

As consultas de propriedade somente com chaves retornam uma chave para cada propriedade indexada de um tipo de entidade especificado. As propriedades não indexadas não estão incluídas. O exemplo abaixo imprime os nomes de todos os tipos de entidade do aplicativo, bem como as propriedades associadas com cada um deles:

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

As consultas desse tipo são restritas implicitamente ao namespace atual e à filtragem de suporte apenas para intervalos na pseudopropriedade __key__, em que as chaves denotam entidades __kind__ ou __property__. Os resultados podem ser classificados por valor __key__ crescente (mas não decrescente). A filtragem é aplicada a pares de propriedade-tipo, classificados primeiro por tipo e depois por propriedade. Por exemplo, suponha que você tenha uma entidade com estas propriedades:

  • tipo Account com as propriedades
    • balance
    • company
  • tipo Employee com as propriedades
    • name
    • ssn
  • tipo Invoice com as propriedades
    • date
    • amount
  • tipo Manager com as propriedades
    • name
    • title
  • tipo Product com as propriedades
    • description
    • price

A consulta para retornar os dados da propriedade tem esta aparência:

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

A consulta acima apresenta o seguinte resultado:

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

Os resultados não incluem a propriedade name do tipo Employee e a propriedade title do tipo Manager, nem quaisquer propriedades dos tipos Account e Product, porque estão fora do intervalo especificado para a consulta.

As consultas de propriedade também são compatíveis com a filtragem de ancestrais em uma chave __kind__ ou __property__ para limitar os resultados da consulta a um único tipo ou propriedade. Você pode usar isso, por exemplo, para ver as propriedades associadas a um determinado tipo de entidade, como no exemplo a seguir:

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 propriedade: somente sem chaves (representação de propriedade)

As consultas de propriedade somente sem chaves, conhecidas como consultas de representação de propriedade, retornam mais informações sobre as representações usadas por cada par de propriedade-tipo. As propriedades não indexadas não estão incluídas. A entidade retornada para a propriedade P do tipo K tem a mesma chave de uma consulta apenas de chaves, junto com uma propriedade property_representation adicional que retorna as representações da propriedade. O valor dessa propriedade é uma instância da classe java.util.Collection<String> que contém uma string para cada representação da propriedade P encontrada em qualquer entidade do tipo K.

Representações e classes de propriedades não são iguais. Várias classes de propriedades podem ser mapeadas para a mesma representação. Por exemplo, java.lang.String e com.google.appengine.api.datastore.PhoneNumber usam a representação STRING.

Veja na tabela a seguir o mapeamento das classes de propriedades para as respectivas representações:

Classe de propriedade Representação
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> Representação de T

O exemplo a seguir encontra todas as representações de uma propriedade especificada para um determinado tipo de entidade:

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