Consultas do Datastore

Uma consulta do Datastore obtém entidades do Cloud Datastore que cumprem um conjunto especificado de condições.

Uma consulta típica inclui o seguinte:

Quando executada, uma consulta obtém todas as entidades do tipo indicado que satisfazem todos os filtros indicados, ordenados pela ordem especificada. As consultas são executadas como só de leitura.

Esta página descreve a estrutura e os tipos de consultas usadas no App Engine para obter dados do Cloud Datastore.

Filtros

Os filtros de uma consulta definem restrições nas propriedades, nas chaves e nos ancestrais das entidades a serem obtidas.

Filtros de propriedades

Um filtro de propriedade especifica

  • Um nome da propriedade
  • Um operador de comparação
  • Um valor da propriedade
Por exemplo:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

O valor da propriedade tem de ser fornecido pela aplicação. Não pode referir-se nem ser calculado em função de outras propriedades. Uma entidade satisfaz o filtro se tiver uma propriedade do nome indicado cujo valor seja comparado com o valor especificado no filtro da forma descrita pelo operador de comparação.

O operador de comparação pode ser qualquer um dos seguintes (definidos como constantes enumeradas na classe aninhada Query.FilterOperator):

Operador Significado
EQUAL Igual a
LESS_THAN Inferior a
LESS_THAN_OR_EQUAL Inferior ou igual a
GREATER_THAN Superior a
GREATER_THAN_OR_EQUAL Superior ou igual a
NOT_EQUAL Diferente de
IN Membro de (igual a qualquer um dos valores numa lista especificada)

O operador NOT_EQUAL executa, na verdade, duas consultas: uma em que todos os outros filtros permanecem inalterados e o filtro NOT_EQUAL é substituído por um filtro LESS_THAN e outra em que é substituído por um filtro GREATER_THAN. Em seguida, os resultados são unidos por ordem. Uma consulta não pode ter mais do que um NOT_EQUAL filtro, e uma consulta que tenha um não pode ter outros filtros de desigualdade.

O operador IN também executa várias consultas: uma para cada item na lista especificada, com todos os outros filtros inalterados e o filtro IN substituído por um filtro EQUAL. Os resultados são unidos pela ordem dos itens na lista. Se uma consulta tiver mais do que um filtro IN, é realizada como várias consultas, uma para cada combinação possível de valores nas listas IN.

Uma única consulta que contenha os operadores NOT_EQUAL ou IN está limitada a um máximo de 30 subconsultas.

Para mais informações sobre como as consultas NOT_EQUAL e IN são traduzidas em várias consultas numa framework JDO/JPA, consulte o artigo Consultas com filtros != e IN.

Filtros principais

Para filtrar o valor da chave de uma entidade, use a propriedade especial Entity.KEY_RESERVED_PROPERTY:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

As ordenações ascendentes em Entity.KEY_RESERVED_PROPERTY também são suportadas.

Quando faz a comparação para verificar a desigualdade, as chaves são ordenadas pelos seguintes critérios, por ordem:

  1. Caminho do antepassado
  2. Tipo de entidade
  3. Identificador (nome da chave ou ID numérico)

Os elementos do caminho de antepassados são comparados de forma semelhante: por tipo (string) e, em seguida, pelo nome da chave ou ID numérico. Os tipos e os nomes das chaves são strings e são ordenados por valor de byte; os IDs numéricos são números inteiros e são ordenados numericamente. Se as entidades com o mesmo elemento principal e tipo usarem uma combinação de strings de nomes de chaves e IDs numéricos, as entidades com IDs numéricos precedem as entidades com nomes de chaves.

As consultas em chaves usam índices tal como as consultas em propriedades e requerem índices personalizados nos mesmos casos, com algumas exceções: os filtros de desigualdade ou uma ordem de ordenação ascendente na chave não requerem um índice personalizado, mas uma ordem de ordenação descendente na chave requer. Tal como acontece com todas as consultas, o servidor Web de desenvolvimento cria entradas adequadas no ficheiro de configuração do índice quando é testada uma consulta que precisa de um índice personalizado.

Filtros de antecessores

Pode filtrar as suas consultas do Datastore para um ancestral especificado, para que os resultados devolvidos incluam apenas entidades descendentes desse ancestral:

Query q = new Query("Person").setAncestor(ancestorKey);

Tipos de consultas especiais

Alguns tipos específicos de consulta merecem uma menção especial:

