or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

database-io.mddatabase-profiles.mdindex.mdplain-sql.mdqueries.mdtable-definitions.mdtype-mappings.md

plain-sql.mddocs/

0

# Plain SQL

1

2

Direct SQL query execution with parameter interpolation and result mapping for when you need to drop down to raw SQL.

3

4

## Capabilities

5

6

### SQL Interpolation

7

8

Execute parameterized SQL queries using string interpolation with automatic parameter binding.

9

10

```scala { .api }

11

/**

12

* SQL string interpolation context

13

*/

14

implicit class ActionBasedSQLInterpolation(val sc: StringContext) {

15

/** Create a SQL query with interpolated parameters */

16

def sql(args: Any*): SQLActionBuilder

17

18

/** Create a SQL update/DDL statement with interpolated parameters */

19

def sqlu(args: Any*): SQLActionBuilder

20

}

21

22

/**

23

* Builder for SQL actions with parameter binding

24

*/

25

trait SQLActionBuilder {

26

/** Execute as a query returning typed results */

27

def as[R](implicit getResult: GetResult[R]): StreamingDBIO[Vector[R], R]

28

29

/** Execute as an update/insert/delete returning affected row count */

30

def asUpdate: DBIO[Int]

31

32

/** Execute as DDL statement */

33

def asDDL: DBIO[Unit]

34

}

35

```

36

37

**Usage Examples:**

38

39

```scala

40

import slick.jdbc.PostgresProfile.api._

41

42

// Simple parameterized query

43

val name = "Latte"

44

val maxPrice = 3.50

45

val query = sql"SELECT * FROM coffees WHERE name = $name AND price <= $maxPrice".as[Coffee]

46

47

// SQL update with parameters

48

val updatePrice = sqlu"UPDATE coffees SET price = price * 1.1 WHERE category = 'premium'"

49

50

// SQL insert

51

val newCoffeeId = 123

52

val coffeeName = "New Blend"

53

val price = 4.25

54

val insert = sqlu"INSERT INTO coffees (id, name, price) VALUES ($newCoffeeId, $coffeeName, $price)"

55

56

// DDL statements

57

val createTable = sqlu"""

58

CREATE TABLE temp_analysis (

59

id SERIAL PRIMARY KEY,

60

coffee_name VARCHAR(100),

61

analysis_date DATE

62

)

63

"""

64

65

// Complex query with joins

66

val userId = 42

67

val complexQuery = sql"""

68

SELECT u.name, c.name, o.quantity, o.order_date

69

FROM users u

70

JOIN orders o ON u.id = o.user_id

71

JOIN coffees c ON o.coffee_id = c.id

72

WHERE u.id = $userId

73

ORDER BY o.order_date DESC

74

""".as[(String, String, Int, Date)]

75

```

76

77

### Result Mapping

78

79

Map SQL result sets to Scala types using GetResult type class.

80

81

```scala { .api }

82

/**

83

* Type class for mapping SQL result sets to Scala types

84

*/

85

trait GetResult[T] {

86

/** Extract a value of type T from the current position in a PositionedResult */

87

def apply(pr: PositionedResult): T

88

}

89

90

/**

91

* Positioned result set reader

92

*/

93

trait PositionedResult {

94

/** Read next column as specific type */

95

def nextBoolean(): Boolean

96

def nextByte(): Byte

97

def nextShort(): Short

98

def nextInt(): Int

99

def nextLong(): Long

100

def nextFloat(): Float

101

def nextDouble(): Double

102

def nextString(): String

103

def nextDate(): Date

104

def nextTime(): Time

105

def nextTimestamp(): Timestamp

106

def nextBigDecimal(): BigDecimal

107

def nextBytes(): Array[Byte]

108

def nextBlob(): Blob

109

def nextClob(): Clob

110

111

/** Read optional values */

112

def nextBooleanOption(): Option[Boolean]

113

def nextIntOption(): Option[Int]

114

def nextStringOption(): Option[String]

115

// ... other optional types

116

117

/** Skip the next column */

118

def skip(): Unit

119

}

120

121

object GetResult {

122

/** Implicit GetResult instances for basic types */

123

implicit val GetBoolean: GetResult[Boolean]

124

implicit val GetInt: GetResult[Int]

125

implicit val GetLong: GetResult[Long]

126

implicit val GetString: GetResult[String]

127

implicit val GetDate: GetResult[Date]

128

implicit val GetTimestamp: GetResult[Timestamp]

129

implicit val GetBigDecimal: GetResult[BigDecimal]

130

131

/** GetResult for Option types */

132

implicit def GetOption[T](implicit base: GetResult[T]): GetResult[Option[T]]

133

134

/** GetResult for tuples */

135

implicit def GetTuple2[T1, T2](implicit g1: GetResult[T1], g2: GetResult[T2]): GetResult[(T1, T2)]

136

implicit def GetTuple3[T1, T2, T3](implicit g1: GetResult[T1], g2: GetResult[T2], g3: GetResult[T3]): GetResult[(T1, T2, T3)]

137

// ... up to Tuple22

138

}

139

```

