Skip to content

Commit 9902aca

Browse files
committed
Documentation for custom JDBC operations
1 parent 2f1ee69 commit 9902aca

File tree

5 files changed

+154
-2
lines changed

5 files changed

+154
-2
lines changed

build.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,9 @@ lazy val docs = project
548548
"scalaVersion" -> scalaVersion.value,
549549
"canonical.base_url" -> "https://github.com/typelevel/doobie/"
550550
),
551+
// FIXME:
551552
mdocIn := baseDirectory.value / "src" / "main" / "mdoc",
553+
// mdocIn := baseDirectory.value / "src" / "main" / "mdoc" / "docs" / "19-Custom-JDBC-Operations.md",
552554
ghpagesRepository := (ThisBuild / baseDirectory).value / "doc_worktree",
553555
mdocExtraArguments ++= Seq("--no-link-hygiene"),
554556
Compile / paradox / sourceDirectory := mdocOut.value,

modules/core/src/test/scala/doobie/util/QuerySuite.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import cats.data.NonEmptyList
88
import cats.effect.IO
99
import cats.syntax.all.*
1010
import doobie.*
11+
import doobie.hi.connection.PreparedExecution
1112
import doobie.hi.resultset as IHRS
1213
import doobie.implicits.*
1314

modules/docs/src/main/mdoc/docs/12-Custom-Mappings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ case class LogEntry(msg: String, ex: Exception)
3333

3434
When we attempt to define a `Query0[LogEntry]` we get a type error similar to the one above.
3535

