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

queries.mddocs/

0

# Query Language

1

2

Type-safe query composition with monadic operations, joins, aggregations, and filtering using Slick's lifted embedding DSL.

3

4

## Capabilities

5

6

### Query Construction

7

8

Build type-safe database queries using the Query class and its operations.

9

10

```scala { .api }

11

/**

12

* Core query representation with monadic operations

13

* @param E Element type (what you're selecting from)

14

* @param U Unpacked type (the Scala type you get)

15

* @param C Collection type (usually Seq)

16

*/

17

abstract class Query[+E, U, C[_]] extends Rep[C[U]] {

18

/** Apply a filter predicate (WHERE clause) */

19

def filter(f: E => Rep[Boolean]): Query[E, U, C]

20

21

/** Transform each element (SELECT clause) */

22

def map[F, G, T](f: E => F)(implicit shape: Shape[_ <: FlatShapeLevel, F, G, T]): Query[G, T, C]

23

24

/** Monadic composition for complex queries */

25

def flatMap[F, G, T](f: E => Query[F, T, Seq])(implicit shape: Shape[_ <: FlatShapeLevel, F, G, T]): Query[G, T, C]

26

27

/** Sort the results (ORDER BY clause) */

28

def sortBy[T](f: E => T)(implicit ord: slick.lifted.Ordering[T]): Query[E, U, C]

29

30

/** Group results by a key (GROUP BY clause) */

31

def groupBy[K](f: E => K): Query[(K, Query[E, U, C]), (K, C[U]), C]

32

33

/** Limit number of results */

34

def take(num: Int): Query[E, U, C]

35

36

/** Skip a number of results */

37

def drop(num: Int): Query[E, U, C]

38

39

/** Remove duplicate results */

40

def distinct: Query[E, U, C]

41

42

/** Check if any results exist */

43

def exists: Rep[Boolean]

44

45

/** Count the number of results */

46

def length: Rep[Int]

47

48

/** Check if the query is empty */

49

def isEmpty: Rep[Boolean]

50

51

/** Check if the query is non-empty */

52

def nonEmpty: Rep[Boolean]

53

54

/** Apply filter conditionally based on Option value */

55

def filterOpt[V, T](optValue: Option[V])(f: (E, V) => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]

56

57

/** Apply filter conditionally based on boolean condition */

58

def filterIf[T](p: Boolean)(f: E => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]

59

60

/** Apply filterNot conditionally based on Option value */

61

def filterNotOpt[V, T](optValue: Option[V])(f: (E, V) => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]

62

63

/** Apply filterNot conditionally based on boolean condition */

64

def filterNotIf[T](p: Boolean)(f: E => T)(implicit wt: CanBeQueryCondition[T]): Query[E, U, C]

65

}

66

```

67

68

**Usage Examples:**

69

70

```scala

71

// Basic filtering and mapping

72

val expensiveCoffees = coffees.filter(_.price > 3.0)

73

val coffeeNames = coffees.map(_.name)

74

val coffeePricesInCents = coffees.map(c => (c.name, c.price * 100))

75

76

// Sorting

77

val coffeesByName = coffees.sortBy(_.name)

78

val coffeesByPriceDesc = coffees.sortBy(_.price.desc)

79

80

// Combining operations

81

val cheapCoffeeNames = coffees

82

.filter(_.price < 2.0)

83

.sortBy(_.name)

84

.map(_.name)

85

86

// Limiting results

87

val first5Coffees = coffees.take(5)

88

val skip10AndTake5 = coffees.drop(10).take(5)

89

90

// Existence checks

91

val hasExpensiveCoffees = coffees.filter(_.price > 5.0).exists

92

val coffeeCount = coffees.length

93

94

// Conditional filtering

95

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

96

val coffeesByOptionalCategory = coffees.filterOpt(optionalCategory) { (coffee, category) =>

97

coffee.category === category

98

}

99

100

val includeExpensive = true

101

val filteredCoffees = coffees.filterIf(includeExpensive)(_.price > 4.0)

102

103

// Conditional negative filtering

104

val excludeCategory: Option[String] = Some("decaf")

105

val nonDecafCoffees = coffees.filterNotOpt(excludeCategory) { (coffee, category) =>

106

coffee.category === category

107

}

108

```

