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

Add map_nulls_to_nil configuration #17

Merged
merged 3 commits into from
Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.2] - 2021-06-02

### Added
- Added `map_nulls_to_nil?` variable to connection configuration to allow conversion of `:null` values to `:nil` in snowflake query response

## [0.3.1] - 2021-03-10

### Fixed
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ config :snowflex,
driver: "/path/to/my/ODBC/driver" # defaults to "/usr/lib/snowflake/odbc/lib/libSnowflake.so"
```

Connection pools are not automatically started for you. You will need to define and establish each connection pool in your application module.
Connection pools are not automatically started for you. You will need to define and establish each connection pool in your application module. configuration values related to connection timeouts and the mapping of `:null` query values can be set here.

First, create a module to hold your connection information:

```elixir
defmodule MyApp.SnowflakeConnection do
use Snowflex.Connection,
otp_app: :my_app
otp_app: :my_app,
timeout: :timer.minutes(20),
map_nulls_to_nil?: true
end
```

Expand Down Expand Up @@ -53,6 +55,10 @@ config :my_app, MyApp.SnowflakeConnection,
]
```

The odbc driver will, by default, return `:null` for empty values returned from snowflake queries.
This will be converted to `nil` by default by Snowflex. A configuration value `map_nulls_to_nil?`
can be set to `false` if you do not desire this behavior.

Then, in your application module, you would start your connection:

```elixir
Expand Down
29 changes: 20 additions & 9 deletions lib/snowflex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,34 @@ defmodule Snowflex do

@type query_param :: {odbc_data_type(), [value()]}
@type sql_data :: list(%{optional(String.t()) => String.t()})
@type query_opts :: [timeout: timeout(), map_null_to_nil?: boolean()]

@spec sql_query(atom(), String.t(), timeout()) ::
@spec sql_query(atom(), String.t(), query_opts()) ::
sql_data() | {:error, term()}
def sql_query(pool_name, query, timeout) do
def sql_query(pool_name, query, opts) do
timeout = Keyword.get(opts, :timeout)

case :poolboy.transaction(
pool_name,
&Worker.sql_query(&1, query, timeout),
timeout
) do
{:ok, results} -> process_results(results)
{:ok, results} -> process_results(results, opts)
err -> err
end
end

@spec param_query(atom(), String.t(), list(query_param()), timeout()) ::
@spec param_query(atom(), String.t(), list(query_param()), query_opts()) ::
sql_data() | {:error, term()}
def param_query(pool_name, query, params \\ [], timeout) do
def param_query(pool_name, query, params, opts) do
timeout = Keyword.get(opts, :timeout)

case :poolboy.transaction(
pool_name,
&Worker.param_query(&1, query, params, timeout),
timeout
) do
{:ok, results} -> process_results(results)
{:ok, results} -> process_results(results, opts)
err -> err
end
end
Expand All @@ -70,11 +75,13 @@ defmodule Snowflex do

# Helpers

defp process_results(data) when is_list(data) do
Enum.map(data, &process_results(&1))
defp process_results(data, opts) when is_list(data) do
Enum.map(data, &process_results(&1, opts))
end

defp process_results({:selected, headers, rows}) do
defp process_results({:selected, headers, rows}, opts) do
map_nulls_to_nil? = Keyword.get(opts, :map_nulls_to_nil?)

bin_headers =
headers
|> Enum.map(fn header -> header |> to_string() |> String.downcase() end)
Expand All @@ -86,6 +93,7 @@ defmodule Snowflex do
row
|> elem(index)
|> to_string_if_charlist()
|> map_null_to_nil(map_nulls_to_nil?)

Map.put(map, col, data)
end)
Expand All @@ -95,6 +103,9 @@ defmodule Snowflex do
defp to_string_if_charlist(data) when is_list(data), do: to_string(data)
defp to_string_if_charlist(data), do: data

defp map_null_to_nil(:null, true), do: nil
defp map_null_to_nil(data, _), do: data

defp cast_row(row, schema) do
schema
|> struct()
Expand Down
10 changes: 7 additions & 3 deletions lib/snowflex/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,21 @@ defmodule Snowflex.Connection do
# setup compile time config
otp_app = Keyword.fetch!(opts, :otp_app)
timeout = Keyword.get(opts, :timeout, :timer.seconds(60))
map_nulls_to_nil? = Keyword.get(opts, :map_nulls_to_nil?, false)
keep_alive? = Keyword.get(opts, :keep_alive?, false)

@otp_app otp_app
@name __MODULE__
@timeout timeout
@default_size [
max: 10,
min: 5
]
@keep_alive? keep_alive?
@heartbeat_interval :timer.hours(3)
@query_opts [
timeout: timeout,
map_nulls_to_nil?: map_nulls_to_nil?
]

def child_spec(_) do
config = Application.get_env(@otp_app, __MODULE__, [])
Expand Down Expand Up @@ -130,12 +134,12 @@ defmodule Snowflex.Connection do

@impl Snowflex.Connection
def execute(query) when is_binary(query) do
Snowflex.sql_query(@name, query, @timeout)
Snowflex.sql_query(@name, query, @query_opts)
end

@impl Snowflex.Connection
def execute(query, params) when is_binary(query) and is_list(params) do
Snowflex.param_query(@name, query, params, @timeout)
Snowflex.param_query(@name, query, params, @query_opts)
end
end
end
Expand Down