A fluent query builder for constructing complex database queries in the Speedy API. This class provides a type-safe and intuitive way to build queries with conditions, ordering, pagination, field selection, and entity expansion.
SpeedyQuery offers:
import static com.github.silent.samurai.speedy.api.client.SpeedyQuery.*;
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.orderByAsc("name")
.pageSize(20)
.build();
import static com.github.silent.samurai.speedy.api.client.SpeedyQuery.*;
SpeedyQuery query = SpeedyQuery.from("users")
.where(
and(
condition("age", gte(18)),
condition("active", eq(true)),
or(
condition("role", eq("admin")),
condition("role", eq("moderator"))
)
)
)
.select("id", "name", "email", "role")
.expand("profile", "permissions")
.orderByDesc("createdAt")
.pageNo(1)
.pageSize(50)
.build();
The generated query follows this JSON structure:
{
"$from": "entity_name",
"$where": { "conditions" },
"$select": ["field1", "field2"],
"$expand": ["relation1", "relation2"],
"$orderBy": { "field": "ASC|DESC" },
"$page": { "$index": 0, "$size": 10 }
}
// Start with entity name (recommended)
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.build();
// Or set entity later
SpeedyQuery query = SpeedyQuery.from()
.fromEntity("users")
.where(condition("active", eq(true)))
.build();
SpeedyQuery query = SpeedyQuery.from("users")
.select("id", "name", "email", "createdAt")
.where(condition("active", eq(true)))
.build();
SpeedyQuery supports both simple entity expansions and multi-level nested expansions using dot notation.
SpeedyQuery query = SpeedyQuery.from("users")
.expand("profile")
.expand("permissions")
.expand("department")
.where(condition("active", eq(true)))
.build();
Use dot notation to expand nested relationships:
// Expand product and its category
SpeedyQuery query = SpeedyQuery.from("inventory")
.expand("Product")
.expand("Product.Category")
.where(condition("quantity", gt(0)))
.build();
// Deep nested expansion
SpeedyQuery query = SpeedyQuery.from("inventory")
.expand("Product")
.expand("Product.Category")
.expand("Product.Category.Supplier")
.expand("Product.Category.Supplier.Address")
.where(condition("quantity", gt(0)))
.build();
// Multiple expansion paths at different levels
SpeedyQuery query = SpeedyQuery.from("inventory")
.expand("Product")
.expand("Product.Category")
.expand("Procurement")
.expand("Procurement.Product")
.expand("Procurement.Product.Category")
.where(condition("quantity", gt(0)))
.build();
// Expansion with field selection
SpeedyQuery query = SpeedyQuery.from("inventory")
.select("id", "quantity", "location")
.expand("Product")
.expand("Product.Category")
.expand("Product.Category.Supplier")
.where(condition("quantity", gt(0)))
.build();
.) to separate entity levels in the expansion pathBadRequestException| Use Case | Query | Description |
|---|---|---|
| Product with Category | .expand("Product").expand("Product.Category") |
Include product and its category |
| Deep Supplier Chain | .expand("Product.Category.Supplier.Address") |
Include complete supplier chain |
| Multiple Paths | .expand("Product.Category").expand("Procurement.Product") |
Different expansion paths |
| Selective Expansion | .expand("Product").expand("Product.Category") |
Only expand specific paths |
SpeedyQuery query = SpeedyQuery.from("users")
.orderByAsc("name")
.orderByAsc("createdAt")
.orderByDesc("lastLogin")
.where(condition("active", eq(true)))
.build();
SpeedyQuery query = SpeedyQuery.from("users")
.pageNo(2) // Get the third page (0-based indexing)
.pageSize(50) // Get 50 records per page
.where(condition("active", eq(true)))
.build();
import static com.github.silent.samurai.speedy.api.client.SpeedyQuery.*;
// Equal to
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("status", eq("active")))
.build();
// Not equal to
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("status", ne("inactive")))
.build();
// Greater than
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("age", gt(18)))
.build();
// Less than
SpeedyQuery query = SpeedyQuery.from("products")
.where(condition("price", lt(100)))
.build();
// Greater than or equal
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("score", gte(80)))
.build();
// Less than or equal
SpeedyQuery query = SpeedyQuery.from("products")
.where(condition("quantity", lte(10)))
.build();
// In array of values
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("role", in("admin", "moderator", "user")))
.build();
// Not in array
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("status", nin("deleted", "archived")))
.build();
// Pattern matching (wildcard)
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("name", matches("*john*")))
.build();
Wildcard Syntax:
* = 0 or more characters/spaces? = exactly 1 character/space"*john*" matches “john”, “johnny”, “john doe”, “my john”, etc."john*" matches “john”, “johnny”, “john doe”, etc."*john" matches “john”, “my john”, “the john”, etc."j?hn" matches “john”, “jahn”, etc.SpeedyQuery supports field-to-field comparisons using the field() method. This allows you to compare values between
different fields in the same entity.
// Compare two fields for equality
SpeedyQuery query = SpeedyQuery.from("products")
.where(condition("salePrice", eq("$regularPrice")))
.build();
// Find products where sale price is less than regular price
SpeedyQuery query = SpeedyQuery.from("products")
.where(condition("salePrice", lt("$regularPrice")))
.build();
// Find orders where start date is before end date
SpeedyQuery query = SpeedyQuery.from("orders")
.where(condition("startDate", lte("$endDate")))
.build();
// Compare user tracking fields
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("createdBy", eq("$updatedBy")))
.build();
// Inventory check with field reference
SpeedyQuery query = SpeedyQuery.from("products")
.where(
and(
condition("currentStock", gte(field("minimumStock"))),
condition("salePrice", lt(field("regularPrice")))
)
)
.build();
"$fieldName" to reference another fieldNotFoundException| Use Case | Query | Description |
|---|---|---|
| Price Comparison | condition("salePrice", lt("$regularPrice")) |
Find products on sale |
| Date Range | condition("startDate", lte("$endDate")) |
Valid date ranges |
| User Tracking | condition("createdBy", eq("$updatedBy")) |
Self-updated records |
| Inventory Check | condition("currentStock", gte("$minimumStock")) |
Sufficient inventory |
SpeedyQuery query = SpeedyQuery.from("users")
.where(
and(
condition("active", eq(true)),
condition("age", gte(18)),
condition("verified", eq(true))
)
)
.build();
SpeedyQuery query = SpeedyQuery.from("users")
.where(
or(
condition("role", eq("admin")),
condition("role", eq("moderator"))
)
)
.build();
SpeedyQuery query = SpeedyQuery.from("users")
.where(
and(
condition("active", eq(true)),
condition("age", gte(18)),
or(
condition("role", eq("admin")),
condition("role", eq("moderator")),
and(
condition("verified", eq(true)),
condition("premium", eq(true))
)
)
)
)
.build();
and()/or() can also be nested inside a single condition(...), combining several
operators on the same field without repeating it as a separate top-level condition. This is
the concise way to express patterns like “field matches a value OR is null”:
// Find products whose category is unset (nullable FK) or matches a specific category
SpeedyQuery query = SpeedyQuery.from("products")
.where(
condition("categoryId", or(eq(null), eq("electronics")))
)
.build();
This generates:
{
"$where": {
"categoryId": {
"$or": [
{ "$eq": null },
{ "$eq": "electronics" }
]
}
}
}
It composes with ordinary top-level conditions and with $and the same way:
SpeedyQuery query = SpeedyQuery.from("products")
.where(
condition("categoryId", or(eq(null), eq("electronics"))),
condition("active", eq(true))
)
.build();
As with top-level $and/$or, the logical key must be the only key inside the field’s
condition object — combine additional operators as further array entries instead of mixing
a logical key with a plain operator key.
SpeedyQuery query = SpeedyQuery.from("users")
.where(
and(
condition("active", eq(true)),
or(
condition("name", matches("*john*")),
condition("email", matches("*john*"))
),
condition("age", gte(18))
)
)
.select("id", "name", "email", "avatar")
.orderByAsc("name")
.pageSize(20)
.build();
SpeedyQuery query = SpeedyQuery.from("products")
.where(
and(
condition("category", in("electronics", "computers")),
condition("price", gte(500)),
condition("inStock", eq(true)),
or(
condition("brand", eq("Apple")),
condition("brand", eq("Samsung"))
)
)
)
.select("id", "name", "price", "brand", "rating")
.expand("reviews", "images")
.orderByDesc("rating")
.orderByAsc("price")
.pageSize(50)
.build();
SpeedyQuery query = SpeedyQuery.from("orders")
.where(
and(
condition("userId", eq(userId)),
condition("status", nin("cancelled", "refunded")),
condition("createdAt", gte(startDate)),
condition("createdAt", lte(endDate))
)
)
.select("id", "total", "status", "createdAt")
.expand("items", "shipping")
.orderByDesc("createdAt")
.pageSize(100)
.build();
SpeedyQuery query = SpeedyQuery.from("products")
.where(
and(
// Find products where sale price is less than regular price
condition("salePrice", lt(field("regularPrice"))),
// Ensure current stock is above minimum threshold
condition("currentStock", gte(field("minimumStock"))),
// Only active products
condition("active", eq(true)),
// In specific categories
condition("category", in("electronics", "computers"))
)
)
.select("id", "name", "salePrice", "regularPrice", "currentStock", "minimumStock")
.orderByAsc("currentStock") // Show low stock first
.pageSize(50)
.build();
SpeedyQuery query = SpeedyQuery.from("events")
.where(
and(
// Ensure start date is before end date
condition("startDate", lte(field("endDate"))),
// Events in the future
condition("startDate", gte(LocalDate.now().toString())),
// Active events only
condition("status", eq("active"))
)
)
.select("id", "title", "startDate", "endDate", "location")
.orderByAsc("startDate")
.pageSize(20)
.build();
// Query by date range
SpeedyQuery query = SpeedyQuery.from("ValueTestEntity")
.where(condition("localDate", gt(LocalDate.now().toString())))
.build();
// Query by time
SpeedyQuery query = SpeedyQuery.from("ValueTestEntity")
.where(condition("localTime", gt(LocalTime.of(11, 0).toString())))
.build();
// Query by instant
SpeedyQuery query = SpeedyQuery.from("ValueTestEntity")
.where(condition("instantTime", lt(Instant.now().toString())))
.build();
// Query by double value
SpeedyQuery query = SpeedyQuery.from("ValueTestEntity")
.where(condition("doubleValue", eq(1.5430434)))
.build();
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.select("id", "name")
.prettyPrint() // Logs the query structure
.build();
This will log the generated JSON query to help with debugging.
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.build();
JsonNode queryJson = query.build();
System.out.println(queryJson.toPrettyString());
$between — Inclusive Range FilteringThe $between operator filters records where a field falls within an inclusive range (equivalent to $gte AND $lte).
POST /speedy/v1/Inventory/$query
{
"$from": "Inventory",
"$where": {
"cost": { "$between": [10, 50] }
}
}
Works with numeric, date, datetime, and string (lexicographic) fields:
{
"$from": "Category",
"$where": {
"name": { "$between": ["A", "M"] }
}
}
SpeedyQuery query = SpeedyQuery.from("Inventory")
.where(condition("cost", between(10, 50)))
.build();
Error conditions:
400 BadRequest — “$between only accepts an array”400 BadRequest — “$between requires exactly 2 values”$isnull — IS NULL CheckTwo forms — shorthand (recommended) and explicit:
// Shorthand
{ "modifiedAt": "$isnull" }
// Explicit
{ "modifiedAt": { "$isnull": true } }
Both generate modifiedAt IS NULL. The $isnotnull operator works identically for IS NOT NULL.
SpeedyQuery query = SpeedyQuery.from("Procurement")
.where(condition("modifiedAt", isnull()))
.build();
Error conditions:
400 BadRequest — “$isnull only accepts a boolean value”false value → 400 BadRequest — “$isnull requires true. Use $isnotnull for IS NOT NULL”$isnotnull — IS NOT NULL Check// Shorthand
{ "modifiedAt": "$isnotnull" }
// Explicit
{ "modifiedAt": { "$isnotnull": true } }
Error conditions:
false value → 400 BadRequest — “$isnotnull requires true. Use $isnull for IS NULL”All three operators compose inside $and/$or:
{
"$from": "Procurement",
"$where": {
"$or": [
{ "createdAt": { "$between": ["2024-01-01", "2024-03-31"] } },
{ "modifiedAt": { "$isnull": true } }
]
}
}
import static com.github.silent.samurai.speedy.api.client.SpeedyQuery.*;
// Much cleaner than SpeedyQuery.from("users")
SpeedyQuery query = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.build();
public SpeedyQuery buildUserQuery(String status, int minAge) {
if (status == null || status.trim().isEmpty()) {
throw new IllegalArgumentException("Status cannot be null or empty");
}
if (minAge < 0) {
throw new IllegalArgumentException("Minimum age cannot be negative");
}
return SpeedyQuery.from("users")
.where(
and(
condition("status", eq(status)),
condition("age", gte(minAge))
)
)
.build();
}
// Good: Select only needed fields
SpeedyQuery query = SpeedyQuery.from("users")
.select("id", "name", "email") // Only get what you need
.where(condition("active", eq(true)))
.build();
// Avoid: Getting all fields
SpeedyQuery badQuery = SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.build(); // Gets everything!
// Good: Reasonable page size
SpeedyQuery query = SpeedyQuery.from("users")
.pageSize(20) // Good for UI pagination
.where(condition("active", eq(true)))
.build();
// Avoid: Too large page sizes
SpeedyQuery badQuery = SpeedyQuery.from("users")
.pageSize(10000) // Could cause performance issues
.where(condition("active", eq(true)))
.build();
public class UserQueries {
public static SpeedyQuery activeUsers() {
return SpeedyQuery.from("users")
.where(condition("active", eq(true)))
.build();
}
public static SpeedyQuery usersByRole(String role) {
return SpeedyQuery.from("users")
.where(condition("role", eq(role)))
.build();
}
public static SpeedyQuery usersWithProfile() {
return SpeedyQuery.from("users")
.expand("profile")
.select("id", "name", "email")
.build();
}
}
// Usage
SpeedyQuery query = UserQueries.activeUsers();
SpeedyQuery adminQuery = UserQueries.usersByRole("admin");
| Operator | Method | Description | Example |
|---|---|---|---|
$eq |
eq(Object) |
Equal to | eq("active") |
$ne |
ne(Object) |
Not equal to | ne("inactive") |
$gt |
gt(Object) |
Greater than | gt(18) |
$lt |
lt(Object) |
Less than | lt(100) |
$gte |
gte(Object) |
Greater than or equal | gte(80) |
$lte |
lte(Object) |
Less than or equal | lte(10) |
$in |
in(Object...) |
In array of values | in("A", "B", "C") |
$nin |
nin(Object...) |
Not in array | nin("deleted", "archived") |
$matches |
matches(Object) |
Pattern matching | matches("*john*") |
$between |
between(low, high) |
Inclusive range | between(10, 50) |
$isnull |
isnull() |
IS NULL check | isnull() |
$isnotnull |
isnotnull() |
IS NOT NULL check | isnotnull() |
| Operator | Method | Description | Example |
|---|---|---|---|
$and |
and(JsonNode...) |
Logical AND | and(cond1, cond2, cond3) |
$or |
or(JsonNode...) |
Logical OR | or(cond1, cond2) |
| Category | Methods | Description |
|---|---|---|
| Builder | from(), from(String entity) |
Create new query instances |
| Source | fromEntity(String entity) |
Set the target entity |
| Conditions | where(JsonNode...) |
Add WHERE conditions |
| Selection | select(String...) |
Choose fields to return |
| Expansion | expand(String...) |
Include related entities |
| Ordering | orderByAsc(String), orderByDesc(String) |
Sort results |
| Pagination | pageNo(int), pageSize(int) |
Control result pagination |
| Execution | build(), prettyPrint() |
Generate final query |