defmodule TerminusDB.WOQL.Decoder do
@moduledoc false
# JSON-LD decoder for WOQL queries — inverse of TerminusDB.WOQL.Encoder.
# Handles all four wrapper types: NodeValue, Value, DataValue, ArithmeticValue.
def decode(%{"@type" => "Triple", "graph" => graph} = m) do
TerminusDB.WOQL.quad(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
graph
)
end
def decode(%{"@type" => "Triple"} = m) do
TerminusDB.WOQL.triple(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"])
)
end
def decode(%{"@type" => "And", "and" => queries}) do
TerminusDB.WOQL.and_(Enum.map(queries, &decode/1))
end
def decode(%{"@type" => "Or", "or" => queries}) do
TerminusDB.WOQL.or_(Enum.map(queries, &decode/1))
end
def decode(%{"@type" => "Equals"} = m) do
TerminusDB.WOQL.eq(decode_value(m["left"]), decode_value(m["right"]))
end
def decode(%{"@type" => "Select", "variables" => vars, "query" => query}) do
TerminusDB.WOQL.select(Enum.map(vars, &decode_select_var/1), decode(query))
end
def decode(%{"@type" => "ReadDocument"} = m) do
TerminusDB.WOQL.read_document(decode_node(m["identifier"]), decode_value(m["document"]))
end
def decode(%{"@type" => "TypeOf"} = m) do
TerminusDB.WOQL.type_of(decode_value(m["value"]), decode_value(m["type"]))
end
def decode(%{"@type" => "True"}) do
TerminusDB.WOQL.true_()
end
def decode(%{"@type" => "Not", "query" => q}) do
TerminusDB.WOQL.not_(decode(q))
end
def decode(%{"@type" => "Optional", "query" => q}) do
TerminusDB.WOQL.opt(decode(q))
end
def decode(%{"@type" => "Once", "query" => q}) do
TerminusDB.WOQL.once(decode(q))
end
def decode(%{"@type" => "Immediately", "query" => q}) do
TerminusDB.WOQL.immediately(decode(q))
end
def decode(%{"@type" => "Distinct", "variables" => vars, "query" => q}) do
TerminusDB.WOQL.distinct(Enum.map(vars, &decode_select_var/1), decode(q))
end
def decode(%{"@type" => "Limit", "limit" => n, "query" => q}) do
TerminusDB.WOQL.limit(n, decode(q))
end
def decode(%{"@type" => "Start", "start" => n, "query" => q}) do
TerminusDB.WOQL.start(n, decode(q))
end
def decode(%{"@type" => "OrderBy", "ordering" => ordering, "query" => q}) do
specs =
Enum.map(ordering, fn %{"variable" => var, "order" => order} ->
{var, decode_order(order)}
end)
TerminusDB.WOQL.order_by(specs, decode(q))
end
def decode(%{
"@type" => "GroupBy",
"group_by" => vars,
"template" => template,
"grouped" => grouped,
"query" => q
}) do
TerminusDB.WOQL.group_by(
Enum.map(vars, &decode_select_var/1),
decode_value(template),
decode_value(grouped),
decode(q)
)
end
def decode(%{"@type" => "Count", "count" => countvar, "query" => q}) do
TerminusDB.WOQL.count(decode_value(countvar), decode(q))
end
def decode(%{"@type" => "Collect", "template" => template, "into" => into, "query" => q}) do
TerminusDB.WOQL.collect(decode_value(template), decode_value(into), decode(q))
end
def decode(%{"@type" => "AddedTriple", "graph" => graph} = m) do
TerminusDB.WOQL.added_quad(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
graph
)
end
def decode(%{"@type" => "AddedTriple"} = m) do
TerminusDB.WOQL.added_triple(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"])
)
end
def decode(%{"@type" => "DeletedTriple", "graph" => graph} = m) do
TerminusDB.WOQL.removed_quad(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
graph
)
end
def decode(%{"@type" => "DeletedTriple"} = m) do
TerminusDB.WOQL.removed_triple(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"])
)
end
def decode(%{"@type" => "AddTriple", "graph" => graph} = m) do
TerminusDB.WOQL.add_quad(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
graph
)
end
def decode(%{"@type" => "AddTriple"} = m) do
TerminusDB.WOQL.add_triple(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"])
)
end
def decode(%{"@type" => "DeleteTriple", "graph" => graph} = m) do
TerminusDB.WOQL.delete_quad(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
graph
)
end
def decode(%{"@type" => "DeleteTriple"} = m) do
TerminusDB.WOQL.delete_triple(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"])
)
end
def decode(%{"@type" => "Less"} = m) do
TerminusDB.WOQL.less(decode_value(m["left"]), decode_value(m["right"]))
end
def decode(%{"@type" => "Greater"} = m) do
TerminusDB.WOQL.greater(decode_value(m["left"]), decode_value(m["right"]))
end
def decode(%{"@type" => "Gte"} = m) do
TerminusDB.WOQL.gte(decode_value(m["left"]), decode_value(m["right"]))
end
def decode(%{"@type" => "Lte"} = m) do
TerminusDB.WOQL.lte(decode_value(m["left"]), decode_value(m["right"]))
end
def decode(%{"@type" => "Like"} = m) do
TerminusDB.WOQL.like(
decode_value(m["left"]),
decode_value(m["right"]),
decode_value(m["distance"])
)
end
def decode(%{"@type" => "IsA"} = m) do
TerminusDB.WOQL.isa(decode_node(m["element"]), decode_node(m["type"]))
end
def decode(%{"@type" => "Subsumption"} = m) do
TerminusDB.WOQL.sub(decode_node(m["parent"]), decode_node(m["child"]))
end
def decode(%{"@type" => "Typecast"} = m) do
TerminusDB.WOQL.cast(
decode_value(m["value"]),
decode_value(m["type"]),
decode_value(m["result"])
)
end
def decode(%{"@type" => "Eval", "expression" => expr, "result" => result}) do
TerminusDB.WOQL.eval(decode_arithmetic(expr), decode_value(result))
end
def decode(%{"@type" => "Plus", "arguments" => args}) do
TerminusDB.WOQL.plus(Enum.map(args, &decode_arithmetic/1))
end
def decode(%{"@type" => "Minus", "arguments" => args}) do
TerminusDB.WOQL.minus(Enum.map(args, &decode_arithmetic/1))
end
def decode(%{"@type" => "Times", "arguments" => args}) do
TerminusDB.WOQL.times(Enum.map(args, &decode_arithmetic/1))
end
def decode(%{"@type" => "Divide", "arguments" => args}) do
TerminusDB.WOQL.divide(Enum.map(args, &decode_arithmetic/1))
end
def decode(%{"@type" => "Div", "arguments" => args}) do
TerminusDB.WOQL.div(Enum.map(args, &decode_arithmetic/1))
end
def decode(%{"@type" => "Exp", "first" => base, "second" => exponent}) do
TerminusDB.WOQL.exp(decode_arithmetic(base), decode_arithmetic(exponent))
end
def decode(%{"@type" => "Floor", "data" => value}) do
TerminusDB.WOQL.floor(decode_arithmetic(value))
end
def decode(%{"@type" => "Sum", "list" => list, "result" => result}) do
TerminusDB.WOQL.sum(decode_value(list), decode_value(result))
end
def decode(%{"@type" => "Concatenate", "list" => list, "result" => result}) do
TerminusDB.WOQL.concat(Enum.map(list, &decode_data/1), decode_data(result))
end
def decode(%{"@type" => "Join", "list" => list, "glue" => glue, "result" => output}) do
TerminusDB.WOQL.join(decode_data(list), decode_data(glue), decode_data(output))
end
def decode(%{"@type" => "Substring"} = m) do
TerminusDB.WOQL.substr(
decode_data(m["string"]),
decode_data(m["length"]),
decode_data(m["substring"]),
decode_data(m["before"]),
decode_data(m["after"])
)
end
def decode(%{"@type" => "Trim", "untrimmed" => u, "trimmed" => t}) do
TerminusDB.WOQL.trim(decode_data(u), decode_data(t))
end
def decode(%{"@type" => "Upper", "left" => l, "right" => r}) do
TerminusDB.WOQL.upper(decode_data(l), decode_data(r))
end
def decode(%{"@type" => "Lower", "left" => l, "right" => r}) do
TerminusDB.WOQL.lower(decode_data(l), decode_data(r))
end
def decode(%{"@type" => "Pad"} = m) do
TerminusDB.WOQL.pad(
decode_data(m["string"]),
decode_data(m["pad"]),
decode_data(m["length"]),
decode_data(m["result"])
)
end
def decode(%{"@type" => "Split", "string" => s, "glue" => g, "result" => r}) do
TerminusDB.WOQL.split(decode_data(s), decode_data(g), decode_data(r))
end
def decode(%{"@type" => "Length", "list" => list, "length" => len}) do
TerminusDB.WOQL.length(decode_value(list), decode_value(len))
end
def decode(%{"@type" => "Regexp", "pattern" => p, "string" => s, "result" => r}) do
TerminusDB.WOQL.regexp(decode_data(p), decode_data(s), decode_data(r))
end
def decode(%{"@type" => "Dot", "document" => d, "field" => f, "value" => v}) do
TerminusDB.WOQL.dot(decode_value(d), decode_data(f), decode_value(v))
end
def decode(%{"@type" => "Member", "member" => m, "list" => l}) do
TerminusDB.WOQL.member(decode_value(m), decode_value(l))
end
def decode(%{"@type" => "Slice"} = m) do
TerminusDB.WOQL.slice(
decode_value(m["list"]),
decode_value(m["slice"]),
decode_value(m["from"]),
decode_value(m["to"])
)
end
def decode(%{"@type" => "SetDifference", "left" => l, "right" => r, "result" => res}) do
TerminusDB.WOQL.set_difference(decode_value(l), decode_value(r), decode_value(res))
end
def decode(%{"@type" => "SetIntersection", "left" => l, "right" => r, "result" => res}) do
TerminusDB.WOQL.set_intersection(decode_value(l), decode_value(r), decode_value(res))
end
def decode(%{"@type" => "SetUnion", "left" => l, "right" => r, "result" => res}) do
TerminusDB.WOQL.set_union(decode_value(l), decode_value(r), decode_value(res))
end
def decode(%{"@type" => "SetMember", "element" => e, "set" => s}) do
TerminusDB.WOQL.set_member(decode_value(e), decode_value(s))
end
def decode(%{"@type" => "ListToSet", "list" => l, "result" => r}) do
TerminusDB.WOQL.list_to_set(decode_value(l), decode_value(r))
end
def decode(%{"@type" => "Path"} = m) do
subject = decode_node(m["subject"])
pattern = TerminusDB.WOQL.Path.from_jsonld(m["pattern"])
object = decode_value(m["object"])
if m["path"] do
TerminusDB.WOQL.path(subject, pattern, object, decode_value(m["path"]))
else
TerminusDB.WOQL.path(subject, pattern, object)
end
end
def decode(%{"@type" => "HashKey", "base" => base, "key_list" => key_list, "uri" => uri}) do
TerminusDB.WOQL.unique(
decode_data(base),
Enum.map(key_list, &decode_data/1),
decode_node(uri)
)
end
def decode(%{"@type" => "LexicalKey", "base" => base, "key_list" => key_list, "uri" => uri}) do
TerminusDB.WOQL.idgen(
decode_data(base),
Enum.map(key_list, &decode_data/1),
decode_node(uri)
)
end
def decode(%{"@type" => "RandomKey", "base" => base, "uri" => uri}) do
TerminusDB.WOQL.idgen_random(decode_data(base), decode_node(uri))
end
def decode(%{"@type" => "InsertDocument", "document" => doc} = json) do
id = if json["identifier"], do: decode_node(json["identifier"]), else: nil
TerminusDB.WOQL.insert_document(decode_document(doc), id)
end
def decode(%{"@type" => "UpdateDocument", "document" => doc} = json) do
id = if json["identifier"], do: decode_node(json["identifier"]), else: nil
TerminusDB.WOQL.update_document(decode_document(doc), id)
end
def decode(%{"@type" => "DeleteDocument", "identifier" => iri}) do
TerminusDB.WOQL.delete_document(decode_node(iri))
end
def decode(%{"@type" => "Using", "collection" => collection, "query" => q}) do
TerminusDB.WOQL.using(collection, decode(q))
end
def decode(%{"@type" => "From", "graph" => graph, "query" => q}) do
TerminusDB.WOQL.from(graph, decode(q))
end
def decode(%{"@type" => "Into", "graph" => graph, "query" => q}) do
TerminusDB.WOQL.into(graph, decode(q))
end
def decode(%{"@type" => "Comment", "comment" => %{"@value" => text}, "query" => q}) do
TerminusDB.WOQL.comment(text, decode(q))
end
def decode(%{"@type" => "Size", "graph" => graph, "size" => size_var}) do
TerminusDB.WOQL.size(graph, decode_value(size_var))
end
def decode(%{"@type" => "TripleCount", "graph" => graph, "triple_count" => count_var}) do
TerminusDB.WOQL.triple_count(graph, decode_value(count_var))
end
def decode(%{"@type" => "TripleSlice", "graph" => graph} = m) do
TerminusDB.WOQL.quad_slice(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["low"]),
decode_value(m["high"]),
graph
)
end
def decode(%{"@type" => "TripleSlice"} = m) do
TerminusDB.WOQL.triple_slice(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["low"]),
decode_value(m["high"])
)
end
def decode(%{"@type" => "TripleSliceRev", "graph" => graph} = m) do
TerminusDB.WOQL.quad_slice_rev(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["low"]),
decode_value(m["high"]),
graph
)
end
def decode(%{"@type" => "TripleSliceRev"} = m) do
TerminusDB.WOQL.triple_slice_rev(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["low"]),
decode_value(m["high"])
)
end
def decode(%{"@type" => "TripleNext", "graph" => graph} = m) do
TerminusDB.WOQL.quad_next(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["next"]),
graph
)
end
def decode(%{"@type" => "TripleNext"} = m) do
TerminusDB.WOQL.triple_next(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["next"])
)
end
def decode(%{"@type" => "TriplePrevious", "graph" => graph} = m) do
TerminusDB.WOQL.quad_previous(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["previous"]),
graph
)
end
def decode(%{"@type" => "TriplePrevious"} = m) do
TerminusDB.WOQL.triple_previous(
decode_node(m["subject"]),
decode_node(m["predicate"]),
decode_value(m["object"]),
decode_value(m["previous"])
)
end
# --------------------------------------------------------------------------
# Temporal / Allen interval algebra
# --------------------------------------------------------------------------
def decode(%{"@type" => "Interval"} = m) do
TerminusDB.WOQL.interval(
decode_value(m["start"]),
decode_value(m["end"]),
decode_value(m["interval"])
)
end
def decode(%{"@type" => "IntervalStartDuration"} = m) do
TerminusDB.WOQL.interval_start_duration(
decode_value(m["start"]),
decode_value(m["duration"]),
decode_value(m["interval"])
)
end
def decode(%{"@type" => "IntervalDurationEnd"} = m) do
TerminusDB.WOQL.interval_duration_end(
decode_value(m["duration"]),
decode_value(m["end"]),
decode_value(m["interval"])
)
end
def decode(%{"@type" => "IntervalRelation"} = m) do
TerminusDB.WOQL.interval_relation(
decode_value(m["relation"]),
decode_value(m["start"]),
decode_value(m["end"]),
decode_value(m["start2"]),
decode_value(m["end2"])
)
end
def decode(%{"@type" => "IntervalRelationTyped"} = m) do
TerminusDB.WOQL.interval_relation_typed(
decode_value(m["relation"]),
decode_value(m["left"]),
decode_value(m["right"])
)
end
def decode(%{"@type" => "DateDuration"} = m) do
TerminusDB.WOQL.date_duration(
decode_value(m["start"]),
decode_value(m["end"]),
decode_value(m["duration"])
)
end
def decode(%{"@type" => "DayAfter"} = m) do
TerminusDB.WOQL.day_after(
decode_value(m["date"]),
decode_value(m["next"])
)
end
def decode(%{"@type" => "DayBefore"} = m) do
TerminusDB.WOQL.day_before(
decode_value(m["date"]),
decode_value(m["previous"])
)
end
def decode(%{"@type" => "Weekday"} = m) do
TerminusDB.WOQL.weekday(
decode_value(m["date"]),
decode_value(m["weekday"])
)
end
def decode(%{"@type" => "WeekdaySundayStart"} = m) do
TerminusDB.WOQL.weekday_sunday_start(
decode_value(m["date"]),
decode_value(m["weekday"])
)
end
def decode(%{"@type" => "IsoWeek"} = m) do
TerminusDB.WOQL.iso_week(
decode_value(m["date"]),
decode_value(m["year"]),
decode_value(m["week"])
)
end
def decode(%{"@type" => "MonthStartDate"} = m) do
TerminusDB.WOQL.month_start_date(
decode_value(m["year_month"]),
decode_value(m["date"])
)
end
def decode(%{"@type" => "MonthEndDate"} = m) do
TerminusDB.WOQL.month_end_date(
decode_value(m["year_month"]),
decode_value(m["date"])
)
end
def decode(%{"@type" => "MonthStartDates"} = m) do
TerminusDB.WOQL.month_start_dates(
decode_value(m["date"]),
decode_value(m["start"]),
decode_value(m["end"])
)
end
def decode(%{"@type" => "MonthEndDates"} = m) do
TerminusDB.WOQL.month_end_dates(
decode_value(m["date"]),
decode_value(m["start"]),
decode_value(m["end"])
)
end
def decode(%{"@type" => "InRange"} = m) do
TerminusDB.WOQL.in_range(
decode_value(m["value"]),
decode_value(m["start"]),
decode_value(m["end"])
)
end
def decode(%{"@type" => "Sequence"} = m) do
step = if m["step"], do: decode_value(m["step"]), else: nil
count = if m["count"], do: decode_value(m["count"]), else: nil
TerminusDB.WOQL.sequence(
decode_value(m["value"]),
decode_value(m["start"]),
decode_value(m["end"]),
step,
count
)
end
def decode(%{"@type" => "RangeMin"} = m) do
TerminusDB.WOQL.range_min(
decode_value(m["list"]),
decode_value(m["min"])
)
end
def decode(%{"@type" => "RangeMax"} = m) do
TerminusDB.WOQL.range_max(
decode_value(m["list"]),
decode_value(m["max"])
)
end
# --------------------------------------------------------------------------
# CSV / IO
# --------------------------------------------------------------------------
def decode(%{"@type" => "Get"} = m) do
TerminusDB.WOQL.get(m["columns"], decode(m["resource"]))
end
def decode(%{"@type" => "Put"} = m) do
TerminusDB.WOQL.put(m["columns"], decode(m["query"]), decode(m["resource"]))
end
def decode(%{"@type" => "QueryResource"} = m) do
source = m["source"]
case source["@type"] do
"FileResource" ->
format = decode_format(m["format"])
TerminusDB.WOQL.file(source["file_name"], format: format)
"RemoteResource" ->
format = decode_format(m["format"])
TerminusDB.WOQL.remote(source["url"], format: format)
"PostResource" ->
format = decode_format(m["format"])
TerminusDB.WOQL.post(source["file_name"], format: format)
_ ->
TerminusDB.WOQL.file(source["file_name"] || "", format: decode_format(m["format"]))
end
end
defp decode_format(%{"format_type" => %{"@value" => format}}), do: format
defp decode_format(_), do: "csv"
# --------------------------------------------------------------------------
# NodeValue decoder
# --------------------------------------------------------------------------
def decode_node(%{"@type" => "NodeValue", "variable" => name}) do
"v:#{name}"
end
def decode_node(%{"@type" => "NodeValue", "node" => node}) do
node
end
def decode_node(value), do: value
# --------------------------------------------------------------------------
# Value decoder (also accepts DataValue/NodeValue for backward compat)
# --------------------------------------------------------------------------
def decode_value(%{"@type" => "Value", "variable" => name}) do
"v:#{name}"
end
def decode_value(%{"@type" => "Value", "node" => node}) do
node
end
def decode_value(%{"@type" => "Value", "data" => %{"@value" => value}}) do
value
end
def decode_value(%{"@type" => "DataValue", "variable" => name}) do
"v:#{name}"
end
def decode_value(%{"@type" => "DataValue", "data" => %{"@value" => value}}) do
value
end
def decode_value(%{"@type" => "NodeValue", "variable" => name}) do
"v:#{name}"
end
def decode_value(%{"@type" => "NodeValue", "node" => node}) do
node
end
def decode_value(%{"@type" => "Value", "dictionary" => %{"data" => pairs}}) do
decode_document_dictionary(pairs)
end
def decode_value(value), do: value
def decode_document(%{"@type" => "Value", "variable" => name}) do
"v:#{name}"
end
def decode_document(%{"@type" => "Value", "dictionary" => %{"data" => pairs}}) do
decode_document_dictionary(pairs)
end
def decode_document(other), do: decode_value(other)
defp decode_document_dictionary(pairs) do
Enum.reduce(pairs, %{}, fn %{"field" => field, "value" => val}, acc ->
Map.put(acc, field, decode_value(val))
end)
end
# --------------------------------------------------------------------------
# DataValue decoder
# --------------------------------------------------------------------------
def decode_data(%{"@type" => "DataValue", "variable" => name}) do
"v:#{name}"
end
def decode_data(%{"@type" => "DataValue", "data" => %{"@value" => value}}) do
value
end
def decode_data(%{"@type" => "Value", "variable" => name}) do
"v:#{name}"
end
def decode_data(%{"@type" => "Value", "data" => %{"@value" => value}}) do
value
end
def decode_data(value), do: value
# --------------------------------------------------------------------------
# ArithmeticValue decoder
# --------------------------------------------------------------------------
def decode_arithmetic(%{"@type" => "ArithmeticValue", "variable" => name}) do
"v:#{name}"
end
def decode_arithmetic(%{"@type" => "ArithmeticValue", "data" => %{"@value" => value}}) do
value
end
def decode_arithmetic(%{"@type" => type} = value) when type != "ArithmeticValue" do
decode(value)
end
def decode_arithmetic(value), do: value
# --------------------------------------------------------------------------
# Select variables
# --------------------------------------------------------------------------
def decode_select_var(name) when is_binary(name), do: "v:#{name}"
defp decode_order("asc"), do: :asc
defp decode_order("desc"), do: :desc
defp decode_order(other), do: other
end