Dllb.Query (Dllb v0.6.0)

Copy Markdown View Source

Query builder that generates dllb SQL strings.

Provides functions to construct CREATE, SELECT, UPDATE, DELETE, RELATE, COUNT, DEFINE (secondary, full-text, and vector index), REMOVE INDEX, and SEARCH / VECTOR SEARCH statements 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>] 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 record.

Builds a GRAPH COMMUNITIES statement for native community detection.

Builds a GRAPH COMPONENTS <edge_table> statement for native connected- components detection.

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>] statement.

Options

  • :where - optional WHERE clause string

Examples

iex> Dllb.Query.count("user")
"COUNT user"

iex> Dllb.Query.count("user", where: "age = 30")
"COUNT user WHERE age = 30"

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 record.

Examples

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

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"

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

  • :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"

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

  • :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"