Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to skip parsing of results #35

Merged
merged 7 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion coveralls.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"coverage_options": {
"minimum_coverage": 100,
"treat_no_relevant_lines_as_covered": true
"treat_no_relevant_lines_as_covered": true,
"html_filter_full_covered": true
},
"skip_files": [
"^test",
Expand Down
44 changes: 44 additions & 0 deletions lib/snowpack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ defmodule Snowpack do
@doc """
Runs a query.

## Options

Options are passed to `DBConnection.prepare/3`, see it's documentation for
all available options.

## Additional Options

* `:parse_results` - Wether or not to do type parsing on the results. Requires
execution to be performed inside a transaction and an extra `DESCRIBE RESULT` to
get the types of the columns in the result. Only important for `SELECT` queries. Default true.

## Examples

iex> Snowpack.query(conn, "SELECT * FROM posts")
Expand Down Expand Up @@ -154,6 +165,27 @@ defmodule Snowpack do
end
end

@doc """
Runs an insert statement.

## Options

Options are passed to `DBConnection.prepare/3`, see it's documentation for
all available options.

## Examples

iex> Snowpack.insert(conn, "INSERT INTO TABEL (ROW1, ROW2) VALUES(1, 2)")
{:ok, %Snowpack.Result{}}

"""
@spec insert(conn, iodata, list, [option()]) ::
{:ok, Snowpack.Result.t()} | {:error, Exception.t()}
def insert(conn, statement, params \\ [], options \\ []) when is_iodata(statement) do
options = Keyword.merge(options, parse_results: false)
query(conn, statement, params, options)
end

@doc """
Prepares a query to be executed later.

Expand All @@ -167,6 +199,12 @@ defmodule Snowpack do
Options are passed to `DBConnection.prepare/3`, see it's documentation for
all available options.

## Additional Options

* `:parse_results` - Wether or not to do type parsing on the results. Requires
execution to be performed inside a transaction and an extra `DESCRIBE RESULT` to
get the types of the columns in the result. Only important for `SELECT` queries. Default true.

## Examples

iex> {:ok, query} = Snowpack.prepare(conn, "", "SELECT ? * ?")
Expand Down Expand Up @@ -208,6 +246,12 @@ defmodule Snowpack do
Options are passed to `DBConnection.prepare_execute/4`, see it's documentation for
all available options.

## Additional Options

* `:parse_results` - Wether or not to do type parsing on the results. Requires
execution to be performed inside a transaction and an extra `DESCRIBE RESULT` to
get the types of the columns in the result. Only important for `SELECT` queries. Default true.

## Examples

iex> {:ok, _query, %Snowpack.Result{rows: [row]}} = Snowpack.prepare_execute(conn, "", "SELECT ? * ?", [2, 3])
Expand Down
113 changes: 68 additions & 45 deletions lib/snowpack/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -191,54 +191,20 @@ defmodule Snowpack.Protocol do
# coveralls-ignore-stop

defp _query(query, params, opts, state) do
metadata = %{params: params, query: query.statement}
parse_results = Keyword.get(opts, :parse_results, true)

metadata = %{params: params, query: query.statement}
start_time = Telemetry.start(:query, metadata)

try do
result = type_aware_query(query, params, opts, state)

case result do
{:error, %Snowpack.Error{odbc_code: :connection_exception} = error} ->
metadata = Map.put(metadata, :error, error)
Telemetry.stop(:query, start_time, metadata)
{:disconnect, error, state}

{:error, error, _column_types} ->
metadata = Map.put(metadata, :error, error)
Telemetry.stop(:query, start_time, metadata)
{:error, error, state}

{:error, error} ->
metadata = Map.put(metadata, :error, error)
Telemetry.stop(:query, start_time, metadata)
{:error, error, state}

{:selected, columns, rows, %{column_types: column_types}} ->
typed_rows = TypeParser.parse_rows(column_types, columns, rows)
num_rows = Enum.count(typed_rows)

metadata = Map.merge(metadata, %{result: :selected, num_rows: num_rows})

Telemetry.stop(:query, start_time, metadata)

{:ok,
%Result{
columns: Enum.map(columns, &to_string(&1)),
rows: typed_rows,
num_rows: num_rows
}, state}

{:updated, :undefined, _col_types} ->
metadata = Map.merge(metadata, %{result: :updated, num_rows: 1})
Telemetry.stop(:query, start_time, metadata)
{:ok, %Result{num_rows: 0}, state}

{:updated, num_rows, _rows} ->
metadata = Map.merge(metadata, %{result: :updated, num_rows: num_rows})
Telemetry.stop(:query, start_time, metadata)
{:ok, %Result{num_rows: num_rows}, state}
end
{result, metadata} =
parse_results
|> maybe_query_with_type_parsing(query, params, opts, state)
|> _handle_query_result(metadata, state)

Telemetry.stop(:query, start_time, metadata)

result
catch
kind, error ->
Telemetry.exception(:query, start_time, kind, error, __STACKTRACE__, metadata)
Expand All @@ -247,7 +213,60 @@ defmodule Snowpack.Protocol do
end
end

defp type_aware_query(query, params, opts, state) do
defp _handle_query_result({:error, %Snowpack.Error{odbc_code: :connection_exception} = error}, metadata, state) do
metadata = Map.put(metadata, :error, error)
{{:disconnect, error, state}, metadata}
end

defp _handle_query_result({:error, error, _column_types}, metadata, state) do
metadata = Map.put(metadata, :error, error)
{{:error, error, state}, metadata}
end

defp _handle_query_result({:error, error}, metadata, state) do
metadata = Map.put(metadata, :error, error)
{{:error, error, state}, metadata}
end

defp _handle_query_result({:selected, columns, rows}, metadata, state) do
result_cols = Enum.map(columns, &to_string/1)
result_rows = Enum.map(rows, &Tuple.to_list/1)
num_rows = Enum.count(result_rows)
metadata = Map.merge(metadata, %{result: :selected, num_rows: num_rows})

{{:ok, %Result{columns: result_cols, rows: result_rows, num_rows: num_rows}, state}, metadata}
end

defp _handle_query_result({:selected, columns, rows, %{column_types: column_types}}, metadata, state) do
result_cols = Enum.map(columns, &to_string/1)
result_rows = TypeParser.parse_rows(column_types, columns, rows)
num_rows = Enum.count(result_rows)
metadata = Map.merge(metadata, %{result: :selected, num_rows: num_rows})

{{:ok, %Result{columns: result_cols, rows: result_rows, num_rows: num_rows}, state}, metadata}
end

defp _handle_query_result({:updated, :undefined, _query_id}, metadata, state) do
metadata = Map.merge(metadata, %{result: :updated, num_rows: 0})
{{:ok, %Result{num_rows: 0}, state}, metadata}
end

defp _handle_query_result({:updated, :undefined}, metadata, state) do
metadata = Map.merge(metadata, %{result: :updated, num_rows: 0})
{{:ok, %Result{num_rows: 0}, state}, metadata}
end

defp _handle_query_result({:updated, num_rows, _query_id}, metadata, state) do
metadata = Map.merge(metadata, %{result: :updated, num_rows: num_rows})
{{:ok, %Result{num_rows: num_rows}, state}, metadata}
end

defp _handle_query_result({:updated, num_rows}, metadata, state) do
metadata = Map.merge(metadata, %{result: :updated, num_rows: num_rows})
{{:ok, %Result{num_rows: num_rows}, state}, metadata}
end

defp maybe_query_with_type_parsing(true, query, params, opts, state) do
case TypeCache.get_column_types(query.statement) do
{:ok, column_types} ->
query_result = ODBC.query(state.pid, query.statement, params, opts, false)
Expand All @@ -263,6 +282,10 @@ defmodule Snowpack.Protocol do
end
end

defp maybe_query_with_type_parsing(false, query, params, opts, state) do
ODBC.query(state.pid, query.statement, params, opts, false)
end

defp execute_return(status, query, message, state, _opts) do
case status do
:ok -> {status, query, message, state}
Expand Down
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ defmodule Snowpack.MixProject do
{:telemetry, "~> 0.4 or ~> 1.0"},
{:jason, "~> 1.2"},
{:bless, "~> 1.2", only: [:dev, :test]},
{:excoveralls, "~> 0.14.4", only: [:dev, :test]},
{:excoveralls, "~> 0.14.5", only: [:dev, :test]},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev, :test, :docs], runtime: false},
{:ex_doc, ">= 0.0.0", only: [:docs], runtime: false},
{:mix_test_watch, "~> 1.0.2", only: [:test, :dev]},
{:mix_test_watch, "~> 1.1.0", only: [:test, :dev]},
{:mimic, "~> 1.7", only: [:dev, :test]},
{:vapor, "~> 0.10.0", only: [:dev, :test, :docs], runtime: false}
]
Expand All @@ -89,7 +89,7 @@ defmodule Snowpack.MixProject do
defp bless_suite do
[
compile: ["--warnings-as-errors", "--force"],
format: ["--check-formatted"],
format: [],
credo: ["--strict"],
"deps.unlock": ["--check-unused"],
coveralls: ["--exclude", "skip_ci"]
Expand Down
Loading