140

141

**Usage Examples:**

142

143

```scala

144

// Built-in type mappings

145

val stringQuery = sql"SELECT name FROM coffees".as[String]

146

val tupleQuery = sql"SELECT name, price FROM coffees".as[(String, Double)]

147

val optionQuery = sql"SELECT description FROM coffees".as[Option[String]]

148

149

// Custom case class mapping

150

case class CoffeeInfo(name: String, price: Double, category: Option[String])

151

152

implicit val getCoffeeInfo = GetResult(r => CoffeeInfo(

153

r.nextString(),

154

r.nextDouble(),

155

r.nextStringOption()

156

))

157

158

val coffeeQuery = sql"SELECT name, price, category FROM coffees".as[CoffeeInfo]

159

160

// Complex type mapping

161

case class OrderSummary(

162

orderId: Int,

163

customerName: String,

164

coffeeName: String,

165

quantity: Int,

166

totalPrice: BigDecimal,

167

orderDate: Timestamp

168

)

169

170

implicit val getOrderSummary = GetResult(r => OrderSummary(

171

r.nextInt(),

172

r.nextString(),

173

r.nextString(),

174

r.nextInt(),

175

r.nextBigDecimal(),

176

r.nextTimestamp()

177

))

178

179

val orderQuery = sql"""

180

SELECT o.id, u.name, c.name, o.quantity, o.total_price, o.order_date

181

FROM orders o

182

JOIN users u ON o.user_id = u.id

183

JOIN coffees c ON o.coffee_id = c.id

184

WHERE o.order_date >= CURRENT_DATE - INTERVAL '30 days'

185

""".as[OrderSummary]

186

```

187

188

### Parameter Binding

189

190

Bind Scala values to SQL parameters using SetParameter type class.

191

192

```scala { .api }

193

/**

194

* Type class for binding Scala values to SQL parameters

195

*/

196

trait SetParameter[T] {

197

/** Set a parameter value in a PositionedParameters */

198

def apply(v: T, pp: PositionedParameters): Unit

199

}

200

201

/**

202

* Positioned parameter setter

203

*/

204

trait PositionedParameters {

205

/** Set parameters of various types */

206

def setBoolean(v: Boolean): Unit

207

def setInt(v: Int): Unit

208

def setLong(v: Long): Unit

209

def setString(v: String): Unit

210

def setDate(v: Date): Unit

211

def setTimestamp(v: Timestamp): Unit

212

def setBigDecimal(v: BigDecimal): Unit

213

def setBytes(v: Array[Byte]): Unit

214

215

/** Set optional parameters */

216

def setBooleanOption(v: Option[Boolean]): Unit

217

def setIntOption(v: Option[Int]): Unit

218

def setStringOption(v: Option[String]): Unit

219

// ... other optional types

220

221

/** Set null parameter */

222

def setNull(sqlType: Int): Unit

223

}

224

225

object SetParameter {

226

/** Implicit SetParameter instances for basic types */

227

implicit val SetBoolean: SetParameter[Boolean]

228

implicit val SetInt: SetParameter[Int]

229

implicit val SetLong: SetParameter[Long]

230

implicit val SetString: SetParameter[String]

231

implicit val SetDate: SetParameter[Date]

232

implicit val SetTimestamp: SetParameter[Timestamp]

233

implicit val SetBigDecimal: SetParameter[BigDecimal]

234

235

/** SetParameter for Option types */

236

implicit def SetOption[T](implicit base: SetParameter[T]): SetParameter[Option[T]]

237

}

238

```

239

240

**Usage Examples:**

241

242

