defmodule EdgeDB.Query do
@moduledoc """
A structure carrying the information related to the query.
It's mostly used in driver internally, but user can retrive it along with `EdgeDB.Result` struct
from succeed query execution using `:raw` option for `EdgeDB.query*/4` functions. See `t:EdgeDB.query_option/0`.
"""
alias EdgeDB.Protocol.{
Codec,
CodecStorage,
Enums
}
defstruct [
:statement,
output_format: :binary,
implicit_limit: 0,
inline_type_names: false,
inline_type_ids: false,
inline_object_ids: true,
cardinality: :many,
required: false,
is_script: false,
capabilities: [],
input_codec: nil,
output_codec: nil,
codec_storage: nil,
cached: false,
params: []
]
@typedoc """
A structure carrying the information related to the query.
Fields:
* `:statement` - EdgeQL statement for execution.
* `:output_format` - the preferred format of the query result.
* `:implicit_limit` - the implicit limit of elements count in the returned set as a result of the query.
* `:inline_type_names` - flag specifying that the result objects should contain type name as property.
* `:inline_type_ids` - flag specifying that the result objects should contain type ID as property.
* `:inline_object_ids` - flag specifying that the result objects should contain ID as property.
* `:cardinality` - the expected number of elements in the returned set as a result of the query.
* `:required` - flag specifying that the result should not be empty.
* `:is_script` - flag specifying that the query statement is a script that shouldn't have any return value.
* `:capabilities` - query capabilities. See
[RFC](https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst#edgedb-changes)
for more information.
* `:input_codec` - codec for encoding query parameters.
* `:output_codec` - codec for decoding the query result.
* `:codec_storage` - codec storage from connection used by query to encode parameters.
* `:cached` - flag specifying whether the request has already been cached by the connection.
* `:params` - query parameters.
"""
@type t() :: %__MODULE__{
statement: String.t(),
output_format: Enums.output_format(),
implicit_limit: non_neg_integer(),
inline_type_names: boolean(),
inline_type_ids: boolean(),
inline_object_ids: boolean(),
cardinality: Enums.cardinality(),
required: boolean(),
is_script: boolean(),
capabilities: Enums.capabilities(),
input_codec: Codec.id() | nil,
output_codec: Codec.id() | nil,
codec_storage: CodecStorage.t(),
cached: boolean(),
params: list(any())
}
end
defimpl DBConnection.Query, for: EdgeDB.Query do
alias EdgeDB.Protocol.{
Codec,
CodecStorage
}
@empty_set %EdgeDB.Set{__items__: []}
@impl DBConnection.Query
def decode(%EdgeDB.Query{}, %EdgeDB.Result{set: %EdgeDB.Set{}} = result, _opts) do
result
end
@impl DBConnection.Query
def decode(
%EdgeDB.Query{output_codec: out_codec, required: required, codec_storage: codec_storage},
%EdgeDB.Result{} = result,
_opts
) do
decode_result(%EdgeDB.Result{result | required: required}, out_codec, codec_storage)
end
@impl DBConnection.Query
def describe(query, _opts) do
query
end
@impl DBConnection.Query
def encode(%EdgeDB.Query{input_codec: nil}, _params, _opts) do
raise EdgeDB.InterfaceError.new("query hasn't been prepared")
end
@impl DBConnection.Query
def encode(%EdgeDB.Query{input_codec: in_codec, codec_storage: codec_storage}, params, _opts) do
codec_storage
|> CodecStorage.get(in_codec)
|> Codec.encode(params, codec_storage)
end
@impl DBConnection.Query
def parse(%EdgeDB.Query{cached: true}, _opts) do
raise EdgeDB.InterfaceError.new("query has been prepared")
end
@impl DBConnection.Query
def parse(query, _opts) do
query
end
defp decode_result(%EdgeDB.Result{cardinality: :no_result} = result, _codec, _codec_storage) do
result
end
defp decode_result(%EdgeDB.Result{} = result, codec, codec_storage) do
encoded_set = result.set
result = %EdgeDB.Result{result | set: @empty_set}
encoded_set
|> Enum.reverse()
|> Enum.reduce(result, fn data, %EdgeDB.Result{set: set} = result ->
element =
codec_storage
|> CodecStorage.get(codec)
|> Codec.decode(data, codec_storage)
%EdgeDB.Result{result | set: add_element_into_set(set, element)}
end)
|> then(fn %EdgeDB.Result{set: set} = result ->
%EdgeDB.Result{result | set: reverse_elements_in_set(set)}
end)
end
defp add_element_into_set(%EdgeDB.Set{__items__: items} = set, element) do
%EdgeDB.Set{set | __items__: [element | items]}
end
defp reverse_elements_in_set(%EdgeDB.Set{__items__: items} = set) do
%EdgeDB.Set{set | __items__: Enum.reverse(items)}
end
end