Skip to content

Commit e571c8c

Browse files
authored
Merge pull request #16 from softinio/more-duckdb
Expand DuckDB type support with new ParameterBinders and BatchBinders
2 parents 41632aa + d314994 commit e571c8c

File tree

11 files changed

+1010
-24
lines changed

11 files changed

+1010
-24
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ Add duck4s to your `build.sbt`:
3838

3939
```scala
4040
// Core library
41-
libraryDependencies += "com.softinio" %% "duck4s" % "0.1.3"
41+
libraryDependencies += "com.softinio" %% "duck4s" % "0.1.4"
4242

4343
// Optional: cats-effect integration (includes fs2)
44-
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.3"
44+
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.4"
4545
```
4646

4747
#### Mill
@@ -51,13 +51,13 @@ Add duck4s to your `build.mill`:
5151
```scala
5252
// Core library
5353
def ivyDeps = Agg(
54-
ivy"com.softinio::duck4s::0.1.3"
54+
ivy"com.softinio::duck4s::0.1.4"
5555
)
5656

5757
// Optional: cats-effect integration (includes fs2)
5858
def ivyDeps = Agg(
59-
ivy"com.softinio::duck4s::0.1.3",
60-
ivy"com.softinio::duck4s-cats-effect::0.1.3"
59+
ivy"com.softinio::duck4s::0.1.4",
60+
ivy"com.softinio::duck4s-cats-effect::0.1.4"
6161
)
6262
```
6363

duck4s/docs/_docs/cats-effect.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ The `duck4s-cats-effect` module provides a purely functional interface for DuckD
1111
### SBT
1212

1313
```sbt
14-
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.3"
14+
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.4"
1515
```
1616

1717
### Mill
1818

1919
```scala sc:nocompile
2020
def ivyDeps = Agg(
21-
ivy"com.softinio::duck4s-cats-effect::0.1.3"
21+
ivy"com.softinio::duck4s-cats-effect::0.1.4"
2222
)
2323
```
2424

duck4s/docs/_docs/getting-started.md

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ Add duck4s to your `build.sbt`:
1414

1515
```sbt
1616
// Core library
17-
libraryDependencies += "com.softinio" %% "duck4s" % "0.1.3"
17+
libraryDependencies += "com.softinio" %% "duck4s" % "0.1.4"
1818

1919
// Optional: cats-effect integration (includes fs2)
20-
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.3"
20+
libraryDependencies += "com.softinio" %% "duck4s-cats-effect" % "0.1.4"
2121
```
2222

2323
### Mill
@@ -27,13 +27,13 @@ Add duck4s to your `build.mill`:
2727
```scala sc:nocompile
2828
// Core library
2929
def ivyDeps = Agg(
30-
ivy"com.softinio::duck4s::0.1.3"
30+
ivy"com.softinio::duck4s::0.1.4"
3131
)
3232

3333
// Optional: cats-effect integration (includes fs2)
3434
def ivyDeps = Agg(
35-
ivy"com.softinio::duck4s::0.1.3",
36-
ivy"com.softinio::duck4s-cats-effect::0.1.3"
35+
ivy"com.softinio::duck4s::0.1.4",
36+
ivy"com.softinio::duck4s-cats-effect::0.1.4"
3737
)
3838
```
3939

