Skip to main content

Querying

The Query class provides a powerful, fluent API for building and executing database queries. Inspired by Apple's Cocoa Predicates and LINQ, it offers a clean, readable syntax for expressing complex query logic.

Query Construction

Basic Factory Methods

// Query a specific type
Query<Article> query = Query.from(Article.class);

// Query all types
Query<Object> allQuery = Query.fromAll();

// Query by ObjectType
ObjectType articleType = ObjectType.getInstance(Article.class);
Query<Article> typeQuery = Query.fromType(articleType);

// Query by type group (multiple related types)
Query<Content> contentQuery = Query.fromGroup("content");

Using a Specific Database

Database searchDb = Database.Static.getInstance("search");

Query<Article> query = Query.from(Article.class)
.using(searchDb);

Predicates and Filtering

where - Initial Predicate

Set the initial query predicate:

// Simple equality
Query.from(Article.class)
.where("title = ?", "Getting Started");

// Comparison
Query.from(Article.class)
.where("publishDate > ?", cutoffDate);

// Multiple parameters
Query.from(Article.class)
.where("title = ? and author = ?", "Getting Started", author);

and - Add AND Conditions

Chain additional conditions with AND:

Query.from(Article.class)
.where("publishDate > ?", cutoffDate)
.and("author/name = ?", "John Doe")
.and("tags = ?", "java");

or - Add OR Conditions

Chain conditions with OR:

Query.from(Article.class)
.where("author/name = ?", "John Doe")
.or("author/name = ?", "Jane Smith");

Complex combinations:

// (publishDate > cutoff) OR (featured = true AND author = john)
Query.from(Article.class)
.where("publishDate > ?", cutoffDate)
.or("featured = ? and author = ?", true, john);

not - Negate Conditions

// Articles NOT by John Doe
Query.from(Article.class)
.where("author/name != ?", "John Doe");

// Or using not()
Query.from(Article.class)
.not("author/name = ?", "John Doe");

having - Filter Grouped Results

Use having for filtering grouped queries:

// Find authors with more than 5 articles
Query.from(Article.class)
.groupBy("author")
.having("_count > ?", 5);

Predicate Operators

Equality Operators

// Equals
.where("status = ?", "published")

// Not equals
.where("status != ?", "draft")

// Null/missing check
.where("deletedDate = missing")
.where("deletedDate != missing")

Comparison Operators

// Greater than
.where("publishDate > ?", date)

// Greater than or equal
.where("publishDate >= ?", date)

// Less than
.where("publishDate < ?", date)

// Less than or equal
.where("publishDate <= ?", date)

String Operators

// Starts with
.where("title ^= ?", "Getting")

// Contains
.where("content contains ?", "database")

Collection Operators

// Contains any of the values
.where("tags = ?", Arrays.asList("java", "database"))

// Contains all values
.where("tags = ?", tag1)
.and("tags = ?", tag2) // Both tags must be present

Range Queries

// Between dates
.where("publishDate > ? and publishDate < ?", startDate, endDate)
// Search across all indexed fields
.where("_any matches ?", "search terms")

// Search specific field
.where("content matches ?", "search terms")

Special Field Keys

_id - Object ID

// Single ID
.where("_id = ?", articleId)

// Multiple IDs
.where("_id = ?", Arrays.asList(id1, id2, id3))

_type - Object Type

// Specific type
.where("_type = ?", Article.class)

// Type ID
.where("_type = ?", ObjectType.getInstance(Article.class).getId())

_any - Search All Fields

// Full-text search across all indexed fields
.where("_any matches ?", "search query")

_label - Display Label

// Search by label (calls getLabel())
.where("_label matches ?", "Article")

Path-Based Field Access

Query nested fields using path notation:

// Author's name
.where("author/name = ?", "John Doe")

// Nested objects
.where("author/company/name = ?", "Acme Corp")

// Collection items
.where("comments/author/name = ?", "Jane Smith")

// Map values
.where("metadata/category = ?", "technology")

Sorting

sortAscending

Query.from(Article.class)
.sortAscending("title");