109

110

### Join Operations

111

112

Combine data from multiple tables using various join types.

113

114

```scala { .api }

115

/**

116

* Inner join between two queries

117

*/

118

def join[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, E2), (U, U2), C]

119

120

/**

121

* Left outer join between two queries

122

*/

123

def joinLeft[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, Rep[Option[E2]]), (U, Option[U2]), C]

124

125

/**

126

* Right outer join between two queries

127

*/

128

def joinRight[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(Rep[Option[E]], E2), (Option[U], U2), C]

129

130

/**

131

* Full outer join between two queries

132

*/

133

def joinFull[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(Rep[Option[E]], Rep[Option[E2]]), (Option[U], Option[U2]), C]

134

135

/**

136

* Cross join (cartesian product) between two queries

137

*/

138

def zip[E2, U2, D[_]](q2: Query[E2, U2, D]): Query[(E, E2), (U, U2), C]

139

```

140

141

**Usage Examples:**

142

143

```scala

144

// Setup table queries

145

val users = TableQuery[Users]

146

val orders = TableQuery[Orders]

147

val products = TableQuery[Products]

148

149

// Inner join with explicit condition

150

val usersWithOrders = for {

151

user <- users

152

order <- orders if user.id === order.userId

153

} yield (user.name, order.id)

154

155

// Using join method

156

val userOrderJoin = users.join(orders).on(_.id === _.userId)

157

158

// Left join to include users without orders

159

val allUsersWithOptionalOrders = users.joinLeft(orders).on(_.id === _.userId)

160

161

// Complex join with multiple tables

162

val orderDetails = for {

163

order <- orders

164

user <- users if order.userId === user.id

165

product <- products if order.productId === product.id

166

} yield (user.name, product.name, order.quantity, product.price)

167

168

// Join with filtering

169

val recentOrdersWithUsers = for {

170

order <- orders if order.orderDate > Date.valueOf("2023-01-01")

171

user <- users if order.userId === user.id

172

} yield (user.name, order.orderDate)

173

```

174

175

### Aggregation Functions

176

177

Perform aggregate calculations on query results.

178

179

```scala { .api }

180

/**

181

* Count all rows

182

*/

183

def length: Rep[Int]

184

185

/**

186

* Maximum value of a column

187

*/

188

def max[B >: U](implicit ord: slick.lifted.Ordering[Option[B]]): Rep[Option[B]]

189

190

/**

191

* Minimum value of a column

192

*/

193

def min[B >: U](implicit ord: slick.lifted.Ordering[Option[B]]): Rep[Option[B]]

194

195

/**

196

* Sum of numeric column values

197

*/

198

def sum[B >: U](implicit num: Numeric[B]): Rep[Option[B]]

199

200

/**

201

* Average of numeric column values

202

*/

203

def avg[B >: U](implicit num: Numeric[B]): Rep[Option[B]]

204

```

205

206

Additional aggregate functions available on columns:

207

208

```scala { .api }

209

/**

210

* Count non-null values in a column

211

*/

212

def count: Rep[Int]

213

214

/**

215

* Count distinct values in a column

216

*/

217

def countDistinct: Rep[Int]

218

```

219

220

**Usage Examples:**

221

222

```scala

223

// Basic aggregations

224

val totalCoffees = coffees.length

225

val maxPrice = coffees.map(_.price).max

226

val minPrice = coffees.map(_.price).min

227

val avgPrice = coffees.map(_.price).avg

228

val totalValue = coffees.map(_.price).sum

229

230

// Group by with aggregations

231

val pricesByCategory = coffees

232

.groupBy(_.category)

233

.map { case (category, group) =>

234

(category, group.map(_.price).avg, group.length)

235

}

236

237

// Count distinct

238

val distinctCategories = coffees.map(_.category).countDistinct

239

240

// Complex aggregation query

241

val userOrderStats = orders

242

.groupBy(_.userId)

243

.map { case (userId, group) =>

244

(userId, group.length, group.map(_.quantity).sum, group.map(_.quantity).avg)

245

}

246

```

