Metadati

Datastore fornisce accesso programmatico ad alcuni dei suoi metadati per supportare la metaprogrammazione, implementare funzioni amministrative di backend, semplificare la memorizzazione nella cache coerente e scopi simili. Puoi utilizzarlo, ad esempio, per creare un visualizzatore Datastore personalizzato per la tua applicazione. I metadati disponibili includono informazioni su gruppi di entità, spazi dei nomi, tipi di entità e proprietà utilizzati dalla tua applicazione, nonché le rappresentazioni delle proprietà per ogni proprietà.

La dashboard di Datastore nella console Google Cloud fornisce anche alcuni metadati sulla tua applicazione, ma i dati visualizzati sono diversi per alcuni aspetti importanti da quelli restituiti da queste funzioni.

  • Aggiornamento. La lettura dei metadati tramite l'API restituisce i dati attuali, mentre i dati della dashboard vengono aggiornati solo una volta al giorno.
  • Contenuti. Alcuni metadati nella dashboard non sono disponibili tramite le API e viceversa.
  • Velocità. Le query e i get dei metadati vengono fatturati nello stesso modo delle query e dei get di Datastore. Le query sui metadati che recuperano informazioni su spazi dei nomi, tipi e proprietà sono generalmente lente da eseguire. Come regola generale, una query sui metadati che restituisce N entità richiede circa lo stesso tempo di N query ordinarie che ciascuna restituisce una singola entità. Inoltre, le query di rappresentazione delle strutture (query sulle strutture non solo con chiavi) sono più lente rispetto alle query sulle strutture solo con chiavi. I metadati dei gruppi di entità sono leggermente più veloci rispetto all'ottenimento di un'entità normale.

Metadati del gruppo di entità

Cloud Datastore fornisce l'accesso alla "versione" di un gruppo di entità, un numero strettamente positivo che aumenta a ogni modifica del gruppo di entità.

Le versioni dei gruppi di entità vengono ottenute chiamando get() su una pseudo-entità speciale che contiene una proprietà __version__ strettamente positiva. La chiave della pseudo-entità può essere creata utilizzando il metodo 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 precedente

Nel comportamento precedente della versione del gruppo di entità, la versione del gruppo di entità aumenta solo in caso di modifiche al gruppo di entità. Il comportamento precedente dei metadati dei gruppi di entità potrebbe essere utilizzato, ad esempio, per mantenere una cache coerente di una query sull'antenato complessa in un gruppo di entità.

Questo esempio memorizza nella cache i risultati della query (un conteggio dei risultati corrispondenti) e utilizza il comportamento precedente delle versioni del gruppo di entità per utilizzare il valore memorizzato nella cache se è aggiornato:

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

Le entità __entity_group__ potrebbero non esistere per i gruppi di entità in cui non è mai stata eseguita una scrittura.

Query dei metadati

La classe Java Entities, definita nel pacchetto com.google.appengine.api.datastore, fornisce tre tipi di entità speciali riservati alle query sui metadati. Sono indicati da costanti statiche della classe Entities:

Costante statica Tipo di entità
Entities.NAMESPACE_METADATA_KIND __namespace__
Entities.KIND_METADATA_KIND __kind__
Entities.PROPERTY_METADATA_KIND __property__

Questi tipi non entrano in conflitto con altri con gli stessi nomi che potrebbero già esistere nella tua applicazione. Eseguendo query su questi tipi speciali, puoi recuperare le entità contenenti i metadati desiderati.

Le entità restituite dalle query sui metadati vengono generate in modo dinamico in base allo stato corrente di Datastore. Sebbene sia possibile creare oggetti Entity locali di tipi __namespace__, __kind__ o __property__, qualsiasi tentativo di archiviarli in Datastore non andrà a buon fine con un IllegalArgumentException.

Il modo più semplice per eseguire query sui metadati è tramite l'API Datastore di basso livello. L'esempio seguente stampa i nomi di tutti gli spazi dei nomi di un'applicazione:

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

Query sullo spazio dei nomi

Se la tua applicazione utilizza l'API Namespaces, puoi utilizzare una query sullo spazio dei nomi per trovare tutti gli spazi dei nomi utilizzati nelle entità dell'applicazione. In questo modo puoi eseguire attività come le funzioni amministrative su più spazi dei nomi.