36-
```scala mdoc:fail
36+
```scala
3737
sql"SELECT message, detail FROM log".query[LogEntry]
3838
```
3939

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Custom JDBC Operations
2+
3+
Doobie aims to provide a nice API for typical database operations,
4+
but sometimes you need more - such as calling specific JDBC methods.
5+
In this page we list ways you can customize your JDBC query, from high level to lower level.
6+
7+
## Low-level JDBC API (`doobie.free.*` / `doobie.F*`)
8+
9+
In the low-level API, Doobie encodes the JDBC API as a [free monad](https://typelevel.org/cats/datatypes/freemonad.html)
10+
(hence `free` in the package name).
11+
Each standard JDBC type has its own free monad counterpart covering its operations
12+
13+
- `doobie.free.ConnectionIO`/`doobie.FC` represents operations over `java.sql.Connection`
14+
- `doobie.free.PreparedStatement`/`doobie.FPS` represents operations over `java.sql.PreparedStatement`
15+
- `doobie.free.ResultSetIO`/`doobie.FRS` represents operations over `java.sql.ResultSet`
16+
- ...and similarly for other JDBC types
17+
18+
For example, in `doobie.FC`/`doobie.free.connection` module you will find equivalents to the methods on `java.sql.Connection` class.
19+
(FC is an acronym where F == Free and C == Connection). Similarly, `doobie.FPS`/`doobie.free.preparedstatement` module contains equivalents to the methods on `java.sql.PreparedStatement` class.
20+
21+
Just like we need to be careful to close Connections and PreparedStatements with JDBC, **care must be taken when using
22+
doobie's low-level API to ensure resources like Connections and PreparedStatements are closed.**
23+
24+
### Useful low-level operations
25+
26+
#### `raw`: Access to the underlying JDBC object
27+
28+
Provides access to the underlying JDBC object.
29+
30+
```scala mdoc
31+
import doobie.FRS // alias for doobie.free.resultset
32+
33+
FRS.raw { resultSet: java.sql.ResultSet =>
34+
// ... do something with the ResultSet
35+
resultSet.getInt(1)
36+
}
37+
38+
```
39+
40+
#### `embed`: Convert between various `*IO`s
41+
42+
The `embed` method allows you to compose a "lower-level" JDBC operation
43+
chain into a "higher-level" one.
44+
45+
In the example below, we embed a `ResultSetIO` into a `PreparedStatementIO`, which is itself then embedded into a `ConnectionIO`
46+
47+
```scala mdoc:silent
48+
import doobie.{FPS, FC}
49+
import doobie.{ResultSetIO, ConnectionIO}
50+
import cats.effect.syntax.all._
51+
import doobie.syntax.all._
52+
53+
val readFirstRowCol1: ResultSetIO[String] = FRS.next.flatMap(_ => FRS.getString(1))
54+
55+
// Use bracket to ensure resources are closed
56+
val embedExample: ConnectionIO[String] = FC.prepareStatement("SELECT 1").bracket { preparedStatement =>
57+
FC.embed(
58+
preparedStatement,
59+
FPS.executeQuery.bracket { resultSet =>
60+
FPS.embed(resultSet, readFirstRowCol1)
61+
}(resultSet => FPS.embed(resultSet, FRS.close)))
62+
}(ps => FC.embed(ps, FPS.close))
63+
```
64+
65+
#### Other operations
66+
67+
You will find many familiar methods from `cats.effect.IO`
68+
with the same semantics, such as:
69+
70+
- `pure`: Lift a pure value
71+
- `raiseError`: Raise an error
72+
- `delay`: Suspend a computation
73+
74+
These are eventually interpreted into equivalent `cats.effect.IO` operations.
75+
76+
## High-level API (`doobie.hi.*` / `doobie.H*`)
77+
78+
The `doobie.hi.*` modules provide high-level APIs which handle concerns like
79+
closing Connections and logging for you. The high-level module builds upont the low-level API (`doobie.free.*`).
80+
81+
In the example below, we use `doobie.HC.executeWithResultSet`
82+
to execute a query and obtain the results. There is no need explicitly close the Connection, PreparedStatement or ResultSet
83+
because it's handled by `HC.executeWithResultSet` already.
84+
85+
```scala mdoc:silent
86+
import cats.effect.IO
87+
import cats.effect.unsafe.implicits.global // To allow .unsafeRunSync
88+
import doobie.Transactor
89+
90+
// Create the transactor
91+
val xa: Transactor[IO] = Transactor.fromDriverManager[IO](
92+
driver = "org.postgresql.Driver",
93+
url = "jdbc:postgresql:world",
94+
user = "postgres",
95+
password = "password",
96+
logHandler = None
97+
)
98+
```
99+
100+
```scala mdoc:silent
101+
import doobie.HC // High-level API over java.sql.Connection
102+
import doobie.HRS // High-level API over java.sql.ResultSet
103+
import doobie.ConnectionIO
104+
import doobie.util.log.{LoggingInfo, Parameters}
105+
import cats.effect.unsafe.implicits.global
106+
107+
val sql = "SELECT * FROM (VALUES (1, '1'), (2, '2'))"
108+
val program: ConnectionIO[List[(Int, String)]] = HC.executeWithResultSet(
109+
create = FC.prepareStatement(sql),
110+
prep = FPS.unit,
111+
exec = FPS.executeQuery,
112+
process = HRS.list[(Int, String)],
113+
loggingInfo = LoggingInfo(sql, Parameters.NonBatch(List.empty), label = doobie.util.unlabeled)
114+
)
115+
```
116+
117+
```scala mdoc
118+
program.transact(xa).unsafeRunSync()
119+
```
120+
121+
### Useful high-level APIs in `doobie.hi.connection`/`doobie.HC`
122+
123+
- `executeWithResultSet`: Create and execute a `PreparedStatement` and then process the ResultSet
124+
- `executeWithoutResultSet`: Create and execute a `PreparedStatement` which immediately returns the result without reading from a `ResultSet`
125+
(e.g. for updates since the updated row count does not require reading from a `ResultSet`)
126+
- `stream`: Execute a `PreparedStatement` query and provide rows in chunks, streamed via `fs2.Stream`
127+
128+
## Tweaking `Query`/`Update` execution with `*AlteringExecution` methods
129+
130+
If you just need to do a small "tweak" to your typical `Query`/`Update` execution steps,
131+
you can use methods like `toAlteringExecution`/`toMapAlteringExecution` to customize the steps.
132+
133+
```scala mdoc
134+
import cats.syntax.all._
135+
import doobie.hi.connection.PreparedExecution
136+
137+
fr"select name from country order by code limit 10"
138+
.query[String]
139+
.toAlteringExecution[List, List[String]] { (steps: PreparedExecution[List[String]]) =>
140+
steps.copy(
141+
process = FRS.setFetchSize(5) *> steps.process
142+
)
143+
}
144+
.transact(xa)
145+
.unsafeRunSync()
146+
147+
```
148+

modules/docs/src/main/mdoc/docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* [Logging](10-Logging.md)
1212
* [Arrays](11-Arrays.md)
1313
* [Custom-Mappings](12-Custom-Mappings.md)
14+
* [Custom-JDBC-Operations](19-Custom-JDBC-Operations.md)
1415
* [Unit-Testing](13-Unit-Testing.md)
1516
* [Managing-Connections](14-Managing-Connections.md)
1617
* [Extensions-PostgreSQL](15-Extensions-PostgreSQL.md)
@@ -21,4 +22,4 @@
2122

2223
# Book of Doobie
2324

24-
@@toc { depth=1 }
25+
@@toc { depth=1 }

0 commit comments

Comments
 (0)