Dllb.Query (Dllb v0.5.0)

Copy Markdown View Source

Query builder that generates dllb SQL strings.

Provides functions to construct CREATE, SELECT, UPDATE, DELETE, RELATE, and DEFINE 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 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 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 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.

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

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"

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"