Consultas sem tipo

Uma consulta sem tipo e sem filtro de antepassados obtém todas as entidades de uma aplicação do Datastore. Isto inclui entidades criadas e geridas por outras funcionalidades do App Engine, como entidades de estatísticas e entidades de metadados do Blobstore (se existirem). Essas consultas sem tipo não podem incluir filtros nem ordens de ordenação em valores de propriedades. No entanto, podem filtrar por chaves de entidades especificando Entity.KEY_RESERVED_PROPERTY como o nome da propriedade:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

Consultas de antepassados

Uma consulta com um filtro de antepassados limita os respetivos resultados à entidade especificada e aos respetivos descendentes:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

Consultas predecessoras sem tipo

Uma consulta sem tipo que inclua um filtro de antepassados vai obter o antepassado especificado e todos os respetivos descendentes, independentemente do tipo. Este tipo de consulta não requer índices personalizados. Tal como todas as consultas sem tipo, não pode incluir filtros nem ordens de ordenação nos valores das propriedades, mas pode filtrar pela chave da entidade:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

O exemplo seguinte ilustra como obter todas as entidades descendentes de um determinado antepassado:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

Consultas apenas com chaves

Uma consulta apenas com chaves devolve apenas as chaves das entidades de resultados, em vez das próprias entidades, com uma latência e um custo inferiores aos da obtenção de entidades inteiras:

Query q = new Query("Person").setKeysOnly();

Geralmente, é mais económico fazer primeiro uma consulta apenas de chaves e, em seguida, obter um subconjunto de entidades dos resultados, em vez de executar uma consulta geral que pode obter mais entidades do que as que realmente precisa.

Consultas de projeção

Por vezes, tudo o que precisa dos resultados de uma consulta são os valores de algumas propriedades específicas. Nestes casos, pode usar uma consulta de projeção para obter apenas as propriedades nas quais tem realmente interesse, com uma latência e um custo inferiores aos da obtenção da entidade completa. Consulte a página Consultas de projeção para ver detalhes.

Ordene encomendas

Uma ordenação de uma consulta especifica

  • Um nome da propriedade
  • Uma direção de ordenação (ascendente ou descendente)

Por exemplo:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

Se uma consulta incluir várias ordens de ordenação, estas são aplicadas na sequência especificada. O exemplo seguinte ordena primeiro por apelido em ordem ascendente e, em seguida, por altura em ordem descendente:

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

Se não forem especificadas ordens de ordenação, os resultados são devolvidos na ordem em que são obtidos a partir do Datastore.

Nota: devido à forma como o Datastore executa as consultas, se uma consulta especificar filtros de desigualdade numa propriedade e ordens de ordenação noutras propriedades, a propriedade usada nos filtros de desigualdade tem de ser ordenada antes das outras propriedades.

Índices

Todas as consultas do Datastore calculam os respetivos resultados através de um ou mais índices, que contêm chaves de entidades numa sequência especificada pelas propriedades do índice e, opcionalmente, os antepassados da entidade. Os índices são atualizados de forma incremental para refletir quaisquer alterações que a aplicação faça às respetivas entidades, de modo que os resultados corretos de todas as consultas estejam disponíveis sem necessidade de cálculos adicionais.

O App Engine predefine um índice simples em cada propriedade de uma entidade. Uma aplicação do App Engine pode definir mais índices personalizados num ficheiro de configuração de índice denominado datastore-indexes.xml, que é gerado no diretório /war/WEB-INF/appengine-generated da sua aplicação . O servidor de programação adiciona automaticamente sugestões a este ficheiro à medida que encontra consultas que não podem ser executadas com os índices existentes. Pode ajustar os índices manualmente editando o ficheiro antes de carregar a aplicação.

Exemplo de interface de consulta

A API Java Datastore de baixo nível fornece a classe Query para criar consultas e a interface PreparedQuery para obter entidades do Datastore:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

Repare na utilização de FilterPredicate e CompositeFilter para criar filtros. Se estiver a definir apenas um filtro numa consulta, pode usar apenas FilterPredicate:

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

No entanto, se quiser definir mais do que um filtro numa consulta, tem de usar CompositeFilter, que requer, pelo menos, dois filtros. O exemplo acima usa o auxiliar de atalhos CompositeFilterOperator.and; o exemplo seguinte mostra uma forma de construir um filtro OR composto:

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

O que se segue?