Skip to main content

lib/terminus_db/woql/decoder.ex

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