// Multiple fields
Query.from(Article.class)
.sortAscending("author/name")
.sortAscending("publishDate");

sortDescending

Query.from(Article.class)
.sortDescending("publishDate");

Sort by Relevance

Sort by relevance to a predicate (requires search-capable database):

Query.from(Article.class)
.where("_any matches ?", "java database")
.sortRelevant(100, "_any matches ?", "java database");

Sort by Newest/Oldest

// Newest first
Query.from(Article.class)
.sortNewest(100, "publishDate");

// Oldest first
Query.from(Article.class)
.sortOldest(100, "publishDate");

Multiple Sort Criteria

Query.from(Article.class)
.sortDescending("featured") // Featured articles first
.sortDescending("publishDate") // Then by date
.sortAscending("title"); // Then by title

Query Execution

selectAll

Execute query and return all results:

List<Article> articles = Query.from(Article.class)
.where("publishDate > ?", cutoffDate)
.selectAll();

select (Pagination)

Execute with offset and limit:

// First page (items 0-9)
List<Article> page1 = Query.from(Article.class)
.sortDescending("publishDate")
.select(0, 10);

// Second page (items 10-19)
List<Article> page2 = Query.from(Article.class)
.sortDescending("publishDate")
.select(10, 10);

// Calculate offset from page number
int page = 3;
int pageSize = 10;
int offset = (page - 1) * pageSize;
List<Article> pageN = query.select(offset, pageSize);

first

Get the first result (or null):

Article article = Query.from(Article.class)
.where("slug = ?", "getting-started")
.first();

if (article == null) {
// Not found
}

findFirst (Optional)

Get the first result as an Optional:

Optional<Article> optional = Query.from(Article.class)
.where("slug = ?", "getting-started")
.findFirst();

optional.ifPresent(article -> {
System.out.println("Found: " + article.getTitle());
});

// Or with default
Article article = Query.from(Article.class)
.where("slug = ?", "getting-started")
.findFirst()
.orElse(defaultArticle);

count

Count matching objects:

long total = Query.from(Article.class).count();

long published = Query.from(Article.class)
.where("publishDate != missing")
.count();

iterable

Get a streaming iterable for large result sets:

Query<Article> query = Query.from(Article.class)
.where("publishDate > ?", cutoffDate);

for (Article article : query.iterable(100)) { // Fetch size 100
// Process each article
// Memory efficient for large datasets
}

deleteAll

Delete all matching objects:

// Delete old draft articles
long deletedCount = Query.from(Article.class)
.where("status = ? and createdDate < ?", "draft", cutoffDate)
.deleteAll();

System.out.println("Deleted " + deletedCount + " drafts");

Grouping and Aggregation

groupBy

Group results by one or more fields:

// Group by author
List<Grouping<Article>> byAuthor = Query.from(Article.class)
.groupBy("author")
.selectAll(); // Returns List<Grouping<Article>>

for (Grouping<Article> group : byAuthor) {
Author author = (Author) group.getKeys().get(0);
long count = group.getCount();
System.out.println(author.getName() + ": " + count + " articles");
}
// Group by multiple fields
List<Grouping<Article>> groups = Query.from(Article.class)
.groupBy("author", "publishYear")
.selectAll();

for (Grouping<Article> group : groups) {
Author author = (Author) group.getKeys().get(0);
Integer year = (Integer) group.getKeys().get(1);
long count = group.getCount();
System.out.println(author.getName() + " in " + year + ": " + count);
}

Filtering Grouped Results

// Authors with more than 5 articles
List<Grouping<Article>> prolificAuthors = Query.from(Article.class)
.groupBy("author")
.having("_count > ?", 5)
.selectAll();

Aggregation Functions

// Get count for each group
for (Grouping<Article> group : groups) {
long count = group.getCount();
// Use count...
}

// Access first item in group
for (Grouping<Article> group : groups) {
Article firstArticle = group.getFirst();
// Use firstArticle...
}

// Get all items in group
for (Grouping<Article> group : groups) {
List<Article> articles = group.getItems();
// Process all articles in this group...
}

Query Modifiers

