TypeDB.Given (TypeDB v0.5.1)

Copy Markdown View Source

Encodes input rows for TypeQL's given stage (TypeDB 3.12+).

A given stage binds variables to rows supplied beside the query, and runs the rest of the pipeline once per row. That makes it both the fast way to write many rows — one request, one query compilation — and the only safe way to put user input into a query.

TypeDB.query(conn, "social", """
  given $n: string;
  insert $p isa person, has name == $n;
""", given_rows: [%{"n" => "Alice"}, %{"n" => "Bob"}])

Why the driver encodes values

The HTTP API accepts a bare JSON string for a row entry, but TypeDB then parses it as a TypeQL literal, so a value containing a quote is a parse error — and the escaping rules are TypeQL's, not JSON's. The tagged form (%{"kind" => "value", "value" => …, "valueType" => …}) is quoted by JSON itself and is therefore exact for arbitrary content.

This module always emits the tagged form, so given_rows is safe against TypeQL injection for any input, including quotes, semicolons and newlines. Pass plain Elixir terms and let it do the encoding.

Supported terms

ElixirTypeDB
String.t()string
integer()integer
float()double
boolean()boolean
Date.t()date
NaiveDateTime.t()datetime
DateTime.t() / TypeDB.DateTimeTZ.t()datetime-tz
TypeDB.Duration.t()duration
Decimal.t()decimal
nilan unbound optional column
TypeDB.Concept.Entity / Relation / Attribute / Valuethe concept itself

Concepts returned by a previous query can be fed straight back in, which is how you bind a given $p: person; column.

A map that already carries a "kind" key is passed through untouched, as an escape hatch for wire forms this module does not build — the one input this module does not tag. Build it from your own code rather than from a user's value, for a reason worth stating precisely, because it is not the obvious one.

Injection is not the risk. The payload is JSON, and TypeDB type-checks every column the query declares, so a user-supplied map cannot subvert a value column: against a given $n: string;, a value tagged integer is 400 GVN7, a concept is 400 PEX9, and an invented "kind" is 400 HSR2. The server closes all three.

The risk is a query that declares a concept column. given $p: person; will accept %{"kind" => "entity", "iid" => …}, and whoever chose that iid chose which entity the query reads — an insecure direct object reference, not a parse-level attack. Bind those columns with concepts your own code fetched, and the question does not arise.

Examples

iex> TypeDB.Given.encode("Alice")
%{"kind" => "value", "value" => "Alice", "valueType" => "string"}

A value that would end a TypeQL string literal is data here, not syntax, which is the whole point:

iex> TypeDB.Given.encode(~S|Robert"); drop|)
%{"kind" => "value", "value" => ~S|Robert"); drop|, "valueType" => "string"}

Summary

Types

One input row: variable name to value.

Functions

Encodes a single value into its tagged wire form.

Encodes a single row.

Encodes a list of rows into the HTTP wire format.

Types

row()

@type row() :: %{optional(String.t() | atom()) => term()}

One input row: variable name to value.

Functions

encode(wire)

@spec encode(term()) :: map() | nil

Encodes a single value into its tagged wire form.

encode_row(row)

@spec encode_row(row()) :: map()

Encodes a single row.

encode_rows(rows)

@spec encode_rows([row()] | nil) :: [map()] | nil

Encodes a list of rows into the HTTP wire format.

Returns nil for nil, so it can be applied unconditionally to an optional :given_rows option.