SqlKit (sql_kit v0.2.0)
View SourceExecute raw SQL in strings or .sql files, get maps and structs back.
SqlKit provides two ways to execute SQL with automatic result transformation:
Direct SQL Execution
Execute SQL strings directly with any Ecto repo:
SqlKit.query_all(MyApp.Repo, "SELECT * FROM users WHERE age > $1", [21])
# => [%{id: 1, name: "Alice", age: 30}, ...]
SqlKit.query_one!(MyApp.Repo, "SELECT * FROM users WHERE id = $1", [1], as: User)
# => %User{id: 1, name: "Alice"}File-Based SQL
For larger queries, keep SQL in dedicated files with compile-time embedding:
defmodule MyApp.Reports.SQL do
use SqlKit,
otp_app: :my_app,
repo: MyApp.Repo,
dirname: "reports",
files: ["stats.sql"]
end
MyApp.Reports.SQL.query_one!("stats.sql", [report_id])DuckDB File-Based SQL
For DuckDB, use the :backend option instead of :repo. The pool must be
started in your supervision tree with the database configuration:
# In your application.ex supervision tree:
children = [
{SqlKit.DuckDB.Pool,
name: MyApp.AnalyticsPool,
database: "priv/analytics.duckdb",
pool_size: 4}
]
# Then define your SQL module:
defmodule MyApp.Analytics.SQL do
use SqlKit,
otp_app: :my_app,
backend: {:duckdb, pool: MyApp.AnalyticsPool},
dirname: "analytics",
files: ["daily_summary.sql"]
end
MyApp.Analytics.SQL.query_all("daily_summary.sql", [~D[2024-01-01]])Supported Databases
Any Ecto adapter returning a result map containing rows and columns should work. The test suite covers the following adapters:
| Database | Ecto Adapter | Driver |
|---|---|---|
| PostgreSQL | Ecto.Adapters.Postgres | Postgrex |
| SQLite | Ecto.Adapters.SQLite3 | Exqlite |
| MySQL | Ecto.Adapters.MyXQL | MyXQL |
| MariaDB | Ecto.Adapters.MyXQL | MyXQL |
| SQL Server | Ecto.Adapters.Tds | Tds |
| ClickHouse | Ecto.Adapters.ClickHouse | Ch |
Configuration
# config/config.exs
config :my_app, SqlKit,
root_sql_dir: "priv/repo/sql" # default
# config/dev.exs and config/test.exs
config :my_app, SqlKit,
load_sql: :dynamic # read from disk at runtime
# config/prod.exs (or rely on default)
config :my_app, SqlKit,
load_sql: :compiled # use compile-time embedded SQLOptions
:otp_app(required) - Your application name:repo- The Ecto repo module to use for queries (required unless:backendis specified):backend- Alternative to:repofor non-Ecto databases. Currently supports:{:duckdb, pool: PoolName}- Use a DuckDB connection pool
:dirname(required) - Subdirectory within root_sql_dir for this module's SQL files:files(required) - List of SQL filenames to load
Note: You must specify either :repo or :backend, but not both.
Summary
Functions
Alias for query_one/4. See query_one/4 documentation.
Alias for query_one!/4. See query_one!/4 documentation.
Executes a SQL query and returns all rows as a list of maps or structs.
Executes a SQL query and returns one row as a map or struct, or nil if no results.
Executes a SQL query and returns exactly one row as a map or struct.
Types
@type backend() :: Ecto.Repo.t() | struct() | atom()
A backend for executing SQL queries.
Can be:
- An Ecto repo module (e.g.,
MyApp.Repo) - A
SqlKit.DuckDB.Connectionstruct (for direct DuckDB connections) - A
SqlKit.DuckDB.Poolname (atom) for pooled DuckDB connections
Functions
Alias for query_one/4. See query_one/4 documentation.
Alias for query_one!/4. See query_one!/4 documentation.
Executes a SQL query and returns all rows as a list of maps or structs.
Raises on query execution errors. This matches Ecto.Repo.all/2 semantics.
Backend
The first argument can be:
- An Ecto repo module (e.g.,
MyApp.Repo) - A
SqlKit.DuckDB.Connectionstruct - A
SqlKit.DuckDB.Poolname (atom)
Options
:as- Struct module to cast results into:unsafe_atoms- Iftrue, usesString.to_atom/1instead ofString.to_existing_atom/1for column names. Default:false
Examples
SqlKit.query_all(MyApp.Repo, "SELECT * FROM users")
# => [%{id: 1, name: "Alice"}, %{id: 2, name: "Bob"}]
SqlKit.query_all(MyApp.Repo, "SELECT * FROM users WHERE age > $1", [21])
# => [%{id: 1, name: "Alice", age: 30}]
SqlKit.query_all(MyApp.Repo, "SELECT * FROM users", [], as: User)
# => [%User{id: 1, name: "Alice"}, %User{id: 2, name: "Bob"}]
# With DuckDB connection
{:ok, conn} = SqlKit.DuckDB.connect(":memory:")
SqlKit.query_all(conn, "SELECT 1 as num", [])
# => [%{num: 1}]
# With DuckDB pool
SqlKit.query_all(MyApp.DuckDBPool, "SELECT * FROM events", [])
Executes a SQL query and returns one row as a map or struct, or nil if no results.
Returns the result directly, or nil on no results.
Raises SqlKit.MultipleResultsError if more than one row is returned.
Raises on query execution errors. This matches Ecto.Repo.one/2 semantics.
See query_all/4 for backend documentation.
Options
:as- Struct module to cast result into:unsafe_atoms- Iftrue, usesString.to_atom/1instead ofString.to_existing_atom/1for column names. Default:false:query_name- Custom identifier for exceptions (defaults to truncated SQL)
Examples
SqlKit.query_one(MyApp.Repo, "SELECT * FROM users WHERE id = $1", [1])
# => %{id: 1, name: "Alice"}
SqlKit.query_one(MyApp.Repo, "SELECT * FROM users WHERE id = $1", [999])
# => nil
Executes a SQL query and returns exactly one row as a map or struct.
Raises SqlKit.NoResultsError if no rows are returned.
Raises SqlKit.MultipleResultsError if more than one row is returned.
See query_all/4 for backend documentation.
Options
:as- Struct module to cast result into:unsafe_atoms- Iftrue, usesString.to_atom/1instead ofString.to_existing_atom/1for column names. Default:false:query_name- Custom identifier for exceptions (defaults to truncated SQL)
Examples
SqlKit.query_one!(MyApp.Repo, "SELECT * FROM users WHERE id = $1", [1])
# => %{id: 1, name: "Alice"}
SqlKit.query_one!(MyApp.Repo, "SELECT * FROM users WHERE id = $1", [1], as: User)
# => %User{id: 1, name: "Alice"}