Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
sql_kit-*.tar

# SQLite / DuckDB
*.db
*.duckdb

# General
.DS_Store
__MACOSX/
Expand Down
51 changes: 50 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
# Changelog

## Unreleased
## 0.2.0

### Breaking Changes

- **API aligned with Ecto Repo conventions** - This is a breaking change that simplifies the API to match [`Ecto.Repo.all/2`](https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2) and [`Ecto.Repo.one/2`](https://hexdocs.pm/ecto/Ecto.Repo.html#c:one/2) semantics.

**`query_all/4` now returns list directly** (was `{:ok, list}`)
```elixir
# Before
{:ok, users} = SqlKit.query_all(Repo, "SELECT * FROM users")

# After
users = SqlKit.query_all(Repo, "SELECT * FROM users")
```

**`query_all!/4` removed** (use `query_all/4` instead)
```elixir
# Before
users = SqlKit.query_all!(Repo, "SELECT * FROM users")

# After
users = SqlKit.query_all(Repo, "SELECT * FROM users")
```

**`query_one/4` now returns result or nil directly** (was `{:ok, result | nil}`)
```elixir
# Before
{:ok, user} = SqlKit.query_one(Repo, "SELECT * FROM users WHERE id = $1", [1])
{:ok, nil} = SqlKit.query_one(Repo, "SELECT * FROM users WHERE id = $1", [999])

# After
user = SqlKit.query_one(Repo, "SELECT * FROM users WHERE id = $1", [1])
nil = SqlKit.query_one(Repo, "SELECT * FROM users WHERE id = $1", [999])
```

**`query_one/4` now raises on multiple results** (was `{:error, MultipleResultsError}`)
```elixir
# Before
{:error, %SqlKit.MultipleResultsError{}} =
SqlKit.query_one(Repo, "SELECT * FROM users")

# After
# Raises SqlKit.MultipleResultsError
SqlKit.query_one(Repo, "SELECT * FROM users")
```

**File-based API follows same changes:**
- `MyModule.query_all/3` returns list directly
- `MyModule.query_all!/3` removed
- `MyModule.query_one/3` returns result or nil, raises on multiple

### Added

Expand Down
32 changes: 16 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,20 @@ test/

```elixir
# Execute SQL strings directly with any Ecto repo
SqlKit.query_all!(MyApp.Repo, "SELECT * FROM users WHERE age > $1", [21])
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])
# => %{id: 1, name: "Alice"}

SqlKit.query_all!(MyApp.Repo, "SELECT * FROM users", [], as: User)
SqlKit.query_all(MyApp.Repo, "SELECT * FROM users", [], as: User)
# => [%User{id: 1, name: "Alice"}, ...]

# Non-bang variants
SqlKit.query_all(repo, sql, params, opts) # => {:ok, results} | {:error, reason}
SqlKit.query_one(repo, sql, params, opts) # => {:ok, result | nil} | {:error, reason}
# query_all returns list directly, raises on errors (matches Ecto.Repo.all/2)
SqlKit.query_all(repo, sql, params, opts) # => [results]

# query_one returns result or nil, raises on errors/multiple (matches Ecto.Repo.one/2)
SqlKit.query_one(repo, sql, params, opts) # => result | nil

# Aliases for query_one
SqlKit.query!(repo, sql, params, opts)
Expand Down Expand Up @@ -95,14 +97,12 @@ end

# Usage (same API for both)
MyApp.Reports.SQL.query!("stats.sql", [id]) # single row (alias for query_one!)
MyApp.Reports.SQL.query_one!("stats.sql", [id]) # single row
MyApp.Reports.SQL.query_all!("activity.sql", [id], as: Activity) # all rows as structs
MyApp.Reports.SQL.query_one!("stats.sql", [id]) # single row (raises if no results)
MyApp.Reports.SQL.query_one("stats.sql", [id]) # single row or nil
MyApp.Reports.SQL.query_all("activity.sql", [id], as: Activity) # all rows as structs
MyApp.Reports.SQL.load!("stats.sql") # just get SQL string

# Non-bang variants return {:ok, result} | {:error, reason}
MyApp.Reports.SQL.query("stats.sql", [id])
MyApp.Reports.SQL.query_one("stats.sql", [id])
MyApp.Reports.SQL.query_all("activity.sql", [id])
# Non-bang load returns {:ok, result} | {:error, reason}
MyApp.Reports.SQL.load("stats.sql")
```

Expand Down Expand Up @@ -136,7 +136,7 @@ DuckDB is unique - it's not an Ecto adapter but a direct NIF driver. SqlKit prov
```elixir
# Direct connection (BYO)
{:ok, conn} = SqlKit.DuckDB.connect(":memory:")
SqlKit.query_all!(conn, "SELECT * FROM users", [])
SqlKit.query_all(conn, "SELECT * FROM users", [])
SqlKit.DuckDB.disconnect(conn)

# Pooled connection (recommended for production)
Expand All @@ -149,7 +149,7 @@ SqlKit.DuckDB.disconnect(conn)

# Then use the pool:
{:ok, pool} = SqlKit.DuckDB.Pool.start_link(name: MyPool, database: ":memory:")
SqlKit.query_all!(pool, "SELECT * FROM events", [])
SqlKit.query_all(pool, "SELECT * FROM events", [])

# File-based SQL with DuckDB (use :backend instead of :repo)
defmodule MyApp.Analytics.SQL do
Expand All @@ -160,7 +160,7 @@ defmodule MyApp.Analytics.SQL do
files: ["daily_summary.sql"]
end

MyApp.Analytics.SQL.query_all!("daily_summary.sql", [~D[2024-01-01]])
MyApp.Analytics.SQL.query_all("daily_summary.sql", [~D[2024-01-01]])
```

Key differences from Ecto-based databases:
Expand All @@ -177,8 +177,8 @@ Key differences from Ecto-based databases:

```elixir
# Caching is enabled by default
SqlKit.query_all!(pool, "SELECT * FROM events WHERE id = $1", [1])
SqlKit.query_all!(pool, "SELECT * FROM events WHERE id = $1", [2]) # uses cached statement
SqlKit.query_all(pool, "SELECT * FROM events WHERE id = $1", [1])
SqlKit.query_all(pool, "SELECT * FROM events WHERE id = $1", [2]) # uses cached statement

# Disable caching for specific queries
SqlKit.DuckDB.Pool.query!(pool, sql, params, cache: false)
Expand Down
Loading