timeout

Set query timeout in seconds:

Query.from(Article.class)
.timeout(30.0) // 30 second timeout
.selectAll();

noCache

Bypass query result cache:

Query.from(Article.class)
.noCache()
.selectAll();

master

Execute on master/primary database (force read from write source):

// Ensure we read from master, not replica
Query.from(Article.class)
.master()
.where("_id = ?", articleId)
.first();

Useful when:

  • You just wrote data and need to read it immediately
  • Avoiding replication lag
  • Critical reads that must be up-to-date

resolveToReferenceOnly

Load only reference information, not full objects:

List<Article> refs = Query.from(Article.class)
.resolveToReferenceOnly()
.selectAll();

// refs contains lightweight reference objects
// Good for getting IDs, types, but not full data

resolveInvisible

Include records marked as invisible:

Query.from(Article.class)
.resolveInvisible()
.selectAll();

option

Set database-specific options:

Query.from(Article.class)
.option("hint", "USE INDEX (idx_author)") // SQL hint
.option("readPreference", "secondary") // MongoDB option
.selectAll();

comment

Add an informational comment to the query:

Query.from(Article.class)
.comment("Dashboard recent articles widget")
.selectAll();

Useful for:

  • Debugging slow queries
  • Monitoring and logging
  • Tracking query origins in database logs

Subqueries

Use queries as predicate values for powerful filtering:

// Find articles by authors in California
Query<Article> articles = Query.from(Article.class)
.where("author = ?",
Query.from(Author.class)
.where("state = ?", "CA")
)
.selectAll();
// Find articles NOT by specific authors
Query<Article> articles = Query.from(Article.class)
.where("author != ?",
Query.from(Author.class)
.where("blacklisted = ?", true)
)
.selectAll();
// Complex subquery
Query<Article> articles = Query.from(Article.class)
.where("author = ?",
Query.from(Author.class)
.where("company = ?",
Query.from(Company.class)
.where("name = ?", "Acme Corp")
)
)
.selectAll();

Practical Examples

Search with Filters

public List<Article> searchArticles(String query, Author author,
Date startDate, Date endDate,
int page, int pageSize) {
Query<Article> q = Query.from(Article.class);

// Full-text search
if (query != null && !query.isEmpty()) {
q.where("_any matches ?", query);
}

// Filter by author
if (author != null) {
q.and("author = ?", author);
}

// Date range
if (startDate != null) {
q.and("publishDate >= ?", startDate);
}
if (endDate != null) {
q.and("publishDate <= ?", endDate);
}

// Pagination
int offset = (page - 1) * pageSize;
return q.sortDescending("publishDate")
.select(offset, pageSize);
}

Dynamic Query Building

public List<Article> findArticles(Map<String, Object> filters) {
Query<Article> query = Query.from(Article.class);
boolean first = true;

for (Map.Entry<String, Object> entry : filters.entrySet()) {
String field = entry.getKey();
Object value = entry.getValue();

query.and(field + " = ?", value);
}

return query.selectAll();
}
public List<Article> findRelatedArticles(Article article, int limit) {
// Find articles with overlapping tags, same author, or similar title
return Query.from(Article.class)
.where("_id != ?", article.getId()) // Exclude current article
.and("tags = ?", article.getTags()) // Matching tags
.or("author = ?", article.getAuthor()) // Same author
.sortDescending("publishDate")
.select(0, limit);
}
public class SearchResult {
private List<Article> articles;
private Map<String, Long> authorCounts;
private Map<String, Long> tagCounts;

public static SearchResult search(String searchQuery) {
Query<Article> baseQuery = Query.from(Article.class)
.where("_any matches ?", searchQuery);

// Get articles
List<Article> articles = baseQuery
.sortRelevant(100, "_any matches ?", searchQuery)
.select(0, 20);

// Get author facets
Map<String, Long> authorCounts = new HashMap<>();
List<Grouping<Article>> authorGroups = baseQuery
.groupBy("author")
.selectAll();
for (Grouping<Article> group : authorGroups) {
Author author = (Author) group.getKeys().get(0);
authorCounts.put(author.getName(), group.getCount());
}

// Get tag facets
Map<String, Long> tagCounts = new HashMap<>();
List<Grouping<Article>> tagGroups = baseQuery
.groupBy("tags")
.selectAll();
for (Grouping<Article> group : tagGroups) {
String tag = (String) group.getKeys().get(0);
tagCounts.put(tag, group.getCount());
}

SearchResult result = new SearchResult();
result.articles = articles;
result.authorCounts = authorCounts;
result.tagCounts = tagCounts;
return result;
}
}