```scala

243

// Basic parameter binding (automatic)

244

val minPrice = 2.50

245

val category = "espresso"

246

val query = sql"SELECT * FROM coffees WHERE price >= $minPrice AND category = $category"

247

248

// Custom parameter binding

249

case class PriceRange(min: Double, max: Double)

250

251

implicit val setPriceRange = SetParameter[PriceRange]((range, pp) => {

252

pp.setDouble(range.min)

253

pp.setDouble(range.max)

254

})

255

256

val priceRange = PriceRange(2.0, 4.0)

257

val rangeQuery = sql"SELECT * FROM coffees WHERE price BETWEEN ? AND ?" + priceRange

258

259

// Option parameter binding

260

val optionalCategory: Option[String] = Some("latte")

261

val categoryQuery = sql"SELECT * FROM coffees WHERE category = $optionalCategory OR $optionalCategory IS NULL"

262

263

// Date/time parameters

264

val startDate = Date.valueOf("2023-01-01")

265

val endDate = Timestamp.from(Instant.now())

266

val dateRangeQuery = sql"""

267

SELECT * FROM orders

268

WHERE order_date >= $startDate AND order_date <= $endDate

269

"""

270

```

271

272

### Static Queries

273

274

Define reusable parameterized queries for better performance and type safety.

275

276

```scala { .api }

277

/**

278

* Static query with parameter and result types

279

*/

280

case class StaticQuery[P, R](query: String)(implicit pconv: SetParameter[P], rconv: GetResult[R]) {

281

/** Apply parameters to create an executable action */

282

def apply(param: P): DBIO[Vector[R]]

283

284

/** Create a streaming action */

285

def stream(param: P): StreamingDBIO[Vector[R], R]

286

}

287

288

object StaticQuery {

289

/** Create a static query with parameters */

290

def query[P, R](sql: String)(implicit pconv: SetParameter[P], rconv: GetResult[R]): StaticQuery[P, R]

291

292

/** Create a static update query */

293

def update[P](sql: String)(implicit pconv: SetParameter[P]): StaticQuery[P, Int]

294

295

/** Create a query with no parameters */

296

def queryNA[R](sql: String)(implicit rconv: GetResult[R]): StaticQuery[Unit, R]

297

298

/** Create an update with no parameters */

299

def updateNA(sql: String): StaticQuery[Unit, Int]

300

}

301

```

302

303

**Usage Examples:**

304

305

```scala

306

// Static query with parameters

307

val findCoffeesByPrice = StaticQuery.query[(Double, Double), Coffee](

308

"SELECT * FROM coffees WHERE price BETWEEN ? AND ? ORDER BY name"

309

)

310

311

// Usage

312

val affordableCoffees = findCoffeesByPrice((2.0, 4.0))

313

val results = db.run(affordableCoffees)

314

315

// Static update query

316

val updateCoffeePrice = StaticQuery.update[Int](

317

"UPDATE coffees SET price = price * 1.1 WHERE id = ?"

318

)

319

320

val updateAction = updateCoffeePrice(123)

321

val affectedRows = db.run(updateAction)

322

323

// Query with no parameters

324

val getAllUsers = StaticQuery.queryNA[User](

325

"SELECT id, name, email FROM users ORDER BY name"

326

)

327

328

val allUsers = db.run(getAllUsers(()))

329

330

// Complex static query

331

val orderSummaryQuery = StaticQuery.query[(Int, Date), OrderSummary]("""

332

SELECT o.id, u.name, c.name, o.quantity, o.total_price, o.order_date

333

FROM orders o

334

JOIN users u ON o.user_id = u.id

335

JOIN coffees c ON o.coffee_id = c.id

336

WHERE u.id = ? AND o.order_date >= ?

337

ORDER BY o.order_date DESC

338

""")

339

340

val userOrders = orderSummaryQuery((userId, startDate))

341

```

342

343

### Database Functions

344

345

Call database-specific functions and stored procedures.

346

347

```scala { .api }

348

/**

349

* Call database functions and procedures

350

*/

351

object DatabaseFunctions {

352

/** Call a function returning a single value */

353

def function[R](name: String, args: Any*)(implicit getResult: GetResult[R]): DBIO[R]

354

355

/** Call a function returning multiple rows */

356

def tableFunction[R](name: String, args: Any*)(implicit getResult: GetResult[R]): StreamingDBIO[Vector[R], R]

357

358

/** Call a stored procedure */

359

def procedure(name: String, args: Any*): DBIO[Int]

360

}

361

```

362

363

**Usage Examples:**

364

365

