Queries

July 10, 2026 ยท View on GitHub

The fluent query builder pushes conditions down to MongrelDB's native indexes for sub-millisecond lookups - bitmap, learned-range, FM-index full text, HNSW vector similarity, and more. Each condition type maps to one specialized index; conditions are AND-ed together.

Q0 = mongreldb:query(Db, <<"orders">>),
Q1 = mongreldb:query_where(Q0, <<"range_f64">>,
    #{<<"column">> => 3, <<"min">> => 100.0, <<"max">> => 500.0}),
Q2 = mongreldb:query_projection(Q1, [1, 2]),
Q3 = mongreldb:query_limit(Q2, 100),
{ok, Rows, _} = mongreldb:query_execute(Db, Q3).

This guide covers every condition type, projection, limits and truncation, combining conditions, and the friendly aliases the builder translates for you.


The basics

Every query starts with mongreldb:query/2 and ends with query_execute/2:

FunctionPurpose
query_where/3Add a native condition. Multiple calls are AND-ed.
query_projection/2Return only these column ids (undefined means all columns).
query_limit/2Cap the number of rows.
query_build/1Produce the request payload (useful for debugging).
query_execute/2Send and decode. Records the truncated flag.
query_truncated/1Whether the last query_execute/2 hit the limit.

The request body produced by query_build/1 matches the daemon's /kit/query shape:

{
  "table": "orders",
  "conditions": [{"range_f64": {"column_id": 3, "lo": 100.0, "hi": 500.0, "lo_inclusive": true, "hi_inclusive": true}}],
  "projection": [1, 2],
  "limit": 100
}

Condition types

params is a map(). Column references use the numeric column id, never the column name.

pk - exact primary-key match

The fastest lookup. value is the primary-key value.

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"pk">>, #{<<"value">> => 42})).

range - integer range (learned-range index)

Inclusive bounds. Omit lo or hi for an open range.

%% amount in [100, 500]
{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"range">>, #{<<"column">> => 3, <<"min">> => 100, <<"max">> => 500})).

%% Open-ended: amount >= 100
{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"range">>, #{<<"column">> => 3, <<"min">> => 100})).

range_f64 - float range with inclusive/exclusive control

Adds lo_inclusive / hi_inclusive flags (default inclusive).

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"range_f64">>, #{
            <<"column">> => 3,
            <<"min">> => 100.0,
            <<"max">> => 500.0,
            <<"min_inclusive">> => true,
            <<"max_inclusive">> => false  %% (100.0, 500.0]
        })).

bitmap_eq - equality on a bitmap-indexed column

Best for low-cardinality columns (status, category, booleans).

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"bitmap_eq">>, #{<<"column">> => 2, <<"value">> => <<"Alice">>})).

bitmap_in - IN predicate on a bitmap-indexed column

Match any of a set of values.

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"bitmap_in">>, #{<<"column">> => 2, <<"values">> => [<<"Alice">>, <<"Bob">>, <<"Carol">>]})).

is_null / is_not_null - null checks

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"is_null">>, #{<<"column">> => 3})).

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"orders">>),
        <<"is_not_null">>, #{<<"column">> => 3})).

fm_contains - full-text substring search (FM-index)

Substring match within a column. Use pattern (the server key) or the friendly value alias - both translate to pattern on the wire for FTS conditions.

Q0 = mongreldb:query(Db, <<"documents">>),
Q1 = mongreldb:query_where(Q0, <<"fm_contains">>,
    #{<<"column">> => 2, <<"pattern">> => <<"database performance">>}),
Q2 = mongreldb:query_limit(Q1, 10),
{ok, _, _} = mongreldb:query_execute(Db, Q2).

%% Friendly alias: "value" -> "pattern" for fm_contains only.
{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"documents">>),
        <<"fm_contains">>, #{<<"column">> => 2, <<"value">> => <<"database">>})).

fm_contains_all - multiple substrings, all must match

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"documents">>),
        <<"fm_contains_all">>, #{<<"column">> => 2, <<"patterns">> => [<<"database">>, <<"performance">>]})).

ann - dense vector similarity (HNSW)

Approximate nearest-neighbors over a float vector column. k is the result count.

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"embeddings">>),
        <<"ann">>, #{<<"column">> => 2, <<"query">> => [0.1, 0.2, 0.3, 0.4], <<"k">> => 10})).

