Query builder that generates dllb SQL strings.
Provides functions to construct CREATE, SELECT (with ORDER BY), UPDATE,
DELETE (point and DELETE ... WHERE), RELATE, COUNT (with GROUP BY),
DEFINE (secondary, full-text, and vector index), REMOVE INDEX, SEARCH /
VECTOR SEARCH / HYBRID SEARCH (with optional WHERE scoping), and the graph
analytics verbs (COMMUNITIES, COMPONENTS, PAGERANK, CENTRALITY, PATH, EDGES)
for the dllb query language. All functions return plain query strings ready
to be sent over the wire.
Summary
Functions
Builds a COUNT <table> [WHERE <clause>] [GROUP BY <field>] statement.
Builds a CREATE statement for inserting a new record.
Builds a CREATE statement with an explicit record ID.
Builds a DEFINE FIELD statement.
Builds a DEFINE FULLTEXT INDEX statement that registers a BM25 full-text
(Tantivy) index over a single text field.
Builds a DEFINE INDEX statement that registers a persisted secondary
index in the engine's catalog and backfills entries for existing rows.
Builds a DEFINE TABLE statement.
Builds a DEFINE VECTOR INDEX statement that registers an approximate
nearest-neighbour (HNSW) index over a dense-embedding field.
Builds a DELETE statement for a single record.
Builds a DELETE <table> [WHERE <clause>] statement that removes every row
matching the (already-built) WHERE clause in a single server-side operation.
Builds a GRAPH CENTRALITY <edge_table> statement: degree centrality over
the edge table, returning {id, score} rows ranked by score descending.
Builds a GRAPH COMMUNITIES statement for native community detection.
Builds a GRAPH COMPONENTS <edge_table> statement for native connected-
components detection.
Builds a GRAPH EDGES <edge_table> [WHERE <clause>] statement: lists the
edges of an edge table as rows of {src, dst, edge_type, weight, ...props},
surfacing the real stored weight (default 1.0). The optional WHERE
filters the synthetic edge rows (e.g. by src, dst, or weight).
Builds a GRAPH PAGERANK <edge_table> statement: weighted PageRank over the
edge table, returning {id, score} rows ranked by score descending.
Builds a GRAPH PATH <src> -> <dst> ON <edge_table> statement: the shortest
directed path between two vertices. src and dst are bare vertex ids (the
id part, not table:id). The result is a single row with found, length,
and path fields.
Builds a HYBRID SEARCH statement that fuses a BM25 full-text query on
text_field with an approximate nearest-neighbour query on vector_field.
Passes through a raw query string without modification.
Builds a RELATE statement to create a graph edge between two records.
Builds a REMOVE INDEX statement that drops a secondary index and all of
its catalog entries. Subsequent queries fall back to full scans.
Builds a SEARCH statement: a BM25 full-text query against a field that
has a full-text index. Results come back as rows ranked best-first, each
carrying an extra score field.
Builds a SELECT statement with optional clauses.
Builds an UPDATE statement for an existing record.
Builds an UPDATE <table> SET ... WHERE <clause> statement that updates
every row matching the (already-built) WHERE clause.
Builds a CREATE ... ON CONFLICT UPDATE statement for idempotent upserts.
Builds a VECTOR SEARCH statement: an approximate nearest-neighbour query
against a field that has a vector index. vector is the query embedding
(a list of numbers). Results come back as rows ordered nearest-first, each
carrying an extra distance field.
Types
Functions
Builds a COUNT <table> [WHERE <clause>] [GROUP BY <field>] statement.
Without :group_by the engine returns a single total count. With
:group_by, it returns one row per distinct value of the field, each with a
count, sorted by count descending (a Dllb.Result.Rows, not a
Dllb.Result.Count).
Options
:where- optional WHERE clause string:group_by- optional field name to group counts by
Examples
iex> Dllb.Query.count("user")
"COUNT user"
iex> Dllb.Query.count("user", where: "age = 30")
"COUNT user WHERE age = 30"
iex> Dllb.Query.count("ast_node", group_by: "kind")
"COUNT ast_node GROUP BY kind"
Builds a CREATE statement for inserting a new record.
Examples
iex> Dllb.Query.create("user", %{name: "Alice", age: 30})
"CREATE user SET age = 30, name = 'Alice'"
Builds a CREATE statement with an explicit record ID.
Examples
iex> Dllb.Query.create_with_id("user", "u1", %{name: "Alice"})
"CREATE user:u1 SET name = 'Alice'"
Builds a DEFINE FIELD statement.
Options
:required- iftrue, appends ASSERT $value IS NOT NONE
Examples
iex> Dllb.Query.define_field("user", "name", "string", required: true)
"DEFINE FIELD name ON user TYPE string ASSERT $value IS NOT NONE"
Builds a DEFINE FULLTEXT INDEX statement that registers a BM25 full-text
(Tantivy) index over a single text field.
Requires a dllb server with full-text/vector services enabled (the default server build).
Options
:analyzer- tokenizer/stemmer to apply. One of"default","simple", or a language ("english","spanish","french","german","italian","portuguese","russian"). Defaults to the engine'sdefaultanalyzer when omitted.
Examples
iex> Dllb.Query.define_fulltext_index("article", "ft_body", "body")
"DEFINE FULLTEXT INDEX ft_body ON TABLE article FIELDS body"
iex> Dllb.Query.define_fulltext_index("article", "ft_body", "body", analyzer: "english")
"DEFINE FULLTEXT INDEX ft_body ON TABLE article FIELDS body ANALYZER english"
Builds a DEFINE INDEX statement that registers a persisted secondary
index in the engine's catalog and backfills entries for existing rows.
The index covers one or more fields. Composite (multi-field) indexes are
matched by the engine using leftmost-prefix planning, so list the most
selective leading field first. Once defined, equality and range
(>, >=, <, <=) predicates on the indexed fields are transparently
accelerated in SELECT/COUNT/UPDATE WHERE clauses — no change to the
query strings is required.
Options
:unique- whentrue, enforces uniqueness over the full indexed tuple; defining the index fails if existing rows already hold duplicate values (defaultfalse)
Examples
iex> Dllb.Query.define_index("user", "by_age", ["age"])
"DEFINE INDEX by_age ON TABLE user FIELDS age"
iex> Dllb.Query.define_index("user", "by_email", ["email"], unique: true)
"DEFINE INDEX by_email ON TABLE user FIELDS email UNIQUE"
iex> Dllb.Query.define_index("ast_node", "idx_file_kind", ["file_path", "kind"])
"DEFINE INDEX idx_file_kind ON TABLE ast_node FIELDS file_path, kind"
Builds a DEFINE TABLE statement.
Mode can be :schemafull or :schemaless.
Examples
iex> Dllb.Query.define_table("user", :schemafull)
"DEFINE TABLE user SCHEMAFULL"
@spec define_vector_index( String.t(), String.t(), String.t(), pos_integer(), keyword() ) :: String.t()
Builds a DEFINE VECTOR INDEX statement that registers an approximate
nearest-neighbour (HNSW) index over a dense-embedding field.
dimension is the (positive) length of the vectors to be indexed. Requires
a dllb server with full-text/vector services enabled (the default server
build).
Options
:metric- distance metric:"cosine"(default),"euclidean"(alias"l2"), or"dot"(alias"dotproduct"/"dot_product"). Defaults to the engine'scosinemetric when omitted.
Examples
iex> Dllb.Query.define_vector_index("ast_node", "vec_src", "source_embedding", 768)
"DEFINE VECTOR INDEX vec_src ON TABLE ast_node FIELDS source_embedding DIMENSION 768"
iex> Dllb.Query.define_vector_index("doc", "vec_emb", "embedding", 8, metric: "euclidean")
"DEFINE VECTOR INDEX vec_emb ON TABLE doc FIELDS embedding DIMENSION 8 METRIC euclidean"
Builds a DELETE statement for a single record.
Examples
iex> Dllb.Query.delete("user:u1")
"DELETE user:u1"
Builds a DELETE <table> [WHERE <clause>] statement that removes every row
matching the (already-built) WHERE clause in a single server-side operation.
Secondary, full-text, and vector indexes are maintained by the engine. The
result is a Dllb.Result.DeletedMany reporting how many rows were removed.
An empty (or omitted) WHERE deletes every row in the table.
Examples
iex> Dllb.Query.delete_where("ast_node", "file_path = '/a.ex'")
"DELETE ast_node WHERE file_path = '/a.ex'"
iex> Dllb.Query.delete_where("ast_node")
"DELETE ast_node"
Builds a GRAPH CENTRALITY <edge_table> statement: degree centrality over
the edge table, returning {id, score} rows ranked by score descending.
Options
:mode-:degree(default, in + out),:indegree, or:outdegree:limit- return only the top-N nodes
Examples
iex> Dllb.Query.graph_centrality("calls")
"GRAPH CENTRALITY calls"
iex> Dllb.Query.graph_centrality("calls", mode: :indegree, limit: 10)
"GRAPH CENTRALITY calls INDEGREE LIMIT 10"
Builds a GRAPH COMMUNITIES statement for native community detection.
Delegates computation to the dllb engine (Rust Louvain / Label Propagation), which runs in O(E) per iteration — orders of magnitude faster than the equivalent pure-Elixir implementation on large graphs.
Options
:algorithm-:louvain(default) or:lp(label propagation):max_iter- maximum optimisation passes (default: 10):resolution- Louvain resolution γ; values < 1.0 → fewer, larger communities; > 1.0 → more, smaller communities (default: 1.0)
Examples
iex> Dllb.Query.graph_communities("calls")
"GRAPH COMMUNITIES calls"
iex> Dllb.Query.graph_communities("calls", algorithm: :lp, max_iter: 20)
"GRAPH COMMUNITIES calls ALGORITHM lp MAX_ITER 20"
iex> Dllb.Query.graph_communities("calls", algorithm: :louvain, resolution: 0.5)
"GRAPH COMMUNITIES calls ALGORITHM louvain RESOLUTION 0.5"
Builds a GRAPH COMPONENTS <edge_table> statement for native connected-
components detection.
Delegates computation to the dllb engine (Rust union-find over the edge
table, treated as undirected). The server returns a compact summary
(component_count, largest, nodes) rather than full membership.
Examples
iex> Dllb.Query.graph_components("calls")
"GRAPH COMPONENTS calls"
Builds a GRAPH EDGES <edge_table> [WHERE <clause>] statement: lists the
edges of an edge table as rows of {src, dst, edge_type, weight, ...props},
surfacing the real stored weight (default 1.0). The optional WHERE
filters the synthetic edge rows (e.g. by src, dst, or weight).
Options
:where- optional WHERE clause string
Examples
iex> Dllb.Query.graph_edges("calls")
"GRAPH EDGES calls"
iex> Dllb.Query.graph_edges("calls", where: "weight > 0.5")
"GRAPH EDGES calls WHERE weight > 0.5"
Builds a GRAPH PAGERANK <edge_table> statement: weighted PageRank over the
edge table, returning {id, score} rows ranked by score descending.
Options
:damping- damping factor (default the engine's0.85):max_iter- maximum power-iteration steps (default the engine's100):limit- return only the top-N nodes
Examples
iex> Dllb.Query.graph_pagerank("calls")
"GRAPH PAGERANK calls"
iex> Dllb.Query.graph_pagerank("calls", damping: 0.9, max_iter: 50, limit: 10)
"GRAPH PAGERANK calls DAMPING 0.9 MAX_ITER 50 LIMIT 10"
Builds a GRAPH PATH <src> -> <dst> ON <edge_table> statement: the shortest
directed path between two vertices. src and dst are bare vertex ids (the
id part, not table:id). The result is a single row with found, length,
and path fields.
Options
:max_depth- bound the search to at most this many edges
Examples
iex> Dllb.Query.graph_path("a", "b", "calls")
"GRAPH PATH a -> b ON calls"
iex> Dllb.Query.graph_path("a", "b", "calls", max_depth: 4)
"GRAPH PATH a -> b ON calls MAX_DEPTH 4"
@spec hybrid_search( String.t(), String.t(), String.t(), String.t(), [number()], keyword() ) :: String.t()
Builds a HYBRID SEARCH statement that fuses a BM25 full-text query on
text_field with an approximate nearest-neighbour query on vector_field.
The engine normalises each modality's scores independently and blends them
as alpha * text_score + (1 - alpha) * vector_score, then returns rows
ranked best-first, each carrying score, text_score, and vector_score
fields. Requires a full-text index on text_field and a vector index on
vector_field.
Options
:alpha- weight on the (normalised) text score, in0.0..1.0; the vector score gets1 - alpha. Defaults to the engine's0.5.:where- optional WHERE clause to scope results server-side:limit- maximum number of hits to return
Examples
iex> Dllb.Query.hybrid_search("doc", "body", "graph db", "embedding", [0.1, 0.2])
"HYBRID SEARCH doc TEXT body 'graph db' VECTOR embedding [0.1, 0.2]"
iex> Dllb.Query.hybrid_search("doc", "body", "graph", "embedding", [0.1, 0.2], alpha: 0.7, limit: 5)
"HYBRID SEARCH doc TEXT body 'graph' VECTOR embedding [0.1, 0.2] ALPHA 0.7 LIMIT 5"
Passes through a raw query string without modification.
Examples
iex> Dllb.Query.raw("INFO FOR DB")
"INFO FOR DB"
Builds a RELATE statement to create a graph edge between two records.
Examples
iex> Dllb.Query.relate("user:a", "follows", "user:b", %{since: "2024"})
"RELATE user:a->follows->user:b SET since = '2024'"
Builds a REMOVE INDEX statement that drops a secondary index and all of
its catalog entries. Subsequent queries fall back to full scans.
Examples
iex> Dllb.Query.remove_index("user", "by_age")
"REMOVE INDEX by_age ON TABLE user"
Builds a SEARCH statement: a BM25 full-text query against a field that
has a full-text index. Results come back as rows ranked best-first, each
carrying an extra score field.
Options
:where- optional WHERE clause to scope results server-side (applied to the ranked hits before:limit):limit- maximum number of hits to return
Examples
iex> Dllb.Query.search("article", "body", "graph database")
"SEARCH article body 'graph database'"
iex> Dllb.Query.search("article", "body", "graph database", limit: 5)
"SEARCH article body 'graph database' LIMIT 5"
iex> Dllb.Query.search("article", "body", "graph", where: "lang = 'en'", limit: 5)
"SEARCH article body 'graph' WHERE lang = 'en' LIMIT 5"
@spec select(String.t(), select_opts()) :: String.t()
Builds a SELECT statement with optional clauses.
Options
:fields- list of field names to select (default["*"]):where- WHERE clause string:order- ORDER BY clause string:limit- LIMIT value:fetch- graph traversal fetch clause (e.g."->calls->fn_node")
Examples
iex> Dllb.Query.select("user", where: "age > 25", limit: 10)
"SELECT * FROM user WHERE age > 25 LIMIT 10"
Builds an UPDATE statement for an existing record.
Examples
iex> Dllb.Query.update("user:u1", %{name: "Bob"})
"UPDATE user:u1 SET name = 'Bob'"
Builds an UPDATE <table> SET ... WHERE <clause> statement that updates
every row matching the (already-built) WHERE clause.
Only the listed fields are changed (partial-update semantics). An empty WHERE string updates all rows in the table.
Examples
iex> Dllb.Query.update_where("ast_node", %{arity: 1}, "kind = 'function_def'")
"UPDATE ast_node SET arity = 1 WHERE kind = 'function_def'"
Builds a CREATE ... ON CONFLICT UPDATE statement for idempotent upserts.
If the record already exists (by table + id), the conflict is resolved by updating it instead of failing:
- with no
update_fields(or an empty map), the engine merges the samefieldsfrom the CREATE into the existing record (ON CONFLICT UPDATE); - with a non-empty
update_fieldsmap, the engine applies those explicit fields to the existing record instead (ON CONFLICT UPDATE SET ...).
Examples
iex> Dllb.Query.upsert("user", "u1", %{name: "Alice", age: 30})
"CREATE user:u1 SET age = 30, name = 'Alice' ON CONFLICT UPDATE"
iex> Dllb.Query.upsert("user", "u1", %{name: "Alice", age: 30}, %{age: 31})
"CREATE user:u1 SET age = 30, name = 'Alice' ON CONFLICT UPDATE SET age = 31"
Builds a VECTOR SEARCH statement: an approximate nearest-neighbour query
against a field that has a vector index. vector is the query embedding
(a list of numbers). Results come back as rows ordered nearest-first, each
carrying an extra distance field.
Options
:where- optional WHERE clause to scope results server-side (applied to the ranked hits before:k):k- number of nearest neighbours to return
Examples
iex> Dllb.Query.vector_search("doc", "embedding", [0.1, 0.2, 0.3])
"VECTOR SEARCH doc embedding [0.1, 0.2, 0.3]"
iex> Dllb.Query.vector_search("doc", "embedding", [0.1, 0.2, 0.3], k: 5)
"VECTOR SEARCH doc embedding [0.1, 0.2, 0.3] K 5"
iex> Dllb.Query.vector_search("doc", "embedding", [0.1, 0.2], where: "project = 'p1'", k: 5)
"VECTOR SEARCH doc embedding [0.1, 0.2] WHERE project = 'p1' K 5"