247

248

### Set Operations

249

250

Combine query results using set operations.

251

252

```scala { .api }

253

/**

254

* Union of two queries (removes duplicates)

255

*/

256

def union[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]

257

258

/**

259

* Union all of two queries (keeps duplicates)

260

*/

261

def unionAll[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]

262

263

/**

264

* Intersection of two queries

265

*/

266

def intersect[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]

267

268

/**

269

* Difference between two queries (elements in first but not second)

270

*/

271

def diff[O >: E, R, D[_]](other: Query[O, R, D]): Query[O, R, C]

272

```

273

274

**Usage Examples:**

275

276

```scala

277

// Union queries

278

val cheapOrExpensive = coffees.filter(_.price < 2.0).union(coffees.filter(_.price > 4.0))

279

280

// All coffee names and tea names

281

val allBeverageNames = coffees.map(_.name).unionAll(teas.map(_.name))

282

283

// Coffee categories that are also tea categories

284

val commonCategories = coffees.map(_.category).intersect(teas.map(_.category))

285

286

// Coffee names that aren't tea names

287

val coffeeOnlyNames = coffees.map(_.name).diff(teas.map(_.name))

288

```

289

290

### Column Operations

291

292

Operations available on individual columns and expressions.

293

294

```scala { .api }

295

/**

296

* Comparison operations

297

*/

298

def ===[P2](e: P2): Rep[Boolean] // Equality

299

def =!=[P2](e: P2): Rep[Boolean] // Inequality

300

def <[P2](e: P2): Rep[Boolean] // Less than

301

def <=[P2](e: P2): Rep[Boolean] // Less than or equal

302

def >[P2](e: P2): Rep[Boolean] // Greater than

303

def >=[P2](e: P2): Rep[Boolean] // Greater than or equal

304

305

/**

306

* Set membership operations

307

*/

308

def in[P2](e: Query[P2, _, Seq]): Rep[Boolean]

309

def inSet[P2](set: Traversable[P2]): Rep[Boolean]

310

def between[P2, P3](start: P2, end: P3): Rep[Boolean]

311

312

/**

313

* Null handling

314

*/

315

def ifNull[B](alt: B): Rep[B]

316

def isDefined: Rep[Boolean] // For Option columns

317

def isEmpty: Rep[Boolean] // For Option columns

318

319

/**

320

* Boolean operations

321

*/

322

def &&(e: Rep[Boolean]): Rep[Boolean] // Logical AND

323

def ||(e: Rep[Boolean]): Rep[Boolean] // Logical OR

324

def unary_!(): Rep[Boolean] // Logical NOT

325

326

/**

327

* Arithmetic operations (for numeric types)

328

*/

329

def +[P2, R](e: P2): Rep[R] // Addition

330

def -[P2, R](e: P2): Rep[R] // Subtraction

331

def *[P2, R](e: P2): Rep[R] // Multiplication

332

def /[P2, R](e: P2): Rep[R] // Division

333

def %[P2, R](e: P2): Rep[R] // Modulo

334

335

/**

336

* String operations

337

*/

338

def ++[P2](e: P2): Rep[String] // String concatenation

339

def like(pattern: Rep[String]): Rep[Boolean] // Pattern matching

340

def startsWith(prefix: Rep[String]): Rep[Boolean]

341

def endsWith(suffix: Rep[String]): Rep[Boolean]

342

```

343

344

**Usage Examples:**

345

346

