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

# SQLite / DuckDB
*.db
*.db-shm
*.db-wal
*.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