Advanced Filtering

public List<Article> findFeaturedOrRecent(Date cutoffDate) {
// Articles that are either featured OR published recently
return Query.from(Article.class)
.where("featured = ?", true)
.or("publishDate > ?", cutoffDate)
.sortDescending("publishDate")
.selectAll();
}

public List<Article> findByMultipleTags(List<String> tags) {
// Articles that have ALL specified tags
Query<Article> query = Query.from(Article.class);
for (String tag : tags) {
query.and("tags = ?", tag);
}
return query.selectAll();
}

public List<Article> findByAnyTag(List<String> tags) {
// Articles that have ANY of the specified tags
return Query.from(Article.class)
.where("tags = ?", tags)
.selectAll();
}

Count by Category

public Map<String, Long> countByCategory() {
Map<String, Long> counts = new LinkedHashMap<>();

List<Grouping<Article>> groups = Query.from(Article.class)
.groupBy("category")
.sortDescending("_count")
.selectAll();

for (Grouping<Article> group : groups) {
String category = (String) group.getKeys().get(0);
counts.put(category, group.getCount());
}

return counts;
}

Find Duplicates

public List<List<Article>> findDuplicateTitles() {
List<List<Article>> duplicates = new ArrayList<>();

// Group by title
List<Grouping<Article>> groups = Query.from(Article.class)
.groupBy("title")
.having("_count > ?", 1) // Only groups with more than 1
.selectAll();

for (Grouping<Article> group : groups) {
// Get all articles with this title
String title = (String) group.getKeys().get(0);
List<Article> dupes = Query.from(Article.class)
.where("title = ?", title)
.selectAll();
duplicates.add(dupes);
}

return duplicates;
}

Performance Tips

Index Your Query Fields

// Ensure queried fields are indexed
@Indexed
private String title;

@Indexed
private Date publishDate;

@Indexed
private Author author;

Use Pagination

// Good - loads 10 items
query.select(0, 10);

// Bad - loads everything
query.selectAll();

Use count() Instead of Loading All

// Good
long count = query.count();

// Bad
long count = query.selectAll().size();

Use Specific Predicates

// Good - uses index efficiently
.where("publishDate > ?", cutoffDate)

// Less efficient - may scan more rows
.where("publishDate != missing")

Common Pitfalls

Forgetting to Index

// This will fail if 'title' is not indexed
Query.from(Article.class)
.where("title = ?", "Getting Started")
.selectAll();
// ERROR: Field 'title' is not indexed

Loading Too Much Data

// Avoid loading thousands of objects at once
List<Article> all = Query.from(Article.class).selectAll();
// Use pagination or iterable() instead

Inefficient Filtering

// Bad - loads everything then filters in memory
List<Article> all = Query.from(Article.class).selectAll();
List<Article> filtered = all.stream()
.filter(a -> a.getPublishDate().after(cutoffDate))
.collect(Collectors.toList());

// Good - filters at database level
List<Article> filtered = Query.from(Article.class)
.where("publishDate > ?", cutoffDate)
.selectAll();

Not Handling Null Results

// Bad - can throw NullPointerException
Article article = Query.from(Article.class)
.where("_id = ?", id)
.first();
String title = article.getTitle(); // NPE if article is null

// Good - check for null
Article article = Query.from(Article.class)
.where("_id = ?", id)
.first();
if (article != null) {
String title = article.getTitle();
}

// Or use Optional
Query.from(Article.class)
.where("_id = ?", id)
.findFirst()
.map(Article::getTitle)
.orElse("Untitled");