```scala

347

// Comparison operations

348

val expensiveCoffees = coffees.filter(_.price > 3.0)

349

val specificCoffee = coffees.filter(_.name === "Latte")

350

val priceRange = coffees.filter(_.price.between(2.0, 4.0))

351

352

// Set membership

353

val popularCoffees = coffees.filter(_.name.inSet(Set("Latte", "Cappuccino", "Espresso")))

354

355

// String operations

356

val latteVariants = coffees.filter(_.name.like("%Latte%"))

357

val coffeeDescriptions = coffees.map(c => c.name ++ " - $" ++ c.price.asColumnOf[String])

358

359

// Null handling

360

val activeUsers = users.filter(_.deletedAt.isEmpty)

361

val usersWithDefaultRole = users.map(u => (u.name, u.role.ifNull("user")))

362

363

// Boolean logic

364

val affordablePopularCoffees = coffees.filter(c => c.price < 3.0 && c.name.like("%Latte%"))

365

366

// Arithmetic

367

val pricesWithTax = coffees.map(c => (c.name, c.price * 1.08))

368

val discountedPrices = coffees.map(c => c.price - (c.price * 0.1))

369

```

370

371

### Subqueries

372

373

Use subqueries for complex filtering and selection.

374

375

```scala { .api }

376

/**

377

* Check if any rows exist in a subquery

378

*/

379

def exists: Rep[Boolean]

380

381

/**

382

* Filter based on subquery results

383

*/

384

def in[P2](subquery: Query[P2, _, Seq]): Rep[Boolean]

385

386

/**

387

* Use subquery in SELECT clause

388

*/

389

def map[F](f: E => F): Query[F, _, C]

390

```

391

392

**Usage Examples:**

393

394

```scala

395

// Exists subquery

396

val usersWithOrders = users.filter(user =>

397

orders.filter(_.userId === user.id).exists

398

)

399

400

// In subquery

401

val usersWhoOrderedExpensiveItems = users.filter(_.id.in(

402

orders.filter(_.total > 100.0).map(_.userId)

403

))

404

405

// Correlated subquery

406

val usersWithOrderCount = users.map { user =>

407

(user.name, orders.filter(_.userId === user.id).length)

408

}

409

410

// Scalar subquery

411

val avgOrderValue = orders.map(_.total).avg

412

val aboveAverageOrders = orders.filter(_.total > avgOrderValue)

413

```

414

415

### For Comprehensions

416

417

Use Scala's for comprehension syntax for complex queries.

418

419

**Usage Examples:**

420

421

```scala

422

// Basic for comprehension

423

val coffeeNamesAndPrices = for {

424

coffee <- coffees

425

if coffee.price > 2.0

426

} yield (coffee.name, coffee.price)

427

428

// Multiple table join

429

val userOrderDetails = for {

430

user <- users

431

order <- orders if order.userId === user.id

432

if order.total > 50.0

433

} yield (user.name, order.id, order.total)

434

435

// Complex filtering and joining

436

val recentOrderSummary = for {

437

order <- orders if order.orderDate > Date.valueOf("2023-01-01")

438

user <- users if order.userId === user.id

439

product <- products if order.productId === product.id

440

} yield (user.name, product.name, order.quantity, order.total)

441

442

// Nested conditions

443

val premiumUserExpensiveOrders = for {

444

user <- users if user.accountType === "premium"

445

order <- orders if order.userId === user.id && order.total > 100.0

446

} yield (user.name, order.total)

447

```

448

449

## Types

450

451

```scala { .api }

452

abstract class Query[+E, U, C[_]] extends Rep[C[U]]

453

trait Rep[T]

454

455

// Query result types

456

type SimpleQuery[E, U] = Query[E, U, Seq]

457

type UnpackQuery[E] = Query[E, E, Seq]

458

459

// Shape types for query mapping

460

trait Shape[Level <: ShapeLevel, -Mixed_, Unpacked_, Packed_]

461

trait ProvenShape[U] extends Shape[FlatShapeLevel, U, U, U]

462

463

// Ordering for sortBy operations

464

trait Ordering[T]

465

object Ordering {

466

def apply[T](implicit ord: scala.math.Ordering[T]): Ordering[T]

467

implicit def columnOrdering[T](implicit tm: TypedType[T], ord: scala.math.Ordering[T]): Ordering[Rep[T]]

468

}

469

```