Skip to content

Commit 12a4501

Browse files
authored
Adding COUNT and COUNT(DISTINCT) aggregation support (#97)
## Summary - Implements COUNT and COUNT(DISTINCT) aggregations for ScalaSQL addressing issue --> #95 - Adds comprehensive API with proper SQL semantics and NULL handling - Provides full cross-dialect compatibility and extensive test coverage - Includes complete documentation following existing patterns ## Changes Made ### Core Operations - **`.countBy(_.column)`** - Generates `COUNT(column)` SQL, ignores NULL values - **`.countDistinctBy(_.column)`** - Generates `COUNT(DISTINCT column)` SQL for unique non-NULL counts - **`.count`** on mapped expressions - `query.map(_.expr).count` - **`.countDistinct`** on mapped expressions - `query.map(_.expr).countDistinct` ### Advanced Usage ```scala // Basic counting Purchase.select.countBy(_.productId) // COUNT(product_id) Purchase.select.countDistinctBy(_.productId) // COUNT(DISTINCT product_id) // Expression counting Purchase.select.map(_.total * 2).count // COUNT(total * 2) Purchase.select.map(p => p.productId % 2).countDistinct // COUNT(DISTINCT productId % 2) // With filters and group by Purchase.select.filter(_.total > 100).countBy(_.productId) Purchase.select.groupBy(_.shippingInfoId)(agg => agg.countDistinctBy(_.productId)) // Multiple aggregates Purchase.select.aggregate(agg => ( agg.countBy(_.productId), agg.countDistinctBy(_.productId), agg.sumBy(_.total) )) ``` ### Files Added/Modified - AggAnyOps.scala - New operations for Aggregatable[Expr[T]] - AggOps.scala - Enhanced with countBy/countDistinctBy methods - DbCountOpsTests.scala - Basic COUNT operation tests - DbCountOpsOptionTests.scala - NULL handling and Option type tests - DbCountOpsAdvancedTests.scala - Complex expressions and edge cases ### Database compatibility - PostgreSQL - MOD() function - MySQL - MOD() function - SQLite - % operator - H2 - MOD() function - MS SQL Server - % operator ### Test Coverage - **DbCountOpsTests.scala** (new): Basic COUNT operations, GROUP BY, filters, joins - **DbCountOpsAdvancedTests.scala** (new): Complex types, edge cases, Option handling - Cross-dialect testing for PostgreSQL, MySQL, SQLite, H2, MS SQL Server - Window function and CTE integration tests - NULL value handling with Option types - Arithmetic expressions (addition, multiplication, modulo) - String operations and concatenation - Complex filtering and grouping scenarios - Multi-dialect SQL generation - Cross-database compatibility verification ### Documentation - **tutorial.md**: Added COUNT examples after existing `.size` documentation - **cheatsheet.md**: Updated aggregate functions table and quick reference - **Window functions**: Added COUNT support documentation ## Test Plan - [x] Compiles successfully for Scala 2.13.12 and 3.6.2 - [x] All existing tests continue to pass - [x] Window functions integrate correctly with existing patterns - [x] Documentation examples validated against actual API - [x] New COUNT operations work across all supported database dialects - [x] All newly created tests pass - [x] Proper NULL handling verified with Option types #95
1 parent 9a504dd commit 12a4501

File tree

9 files changed

+1266
-9
lines changed

9 files changed

+1266
-9
lines changed

docs/cheatsheet.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ Foo.select.sumByOpt(_.myInt) // Option[In
6767
Foo.select.size // Int
6868
// SELECT COUNT(1) FROM foo
6969

70+
Foo.select.countBy(_.myInt) // Int
71+
// SELECT COUNT(my_int) FROM foo
72+
73+
Foo.select.countDistinctBy(_.myInt) // Int
74+
// SELECT COUNT(DISTINCT my_int) FROM foo
75+
76+
Foo.select.map(_.myInt).count // Int
77+
// SELECT COUNT(my_int) FROM foo
78+
79+
Foo.select.map(_.myInt).countDistinct // Int
80+
// SELECT COUNT(DISTINCT my_int) FROM foo
81+
7082
Foo.select.aggregate(fs => (fs.sumBy(_.myInt), fs.maxBy(_.myInt))) // (Int, Int)
7183
// SELECT SUM(my_int), MAX(my_int) FROM foo
7284

@@ -200,14 +212,16 @@ to allow ScalaSql to work with it
200212

201213
**Aggregate Functions**
202214

203-
| Scala | SQL |
204-
|----------------------------------------------------:|------------------------:|
205-
| `a.size` | `COUNT(1)` |
206-
| `a.mkString(sep)` | `GROUP_CONCAT(a, sep)` |
207-
| `a.sum`, `a.sumBy(_.myInt)`, `a.sumByOpt(_.myInt)` | `SUM(my_int)` |
208-
| `a.min`, `a.minBy(_.myInt)`, `a.minByOpt(_.myInt)` | `MIN(my_int)` |
209-
| `a.max`, `a.maxBy(_.myInt)`, `a.maxByOpt(_.myInt)` | `MAX(my_int)` |
210-
| `a.avg`, `a.avgBy(_.myInt)`, `a.avgByOpt(_.myInt)` | `AVG(my_int)` |
215+
| Scala | SQL |
216+
|-------------------------------------------------------------:|------------------------:|
217+
| `a.size` | `COUNT(1)` |
218+
| `a.mkString(sep)` | `GROUP_CONCAT(a, sep)` |
219+
| `a.sum`, `a.sumBy(_.myInt)`, `a.sumByOpt(_.myInt)` | `SUM(my_int)` |
220+
| `a.min`, `a.minBy(_.myInt)`, `a.minByOpt(_.myInt)` | `MIN(my_int)` |
221+
| `a.max`, `a.maxBy(_.myInt)`, `a.maxByOpt(_.myInt)` | `MAX(my_int)` |
222+
| `a.avg`, `a.avgBy(_.myInt)`, `a.avgByOpt(_.myInt)` | `AVG(my_int)` |
223+
| `a.countBy(_.myInt)`, `a.map(_.myInt).count` | `COUNT(my_int)` |
224+
| `a.countDistinctBy(_.myInt)`, `a.map(_.myInt).countDistinct` |`COUNT(DISTINCT my_int)` |
211225

212226
**Select Functions**
213227

0 commit comments

Comments
 (0)