Metadados

O Datastore oferece acesso programático a alguns dos respetivos metadados para suportar a metaprogramação, implementar funções administrativas de back-end, simplificar o armazenamento em cache consistente e fins semelhantes. Pode usá-lo, por exemplo, para criar um visualizador do Datastore personalizado para a sua aplicação. Os metadados disponíveis incluem informações sobre os grupos de entidades, os espaços de nomes, os tipos de entidades e as propriedades que a sua aplicação usa, bem como as representações de propriedades para cada propriedade.

O painel de controlo do Datastore na consola também fornece alguns metadados sobre a sua aplicação, mas os dados apresentados aí diferem em alguns aspetos importantes dos devolvidos por estas funções. Google Cloud

  • Atualidade. A leitura de metadados através da API obtém dados atuais, enquanto os dados no painel de controlo são atualizados apenas uma vez por dia.
  • Conteúdo. Alguns metadados no painel de controlo não estão disponíveis através das APIs. O inverso também é verdadeiro.
  • Velocidade. As consultas e as obtenções de metadados são faturadas da mesma forma que as consultas e as obtenções do Datastore. As consultas de metadados que obtêm informações sobre espaços de nomes, tipos e propriedades são, geralmente, lentas de executar. Como regra geral, espere que uma consulta de metadados que devolve N entidades demore aproximadamente o mesmo tempo que N consultas comuns, cada uma das quais devolve uma única entidade. Além disso, as consultas de representação de propriedades (consultas de propriedades apenas com chaves) são mais lentas do que as consultas de propriedades apenas com chaves. As obtenções de metadados de metadados de grupos de entidades são ligeiramente mais rápidas do que a obtenção de uma entidade normal.

Metadados do grupo de entidades

O Cloud Datastore fornece acesso à "versão" de um grupo de entidades, um número estritamente positivo que tem a garantia de aumentar em todas as alterações ao grupo de entidades.

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

No comportamento da versão do grupo de entidades antigo, a versão do grupo de entidades só aumenta quando são feitas alterações ao grupo de entidades. O comportamento dos metadados do grupo de entidades antigo pode ser usado, por exemplo, para manter uma cache consistente de uma consulta de antepassados complexa num grupo de entidades.

Este exemplo coloca em cache os resultados da consulta (uma contagem de resultados correspondentes) e usa o comportamento antigo das versões do grupo de entidades para usar o valor em cache se estiver atualizado:

// 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__ entidades podem não existir para grupos de entidades que nunca foram escritos.

Consultas de metadados

A classe Java Entities, definida no pacote com.google.appengine.api.datastore, fornece três tipos de entidades especiais que estão reservados para consultas de metadados. São denotados 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__

Estes tipos não entram em conflito com outros do mesmo nome que possam já existir na sua aplicação. Ao consultar estes tipos especiais, pode obter entidades que contêm os metadados pretendidos.

As entidades devolvidas pelas consultas de metadados são geradas dinamicamente com base no estado atual do Datastore. Embora possa criar objetos locais dos tipos __namespace__, __kind__ ou __property__, qualquer tentativa de os armazenar no Datastore falha com um IllegalArgumentException.Entity

A forma mais fácil de emitir consultas de metadados é com a API Datastore de baixo nível. O exemplo seguinte imprime os nomes de todos os espaços de nomes numa aplicação:

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 espaços de nomes

Se a sua aplicação usar a API Namespaces, pode usar uma consulta de espaço de nomes para encontrar todos os espaços de nomes usados nas entidades da aplicação. Isto permite-lhe realizar atividades como funções administrativas em vários espaços de nomes.

As consultas de espaço de nomes devolvem entidades do tipo especial __namespace__ cuja chave é o nome de um espaço de nomes. (Uma exceção é o espaço de nomes predefinido designado pela string vazia "": uma vez que a string vazia não é um nome de chave válido, este espaço de nomes é indexado com o ID numérico 1.) As consultas deste tipo suportam a filtragem apenas para intervalos sobre a pseudopropriedade especial __key__, cujo valor é a chave da entidade. Os resultados podem ser ordenados pelo valor __key__ ascendente (mas não descendente). Uma vez que as entidades __namespace__ não têm propriedades, as consultas apenas com chaves e sem chaves devolvem as mesmas informações.

O exemplo seguinte devolve uma lista dos espaços de nomes de uma aplicação 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

As consultas de tipo devolvem entidades do tipo __kind__ cujo nome da chave é o nome de um tipo de entidade. As consultas deste tipo estão implicitamente restritas ao espaço de nomes atual e suportam a filtragem apenas para intervalos sobre a pseudopropriedade __key__. Os resultados podem ser ordenados por valor de __key__ ascendente (mas não descendente). Uma vez que as entidades __kind__ não têm propriedades, as consultas apenas com chaves e sem chaves devolvem as mesmas informações.

O exemplo seguinte imprime todos os tipos cujos nomes começam por 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 propriedades

As consultas de propriedades devolvem 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 da chave P.
  • A chave da entidade principal tem o tipo __kind__ e o nome da chave K.

O comportamento de uma consulta de propriedade depende de ser uma consulta apenas com chaves ou sem ser apenas com chaves (representação de propriedade), conforme detalhado nas subsecções abaixo.

Consultas de propriedades: apenas chaves

As consultas de propriedades apenas com chaves devolvem 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 seguinte imprime os nomes de todos os tipos de entidades de uma aplicação e as propriedades associadas a cada um:

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 deste tipo estão implicitamente restritas ao espaço de nomes atual e suportam a filtragem apenas para intervalos sobre a pseudopropriedade __key__, em que as chaves denotam entidades __kind__ ou __property__. Os resultados podem ser ordenados pelo valor __key__ ascendente (mas não descendente). A filtragem é aplicada a pares de tipo-propriedade, ordenados primeiro por tipo e, em segundo lugar, por propriedade. Por exemplo, suponhamos que tem uma entidade com estas propriedades:

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

A consulta para devolver os dados da propriedade teria o seguinte aspeto:

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 devolveria o seguinte:

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

Repare que 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 propriedades também suportam a filtragem de antepassados numa chave __kind__ ou __property__, para limitar os resultados da consulta a um único tipo ou propriedade. Pode usar isto, por exemplo, para obter as propriedades associadas a um determinado tipo de entidade, como no exemplo seguinte:

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 propriedades: não apenas chaves (representação de propriedades)

As consultas de propriedades que não são apenas de chaves, conhecidas como consultas de representação de propriedades, devolvem informações adicionais sobre as representações usadas por cada par de tipo-propriedade. (As propriedades não indexadas não estão incluídas.) A entidade devolvida para a propriedade P do tipo K tem a mesma chave que para uma consulta apenas de chaves, juntamente com uma propriedade property_representation adicional que devolve as representações da propriedade. O valor desta 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.

Tenha em atenção que as representações não são o mesmo que as classes de propriedades. 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.)

A tabela seguinte faz o mapeamento das classes de propriedades para as respetivas 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 seguinte encontra todas as representações de uma propriedade especificada para um tipo de entidade 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");
}