Dllb.Query (Dllb v0.7.0)

Copy Markdown View Source

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

fields()

@type fields() :: %{optional(atom()) => term()}

select_opts()

@type select_opts() :: [
  fields: [String.t()],
  where: String.t(),
  order: String.t(),
  limit: non_neg_integer(),
  fetch: String.t()
]

Functions

count(table, opts \\ [])

@spec count(
  String.t(),
  keyword()
) :: String.t()

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"

create(table, fields)

@spec create(String.t(), fields()) :: String.t()

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'"

create_with_id(table, id, fields)

@spec create_with_id(String.t(), String.t(), fields()) :: String.t()

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'"

define_field(table, name, type, opts \\ [])

@spec define_field(String.t(), String.t(), String.t(), keyword()) :: String.t()

Builds a DEFINE FIELD statement.

Options

  • :required - if true, 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"

define_fulltext_index(table, name, field, opts \\ [])

@spec define_fulltext_index(String.t(), String.t(), String.t(), keyword()) ::
  String.t()

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's default analyzer 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"

define_index(table, name, fields, opts \\ [])

@spec define_index(String.t(), String.t(), [String.t()], keyword()) :: String.t()

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 - when true, enforces uniqueness over the full indexed tuple; defining the index fails if existing rows already hold duplicate values (default false)

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"

define_table(name, mode)

@spec define_table(String.t(), :schemafull | :schemaless) :: String.t()

Builds a DEFINE TABLE statement.

Mode can be :schemafull or :schemaless.

Examples

iex> Dllb.Query.define_table("user", :schemafull)
"DEFINE TABLE user SCHEMAFULL"

define_vector_index(table, name, field, dimension, opts \\ [])

@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's cosine metric 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"

delete(record_id)

@spec delete(String.t()) :: String.t()

Builds a DELETE statement for a single record.

Examples

iex> Dllb.Query.delete("user:u1")
"DELETE user:u1"

delete_where(table, where \\ nil)

@spec delete_where(String.t(), String.t() | nil) :: String.t()

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"

graph_centrality(edge_table, opts \\ [])

@spec graph_centrality(
  String.t(),
  keyword()
) :: String.t()

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"

graph_communities(edge_table, opts \\ [])

@spec graph_communities(
  String.t(),
  keyword()
) :: String.t()

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"

graph_components(edge_table)

@spec graph_components(String.t()) :: String.t()

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"

graph_edges(edge_table, opts \\ [])

@spec graph_edges(
  String.t(),
  keyword()
) :: String.t()

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"

graph_pagerank(edge_table, opts \\ [])

@spec graph_pagerank(
  String.t(),
  keyword()
) :: String.t()

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's 0.85)
  • :max_iter - maximum power-iteration steps (default the engine's 100)
  • :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"

graph_path(src, dst, edge_table, opts \\ [])

@spec graph_path(String.t(), String.t(), String.t(), keyword()) :: String.t()

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"

hybrid_search(table, text_field, query, vector_field, vector, opts \\ [])

@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, in 0.0..1.0; the vector score gets 1 - alpha. Defaults to the engine's 0.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"

raw(query_string)

@spec raw(String.t()) :: String.t()

Passes through a raw query string without modification.

Examples

iex> Dllb.Query.raw("INFO FOR DB")
"INFO FOR DB"

relate(from_id, edge_type, to_id, properties \\ %{})

@spec relate(String.t(), String.t(), String.t(), fields()) :: String.t()

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'"

remove_index(table, name)

@spec remove_index(String.t(), String.t()) :: String.t()

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"

search(table, field, query, opts \\ [])

@spec search(String.t(), String.t(), String.t(), keyword()) :: String.t()

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"

select(table, opts \\ [])

@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"

update(record_id, fields)

@spec update(String.t(), fields()) :: String.t()

Builds an UPDATE statement for an existing record.

Examples

iex> Dllb.Query.update("user:u1", %{name: "Bob"})
"UPDATE user:u1 SET name = 'Bob'"

update_where(table, fields, where)

@spec update_where(String.t(), fields(), String.t()) :: String.t()

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'"

upsert(table, id, fields, update_fields \\ %{})

@spec upsert(String.t(), String.t(), fields(), fields()) :: String.t()

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 same fields from the CREATE into the existing record (ON CONFLICT UPDATE);
  • with a non-empty update_fields map, 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"

vector_search(table, field, vector, opts \\ [])

@spec vector_search(String.t(), String.t(), [number()], keyword()) :: String.t()

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"