Le query sullo spazio dei nomi restituiscono entità del tipo speciale __namespace__ il cui nome della chiave è il nome di uno spazio dei nomi. Un'eccezione è lo spazio dei nomi predefinito designato dalla stringa vuota "": poiché la stringa vuota non è un nome di chiave valido, questo spazio dei nomi è associato all'ID numerico 1. Le query di questo tipo supportano il filtro solo per gli intervalli della pseudoproprietà __key__ speciale, il cui valore è la chiave dell'entità. I risultati possono essere ordinati in base al valore __key__ crescente (ma non decrescente). Poiché le entità __namespace__ non hanno proprietà, sia le query basate solo su chiavi sia quelle non basate solo su chiavi restituiscono le stesse informazioni.

L'esempio seguente restituisce un elenco degli spazi dei nomi di un'applicazione nell'intervallo tra due nomi specificati, 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;
}

Query sui tipi

Le query sui tipi restituiscono entità di tipo __kind__ il cui nome della chiave è il nome di un tipo di entità. Le query di questo tipo sono limitate implicitamente allo spazio dei nomi corrente e supportano l'applicazione di filtri solo per gli intervalli della pseudoproprietà __key__. I risultati possono essere ordinati in base al valore __key__ crescente (ma non decrescente). Poiché le entità __kind__ non hanno proprietà, sia le query basate solo su chiavi sia quelle non basate solo su chiavi restituiscono le stesse informazioni.

Il seguente esempio stampa tutti i tipi i cui nomi iniziano con una lettera minuscola:

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

Query sulle proprietà

Le query sulle proprietà restituiscono entità di tipo __property__ che indicano le proprietà associate a un tipo di entità. L'entità che rappresenta la proprietà P di tipo K viene creata come segue:

  • La chiave dell'entità ha il tipo __property__ e il nome della chiave P.
  • La chiave dell'entità principale ha il tipo __kind__ e il nome della chiave K.

Il comportamento di una query sulla proprietà dipende dal fatto che si tratti di una query solo chiavi o non solo chiavi (rappresentazione della proprietà), come descritto nelle sezioni seguenti.

Query sulle proprietà: solo chiavi

Le query sulle proprietà solo chiavi restituiscono una chiave per ogni proprietà indicizzata di un tipo di entità specificato. Le proprietà non indicizzate non sono incluse. L'esempio seguente stampa i nomi di tutti i tipi di entità di un'applicazione e le proprietà associate a ciascuna:

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

Le query di questo tipo sono limitate implicitamente allo spazio dei nomi corrente e supportano il filtro solo per gli intervalli della pseudoproprietà __key__, dove le chiavi indicano entità __kind__ o __property__. I risultati possono essere ordinati in base al valore __key__ crescente (ma non decrescente). Il filtro viene applicato alle coppie tipo-proprietà, ordinate prima per tipo e poi per proprietà: ad esempio, supponiamo di avere un'entità con queste proprietà:

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

La query per restituire i dati della proprietà sarà simile alla seguente:

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 query riportata sopra restituirà quanto segue:

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

Tieni presente che i risultati non includono la proprietà name di tipo Employee e la proprietà title di tipo Manager, né le proprietà di tipi Account e Product, perché non rientrano nell'intervallo specificato per la query.

Le query sulle proprietà supportano anche i filtri degli antenati su una chiave __kind__ o __property__ per limitare i risultati della query a un singolo tipo o proprietà. Puoi utilizzarlo, ad esempio, per recuperare le proprietà associate a un determinato tipo di entità, come nell'esempio seguente:

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

Query sulle proprietà: non solo chiavi (rappresentazione della proprietà)

Le query sulle proprietà non solo con chiavi, chiamate query di rappresentazione della proprietà, restituisce informazioni aggiuntive sulle rappresentazioni utilizzate da ogni coppia di tipo-proprietà. Le proprietà non indicizzate non sono incluse. L'entità restituita per la proprietà P di tipo K ha la stessa chiave di una corrispondente query solo per le chiavi, oltre a una proprietà property_representation aggiuntiva che restituisce le rappresentazioni della proprietà. Il valore di questa proprietà è un'istanza della classe java.util.Collection<String> contenente una stringa per ogni rappresentazione della proprietà P trovata in qualsiasi entità di tipo K.

Tieni presente che le rappresentazioni non sono uguali ai classi di proprietà; più classi di proprietà possono essere associate alla stessa rappresentazione. Ad esempio, java.lang.String e com.google.appengine.api.datastore.PhoneNumber utilizzano entrambi la rappresentazione STRING.

La seguente tabella mappa le classi di proprietà alle relative rappresentazioni:

Classe di proprietà Rappresentazione
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> Rappresentazione di T

L'esempio seguente trova tutte le rappresentazioni di una proprietà specificata per un determinato tipo di entità:

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