@@ -171,6 +171,129 @@ result match
171171
println(s"Batch operation failed: $error")
172172
```
173173

174+
### Supported Parameter Types
175+
176+
Duck4s provides first-class support for all common DuckDB column types. The following types can be used with prepared statements (`setXxx` methods) and batch operations (`addBatch` tuples) without any manual conversion:
177+
178+
| Scala / Java type | Setter method | DuckDB column type |
179+
|---|---|---|
180+
| `Int` | `setInt` | `INTEGER` |
181+
| `Long` | `setLong` | `BIGINT` |
182+
| `Double` | `setDouble` | `DOUBLE` |
183+
| `Float` | `setFloat` | `FLOAT` |
184+
| `Boolean` | `setBoolean` | `BOOLEAN` |
185+
| `String` | `setString` | `VARCHAR` |
186+
| `BigDecimal` | `setBigDecimal` | `DECIMAL` |
187+
| `java.sql.Date` | `setDate` | `DATE` |
188+
| `java.sql.Timestamp` | `setTimestamp` | `TIMESTAMP` |
189+
| `java.sql.Types.*` | `setNull` | any (NULL) |
190+
| `java.util.UUID` | `setObject` | `UUID` |
191+
| `java.time.LocalDate` | `setObject` | `DATE` |
192+
| `java.time.LocalDateTime` | `setObject` | `TIMESTAMP` |
193+
| `java.time.OffsetDateTime` | `setObject` | `TIMESTAMPTZ` |
194+
| `Array[Byte]` | `setBytes` | `BLOB` |
195+
| `Option[T]` | (any of the above) | nullable variant |
196+
197+
The same types are available when reading results from a `DuckDBResultSet` via the corresponding `getXxx` methods. For BLOB columns, use `getBytes(columnLabel)` which internally wraps the DuckDB-specific `getBlob` implementation.
198+
199+
```scala sc-compile-with:Imp.scala
200+
val result = DuckDBConnection.withConnection() { conn =>
201+
for
202+
_ <- conn.executeUpdate("""
203+
CREATE TABLE events (
204+
id INTEGER,
205+
name VARCHAR,
206+
score FLOAT,
207+
price DECIMAL(10,2),
208+
recorded DATE,
209+
created TIMESTAMP,
210+
uid UUID,
211+
payload BLOB
212+
)
213+
""")
214+
215+
ts = java.sql.Timestamp.valueOf("2024-06-15 10:30:00")
216+
date = java.sql.Date.valueOf("2024-06-15")
217+
uuid = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000")
218+
bytes = "hello".getBytes("UTF-8")
219+
220+
_ <- conn.withPreparedStatement("INSERT INTO events VALUES (?, ?, ?, ?, ?, ?, ?, ?)") { stmt =>
221+
for
222+
_ <- stmt.setInt(1, 1)
223+
_ <- stmt.setString(2, "launch")
224+
_ <- stmt.setFloat(3, 9.5f)
225+
_ <- stmt.setBigDecimal(4, BigDecimal("19.99"))
226+
_ <- stmt.setDate(5, date)
227+
_ <- stmt.setTimestamp(6, ts)
228+
_ <- stmt.setObject(7, uuid)
229+
_ <- stmt.setBytes(8, bytes)
230+
count <- stmt.executeUpdate()
231+
yield count
232+
}
233+
234+
rs <- conn.executeQuery("SELECT * FROM events WHERE id = 1")
235+
yield
236+
assert(rs.next())
237+
val retrievedUuid = rs.getObject("uid", classOf[java.util.UUID])
238+
val retrievedBytes = rs.getBytes("payload")
239+
rs.close()
240+
}
241+
```
242+
243+
#### Batch operations with tuples
244+
245+
`addBatch` accepts tuples of up to 6 elements, with any combination of the supported types:
246+
247+
```scala sc-compile-with:Imp.scala
248+
val result = DuckDBConnection.withConnection() { conn =>
249+
for
250+
_ <- conn.executeUpdate("CREATE TABLE readings (id INTEGER, ts TIMESTAMP, uid UUID, val FLOAT, dec DECIMAL(10,2), data BLOB)")
251+
252+
batchResult <- conn.withBatch("INSERT INTO readings VALUES (?, ?, ?, ?, ?, ?)") { batch =>
253+
val ts1 = java.sql.Timestamp.valueOf("2024-01-01 00:00:00")
254+
val uuid1 = java.util.UUID.randomUUID()
255+
for
256+
_ <- batch.addBatch((1, ts1, uuid1, 1.5f, BigDecimal("9.99"), "a".getBytes("UTF-8")))
257+
result <- batch.executeBatch()
258+
yield result
259+
}
260+
yield batchResult
261+
}
262+
```
263+
264+
#### Option support for nullable columns
265+
266+
Wrap any supported type in `Option` to represent nullable columns — `Some(value)` binds the value and `None` inserts SQL NULL:
267+
268+
```scala sc-compile-with:Imp.scala
269+
val result = DuckDBConnection.withConnection() { conn =>
270+
for
271+
_ <- conn.executeUpdate("CREATE TABLE contacts (id INTEGER, email VARCHAR)")
272+
batchResult <- conn.withBatch("INSERT INTO contacts VALUES (?, ?)") { batch =>
273+
for
274+
_ <- batch.addBatch((1, Option("alice@example.com")))
275+
_ <- batch.addBatch((2, Option.empty[String]))
276+
r <- batch.executeBatch()
277+
yield r
278+
}
279+
yield batchResult
280+
}
281+
```
282+
283+
#### Custom ParameterBinder
284+
285+
For types not covered by the built-in binders, implement the `ParameterBinder` type class:
286+
287+
```scala sc-compile-with:Imp.scala
288+
import com.softinio.duck4s.algebra.{ParameterBinder, DuckDBPreparedStatement, DuckDBError}
289+
290+
case class UserId(value: Long)
291+
292+
given ParameterBinder[UserId] with
293+
def bind(stmt: DuckDBPreparedStatement, index: Int, value: UserId): Either[DuckDBError, Unit] =
294+
stmt.setLong(index, value.value).map(_ => ())
295+
```
296+
174297
### Transactions
175298

176299
Use transactions for atomic operations:

0 commit comments

Comments
 (0)