Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/other_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
mongodb-version: [ '4.4', '5.0', '6.0', '7.0', '8.0', '8.2' ]
mongodb-version: [ '6.0', '7.0', '8.0' ]
java: [ '21', '25' ]
steps:
- uses: actions/checkout@main
Expand Down
12 changes: 6 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ developers := List(

licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html"))

crossScalaVersions := Seq("3.7.2", "2.13.17")
crossScalaVersions := Seq("3.8.3", "2.13.18")

scalaVersion := crossScalaVersions.value.last

Expand All @@ -37,13 +37,13 @@ buildInfoOptions += BuildInfoOption.BuildTime

resolvers += "Sonatype OSS Snapshots".at("https://oss.sonatype.org/content/repositories/snapshots")

libraryDependencies += "joda-time" % "joda-time" % "2.14.0"
libraryDependencies += "joda-time" % "joda-time" % "2.14.1"

val circeVersion = "0.14.15"

libraryDependencies ++= Seq("io.circe" %% "circe-core", "io.circe" %% "circe-generic", "io.circe" %% "circe-parser").map(_ % circeVersion)

libraryDependencies += ("org.mongodb.scala" %% "mongo-scala-driver" % "5.6.1").cross(CrossVersion.for3Use2_13)
libraryDependencies += ("org.mongodb.scala" %% "mongo-scala-driver" % "5.6.5").cross(CrossVersion.for3Use2_13)

val MongoJavaServerVersion = "1.47.0"

Expand All @@ -53,13 +53,13 @@ libraryDependencies += "de.bwaldvogel" % "mongo-java-server-h2-backend" % MongoJ

libraryDependencies += "org.xerial.snappy" % "snappy-java" % "1.1.10.8" % Provided

libraryDependencies += "com.github.luben" % "zstd-jni" % "1.5.7-6" % Provided
libraryDependencies += "com.github.luben" % "zstd-jni" % "1.5.7-7" % Provided

libraryDependencies += "org.apache.lucene" % "lucene-queryparser" % "10.3.1"
libraryDependencies += "org.apache.lucene" % "lucene-queryparser" % "10.4.0"

libraryDependencies += "com.github.pathikrit" %% "better-files" % "3.9.2"

libraryDependencies += "com.typesafe" % "config" % "1.4.5"
libraryDependencies += "com.typesafe" % "config" % "1.4.6"

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.6"

Expand Down
6 changes: 3 additions & 3 deletions build_test.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Test / parallelExecution := false

libraryDependencies += "org.liquibase" % "liquibase-core" % "5.0.1" % Test
libraryDependencies += "org.liquibase" % "liquibase-core" % "5.0.2" % Test

// Test

libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.5.20" % Test
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.5.32" % Test

libraryDependencies += "org.scalameta" %% "munit" % "1.2.1"
libraryDependencies += "org.scalameta" %% "munit" % "1.3.0"
8 changes: 6 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default defineConfig({
vite: {
plugins: [
Unocss({
configFile: '../../unocss.config.ts',
configFile: 'docs/uno.config.ts',
}),
],
},
Expand Down Expand Up @@ -92,6 +92,8 @@ function sidebarDocumentation() {
{text: 'Introduction', link: '/documentation/database/'},
{text: 'Mongo Config', link: 'documentation/database/config'},
{text: 'DatabaseProvider', link: 'documentation/database/provider'},
{text: 'Transactions', link: '/documentation/database/transactions'},
{text: 'Collection Management', link: '/documentation/database/collections'},
{text: 'Reactive Streams', link: 'documentation/database/reactive-streams'},
{text: 'Bson', link: 'documentation/database/bson'},
{text: 'Relationships', link: 'documentation/database/relationships'},
Expand All @@ -107,7 +109,9 @@ function sidebarDocumentation() {
{text: 'Introduction', link: '/documentation/mongo-dao/'},
{text: 'MongoDAO Base', link: '/documentation/mongo-dao/base'},
{text: 'CRUD Functions', link: '/documentation/mongo-dao/crud'},
{text: 'Search Functions', link: '/documentation/mongo-dao/search'}
{text: 'Search Functions', link: '/documentation/mongo-dao/search'},
{text: 'Find-and-Modify', link: '/documentation/mongo-dao/find-and-modify'},
{text: 'Change Streams', link: '/documentation/mongo-dao/change-streams'}
]
},
{
Expand Down
85 changes: 85 additions & 0 deletions docs/documentation/database/collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Collection Management

`DatabaseProvider` provides first-class helpers for creating MongoDB's specialised collection types, so you never need to drop down to a raw `runCommand` call.

## Capped Collections

A capped collection has a fixed maximum size (and optionally a maximum document count). MongoDB automatically removes the oldest documents when the limit is reached — useful for logs, event queues, and similar append-heavy workloads.

```scala
def createCappedCollection(
collectionName: String,
maxSizeBytes: Long,
maxDocuments: Option[Long] = None,
databaseName: String = DefaultDatabaseName
): SingleObservable[Unit]
```

### Example

```scala
// 10 MB ring buffer, no document limit
provider.createCappedCollection("audit_log", maxSizeBytes = 10 * 1024 * 1024).result()

// 1 MB ring buffer, at most 1 000 documents
provider.createCappedCollection(
collectionName = "recent_events",
maxSizeBytes = 1024 * 1024,
maxDocuments = Some(1000L)
).result()
```

## Time-Series Collections

Time-series collections are optimised for storing measurements or events that arrive in time order. MongoDB stores them in a compressed columnar format for efficient range queries.

::: warning MongoDB 5.0+ Required
Time-series collections require MongoDB 5.0 or later. The operation is skipped gracefully when the server does not support it.
:::

```scala
def createTimeSeriesCollection(
collectionName: String,
timeField: String,
metaField: Option[String] = None,
granularity: Option[TimeSeriesGranularity] = None,
databaseName: String = DefaultDatabaseName
): SingleObservable[Unit]
```

| Parameter | Description |
|---|---|
| `timeField` | Field that holds the `Date` timestamp for each document (required). |
| `metaField` | Optional field used to group measurements by source/sensor. |
| `granularity` | Hint to MongoDB for bucket sizing: `SECONDS`, `MINUTES`, or `HOURS`. |

### Example

```scala
import com.mongodb.client.model.TimeSeriesGranularity

provider.createTimeSeriesCollection(
collectionName = "sensor_readings",
timeField = "timestamp",
metaField = Some("sensorId"),
granularity = Some(TimeSeriesGranularity.SECONDS)
).result()
```

## CollectionInfo helpers

`collectionInfos()` returns a `List[CollectionInfo]`. Each entry now exposes two helpers:

```scala
def isCapped: Boolean // true when the collection was created as capped
def isTimeSeries: Boolean // true when the collection type is "timeseries"
```

### Example

```scala
val infos = provider.collectionInfos()

infos.filter(_.isCapped).foreach(c => println(s"${c.name} is capped"))
infos.filter(_.isTimeSeries).foreach(c => println(s"${c.name} is time-series"))
```
39 changes: 36 additions & 3 deletions docs/documentation/database/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,46 @@ DatabaseProvider gives access to
* MongoDatabase
* MongoCollection

## DocumentDAO
## DocumentDAO

From an DocumentDAO you can perform DocumentDAO initialization and caching. On this DAO you can perform CRUD operations.

<<< @/../src/test/scala/dev/mongocamp/driver/mongodb/database/DatabaseProviderSuite.scala#document-dao

## Transactions

`withTransaction` executes a block inside a client session with automatic commit and rollback:

```scala
provider.withTransaction { session =>
PersonDAO.insertOne(alice, session).result()
PersonDAO.updateOne(filter, update, session).result()
}
```

See [Transactions](transactions.md) for full API details.

## Collection Creation

### Capped Collections

```scala
provider.createCappedCollection("audit_log", maxSizeBytes = 10 * 1024 * 1024).result()
```

### Time-Series Collections

```scala
provider.createTimeSeriesCollection(
collectionName = "sensor_readings",
timeField = "timestamp"
).result()
```

See [Collection Management](collections.md) for full API details.

## ~~Registries~~

::: danger
::: danger
Registries are no longer supported for automatic case class conversion. For scala 3 support we changed from mongodb driver conversion to circe conversion.
:::
:::
81 changes: 81 additions & 0 deletions docs/documentation/database/transactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Transactions

MongoDB multi-document ACID transactions let you execute several write operations atomically — either all succeed, or none are applied.

::: warning Replica Set Required
Transactions require a MongoDB replica set or MongoDB Atlas. When running against a standalone server or the embedded test server they are skipped gracefully.
:::

## withTransaction

`DatabaseProvider` provides a `withTransaction` helper that manages the session lifecycle for you:

```scala
def withTransaction[T](block: ClientSession => T): T
```

* Starts a `ClientSession` and calls `startTransaction()`.
* Passes the session into `block`.
* **Commits** the transaction if `block` returns normally.
* **Aborts** the transaction (rolls back) if `block` throws any exception — the exception is re-thrown.
* Always closes the session in a `finally` block.

### Example — commit

```scala
provider.withTransaction { session =>
PersonDAO.insertOne(alice, session).result()
PersonDAO.insertOne(bob, session).result()
}
// Both inserted atomically, or neither if an error occurred
```

### Example — rollback

```scala
intercept[RuntimeException] {
provider.withTransaction { session =>
PersonDAO.insertOne(alice, session).result()
throw new RuntimeException("something went wrong")
// alice is NOT persisted — the transaction was aborted
}
}
```

## Session-aware CRUD overloads

All write methods on `MongoDAO` accept an optional `ClientSession` as the last parameter so you can enlist individual operations in an existing transaction:

```scala
// Create
def insertOne(value: A, session: ClientSession): Observable[InsertOneResult]
def insertMany(values: Seq[A], session: ClientSession): Observable[InsertManyResult]

// Update
def replaceOne(filter: Bson, value: A, session: ClientSession): Observable[UpdateResult]
def updateOne(filter: Bson, update: Bson, session: ClientSession): Observable[UpdateResult]
def updateMany(filter: Bson, update: Bson, session: ClientSession): Observable[UpdateResult]

// Delete
def deleteOne(filter: Bson, session: ClientSession): Observable[DeleteResult]
def deleteMany(filter: Bson, session: ClientSession): Observable[DeleteResult]
```

## Session-aware find overloads

Read operations can also be scoped to a session to see the transactional writes made so far:

```scala
def find(session: ClientSession, filter: Bson, sort: Bson, projection: Bson, limit: Int, skip: Int): Observable[A]
def find(session: ClientSession, filter: Bson): Observable[A]
def find(session: ClientSession): Observable[A]
```

### Example — read inside a transaction

```scala
provider.withTransaction { session =>
val docs = PersonDAO.find(session).resultList()
// includes documents inserted earlier in this same transaction
}
```
28 changes: 23 additions & 5 deletions docs/documentation/mongo-dao/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ Base Functions like

are implemented.

[Search Functions](search.md) and [CRUD Functions](crud.md) are available.
[Search Functions](search.md), [CRUD Functions](crud.md), [Find-and-Modify](find-and-modify.md), and [Change Streams](change-streams.md) are available.

## Additional Features

### Synchronous Result

All functions support synchronous result handling.

Use pacckage import
Use package import

```scala
import dev.mongocamp.driver.mongodb._
Expand Down Expand Up @@ -50,7 +50,7 @@ Simply call Raw on your DAO Object.
Drop Collection.

```scala
def drop(): Observable[Void]
def drop(): SingleObservable[Unit]
```

### Count
Expand All @@ -69,8 +69,26 @@ def createIndex(key: Bson, options: IndexOptions = IndexOptions()): SingleObserv
// Simple Index creation
def createIndexForField(field: String, sortAscending: Boolean = true): SingleObservable[String]

def dropIndex(keys: Bson): SingleObservable[Void]
def dropIndex(keys: Bson): SingleObservable[Unit]

// Simple Index delete
def dropIndexForName(name: String): SingleObservable[Void]
def dropIndexForName(name: String): SingleObservable[Unit]
```

### Column Names

Detect the field names present in a collection (uses an aggregation pipeline internally):

```scala
def columnNames(sampleSize: Int = 0, maxWait: Int = DefaultMaxWait): List[String]
```

Pass `sampleSize > 0` to scan only a random sample for better performance on large collections.

### Import JSON

Bulk-import a newline-delimited JSON file (one document per line):

```scala
def importJsonFile(file: better.files.File): SingleObservable[BulkWriteResult]
```
Loading
Loading