Skip to content

Commit

Permalink
Merge pull request #17 from pepsico-ecommerce/nulls_to_nil_config
Browse files Browse the repository at this point in the history
Add map_nulls_to_nil configuration
  • Loading branch information
zbarnes757 authored Jun 2, 2021
2 parents 711ec5d + 40aad89 commit 72a1d5b
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 14 deletions.
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

0 comments on commit 72a1d5b

Please sign in to comment.