```scala

366

// Database-specific functions (PostgreSQL examples)

367

val currentTime = sql"SELECT CURRENT_TIMESTAMP".as[Timestamp].head

368

val randomValue = sql"SELECT RANDOM()".as[Double].head

369

370

// Stored procedures

371

val analyzeTable = sqlu"CALL analyze_coffee_sales(${startDate}, ${endDate})"

372

373

// Custom functions

374

val coffeeRanking = sql"""

375

SELECT name, price,

376

RANK() OVER (ORDER BY price DESC) as price_rank

377

FROM coffees

378

""".as[(String, Double, Int)]

379

380

// JSON operations (PostgreSQL)

381

val jsonData = """{"type": "espresso", "strength": "strong"}"""

382

val jsonQuery = sql"""

383

SELECT * FROM coffees

384

WHERE metadata @> ${jsonData}::jsonb

385

""".as[Coffee]

386

387

// Window functions

388

val salesAnalysis = sql"""

389

SELECT

390

DATE_TRUNC('month', order_date) as month,

391

SUM(total_price) as monthly_total,

392

LAG(SUM(total_price)) OVER (ORDER BY DATE_TRUNC('month', order_date)) as prev_month_total

393

FROM orders

394

GROUP BY DATE_TRUNC('month', order_date)

395

ORDER BY month

396

""".as[(Date, BigDecimal, Option[BigDecimal])]

397

```

398

399

### Batch Operations

400

401

Execute multiple SQL statements efficiently in batches.

402

403

```scala { .api }

404

/**

405

* Batch execution utilities

406

*/

407

object BatchOperations {

408

/** Execute multiple statements in a batch */

409

def batch[T](statements: Seq[String]): DBIO[Seq[Int]]

410

411

/** Execute parameterized statements in a batch */

412

def batchUpdate[P](sql: String, params: Seq[P])(implicit setParam: SetParameter[P]): DBIO[Seq[Int]]

413

}

414

```

415

416

**Usage Examples:**

417

418

```scala

419

// Batch updates

420

val priceUpdates = Seq(

421

(1, 2.75),

422

(2, 3.25),

423

(3, 1.95)

424

)

425

426

val batchUpdate = DBIO.sequence(priceUpdates.map { case (id, price) =>

427

sqlu"UPDATE coffees SET price = $price WHERE id = $id"

428

})

429

430

// Batch inserts using plain SQL

431

val newCoffees = Seq(

432

("Americano", 2.50, "regular"),

433

("Macchiato", 3.75, "specialty"),

434

("Cold Brew", 3.25, "cold")

435

)

436

437

val batchInsert = DBIO.sequence(newCoffees.map { case (name, price, category) =>

438

sqlu"INSERT INTO coffees (name, price, category) VALUES ($name, $price, $category)"

439

})

440

441

// Large batch operation with transaction

442

val largeBatchUpdate = (for {

443

_ <- sqlu"BEGIN"

444

results <- DBIO.sequence(priceUpdates.map { case (id, price) =>

445

sqlu"UPDATE coffees SET price = $price WHERE id = $id"

446

})

447

_ <- sqlu"COMMIT"

448

} yield results).transactionally

449

```

450

451

## Types

452

453

```scala { .api }

454

implicit class SQLInterpolation(val sc: StringContext)

455

trait SQLActionBuilder {

456

def as[R](implicit getResult: GetResult[R]): StreamingDBIO[Vector[R], R]

457

def asUpdate: DBIO[Int]

458

def asDDL: DBIO[Unit]

459

}

460

461

trait GetResult[T] {

462

def apply(pr: PositionedResult): T

463

}

464

465

trait SetParameter[T] {

466

def apply(v: T, pp: PositionedParameters): Unit

467

}

468

469

trait PositionedResult {

470

def nextInt(): Int

471

def nextString(): String

472

def nextDouble(): Double

473

def nextBoolean(): Boolean

474

def nextDate(): Date

475

def nextTimestamp(): Timestamp

476

def nextBigDecimal(): BigDecimal

477

// ... and optional variants

478

}

479

480

trait PositionedParameters {

481

def setInt(v: Int): Unit

482

def setString(v: String): Unit

483

def setDouble(v: Double): Unit

484

def setBoolean(v: Boolean): Unit

485

def setDate(v: Date): Unit

486

def setTimestamp(v: Timestamp): Unit

487

def setBigDecimal(v: BigDecimal): Unit

488

// ... and optional variants

489

}

490

491

case class StaticQuery[P, R](query: String)(implicit pconv: SetParameter[P], rconv: GetResult[R]) {

492

def apply(param: P): DBIO[Vector[R]]

493

def stream(param: P): StreamingDBIO[Vector[R], R]

494

}

495

```