sparse_match - sparse vector match

For sparse/bag-of-words vectors.

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"docs">>),
        <<"sparse_match">>, #{
            <<"column">> => 2,
            <<"query">> => #{<<"0">> => 1.0, <<"7">> => 0.5, <<"42">> => 2.0},
            <<"k">> => 10
        })).

min_hash_similar - MinHash similarity

Near-duplicate detection via MinHash signatures.

{ok, _, _} = mongreldb:query_execute(Db,
    mongreldb:query_where(mongreldb:query(Db, <<"pages">>),
        <<"min_hash_similar">>, #{<<"column">> => 2, <<"query">> => [12, 99, 421, 7], <<"k">> => 5})).

Projection (column selection)

query_projection/2 restricts the columns in each returned row. Pass undefined (or skip the call) for all columns. Projecting to only the columns you need cuts bandwidth and decode cost.

Q0 = mongreldb:query(Db, <<"orders">>),
Q1 = mongreldb:query_where(Q0, <<"range">>, #{<<"column">> => 3, <<"min">> => 100}),
Q2 = mongreldb:query_projection(Q1, [1, 2]),
{ok, Rows, _} = mongreldb:query_execute(Db, Q2).

Returned rows are map() objects keyed by the column id as a binary string.

Limit and the truncated flag

query_limit/2 caps the result. When the server has more matches than the limit allows, it returns the first n and sets truncated: true. Read it with query_truncated/1 after query_execute/2.

Q0 = mongreldb:query(Db, <<"orders">>),
Q1 = mongreldb:query_where(Q0, <<"range">>, #{<<"column">> => 3, <<"min">> => 0}),
Q2 = mongreldb:query_limit(Q1, 100),
{ok, Rows, Q3} = mongreldb:query_execute(Db, Q2),
case mongreldb:query_truncated(Q3) of
    true -> io:format("warning: result capped at ~p; more rows available~n", [length(Rows)]);
    false -> ok
end.

query_truncated/1 returns false until query_execute/2 has run, so build a fresh query for each independent lookup.

Multiple AND conditions

Chain query_where/3 calls. Every condition must match; the server intersects the index results.

%% Customer is Alice AND amount is between 100 and 500.
Q0 = mongreldb:query(Db, <<"orders">>),
Q1 = mongreldb:query_where(Q0, <<"bitmap_eq">>, #{<<"column">> => 2, <<"value">> => <<"Alice">>}),
Q2 = mongreldb:query_where(Q1, <<"range">>, #{<<"column">> => 3, <<"min">> => 100, <<"max">> => 500}),
Q3 = mongreldb:query_projection(Q2, [1, 3]),
Q4 = mongreldb:query_limit(Q3, 50),
{ok, _, _} = mongreldb:query_execute(Db, Q4).

Friendly alias translation

The builder accepts readable parameter names and translates them to the server's canonical on-wire keys. Both spellings work, so use whichever is clearer in context.

You writeSent asApplies to
columncolumn_idall condition types
minlorange, range_f64
maxhirange, range_f64
min_inclusivelo_inclusiverange_f64
max_inclusivehi_inclusiverange_f64
valuepatternfm_contains, fm_contains_all only

The value -> pattern alias applies only to FTS conditions, because pk and bitmap_eq use value as their canonical key. For those, write value directly.

Putting it together

A realistic combined lookup - bitmap equality + range + projection + limit + truncation check:

top_spenders(Db, Customer) ->
    Q0 = mongreldb:query(Db, <<"orders">>),
    Q1 = mongreldb:query_where(Q0, <<"bitmap_eq">>, #{<<"column">> => 2, <<"value">> => Customer}),
    Q2 = mongreldb:query_where(Q1, <<"range">>, #{<<"column">> => 3, <<"min">> => 100}),
    Q3 = mongreldb:query_projection(Q2, [1, 3]),
    Q4 = mongreldb:query_limit(Q3, 50),
    {ok, Rows, Q5} = mongreldb:query_execute(Db, Q4),
    true = mongreldb:query_truncated(Q5) =:= false,
    Rows.

For arbitrary predicates, joins, and aggregations that the native indexes do not cover, use SQL instead - see sql.md.