From 48aad33cceeb3b4fa6dfdbafbc0d77082d05a5ae Mon Sep 17 00:00:00 2001 From: Graham Pople Date: Wed, 9 Jul 2025 12:17:28 +0100 Subject: [PATCH 1/7] Updating for 3.9.0 Including a first pass at documenting the Scala 3 build. --- README.adoc | 8 +- antora.yml | 10 +- build.gradle | 8 +- .../data-durability-acid-transactions.adoc | 5 +- .../examples/scala/ErrorHandling.scala | 9 +- modules/devguide/examples/scala/Queries.scala | 4 +- .../devguide/examples/scala/SubDocument.scala | 6 +- .../devguide/examples/scala/Transcoding.scala | 582 +++++++++--------- modules/devguide/examples/scala/pom.xml | 2 +- modules/hello-world/pages/overview.adoc | 5 + modules/howtos/pages/analytics-using-sdk.adoc | 1 + .../howtos/pages/concurrent-async-apis.adoc | 1 + modules/howtos/pages/json.adoc | 3 + modules/howtos/pages/kv-operations.adoc | 4 +- .../howtos/pages/sqlpp-queries-with-sdk.adoc | 1 + .../howtos/pages/subdocument-operations.adoc | 19 - modules/project-docs/pages/compatibility.adoc | 7 +- .../pages/migrating-to-scala-3.adoc | 19 + .../project-docs/pages/sdk-release-notes.adoc | 50 +- 19 files changed, 403 insertions(+), 341 deletions(-) create mode 100644 modules/project-docs/pages/migrating-to-scala-3.adoc diff --git a/README.adoc b/README.adoc index 931a560b..0a81b078 100644 --- a/README.adoc +++ b/README.adoc @@ -2,8 +2,8 @@ This repository hosts the documentation source for the https://docs.couchbase.com/scala-sdk/1.7/hello-world/overview.html[Couchbase Scala SDK]. -This branch documents the 1.8 release of the SDK. -It follows the https://docs.couchbase.com/scala-sdk/1.8/project-docs/compatibility.html#api-version[SDK 3.7 API]. +This branch documents the 3.9 release of the SDK. +It follows the https://docs.couchbase.com/scala-sdk/3.9/project-docs/compatibility.html#api-version[SDK 3.7 API]. This branch reflects the new Information Architecture for the SDK docs. @@ -21,12 +21,10 @@ in your Antora playbook. If you are not sure the best way to update the docs, please raise a JIRA ticket with your suggestion, putting in as much information as possible. -TODO: Add in link to new JIRA. - == Notes for Docs Maintainers -The philosophy behind the Information Architecture can be found in the https://docs.couchbase.com/scala-sdk/1.8/project-docs/metadoc-about-these-sdk-docs.html[meta doc]. +The philosophy behind the Information Architecture can be found in the https://docs.couchbase.com/scala-sdk/3.9/project-docs/metadoc-about-these-sdk-docs.html[meta doc]. === Directory Structure diff --git a/antora.yml b/antora.yml index 0e4e1cb0..7bde23d7 100644 --- a/antora.yml +++ b/antora.yml @@ -1,5 +1,5 @@ name: scala-sdk -version: '1.8' +version: '3.9' title: Scala SDK start_page: hello-world:overview.adoc nav: @@ -9,13 +9,13 @@ asciidoc: attributes: page-nav-header-levels: 2 server_version: '7.6.6' - sdk_current_version: '1.8.2' - sdk_dot_minor: '1.8' - sdk_dot_major: '1.x' + sdk_current_version: '3.9.0' + sdk_dot_minor: '3.9' + sdk_dot_major: '3.x' version-server: '7.6' version-common: '7.7' name_platform: 'Scala' name-sdk: 'Scala SDK' - sdk_api: '3.7' + sdk_api: '3.8' sdk-api-link: https://docs.couchbase.com/sdk-api/couchbase-scala-client/com/couchbase/client/scala/index.html sdk-gh-link: https://github.com/couchbase/couchbase-jvm-clients/tree/master/scala-client diff --git a/build.gradle b/build.gradle index 6115db06..949eec1c 100644 --- a/build.gradle +++ b/build.gradle @@ -23,10 +23,10 @@ repositories { sourceSets { main { scala { - srcDirs = ['modules/hello-world/examples', - 'modules/howtos/examples', - 'modules/ref/examples', - 'modules/ROOT/examples'] + // Include the actual location of the Scala example sources that ship with the documentation. + // The previous paths pointed to now-non-existent directories, so Gradle found no source files + // and therefore appeared to "do nothing" when you ran the build. + srcDirs = ['modules/devguide/examples/scala'] } } } diff --git a/modules/concept-docs/pages/data-durability-acid-transactions.adoc b/modules/concept-docs/pages/data-durability-acid-transactions.adoc index 60a3c491..48cca97f 100644 --- a/modules/concept-docs/pages/data-durability-acid-transactions.adoc +++ b/modules/concept-docs/pages/data-durability-acid-transactions.adoc @@ -155,8 +155,6 @@ A practical look at this -- using the `durability` parameter with mutating opera -This durability is enforced by the cluster, - // *TODO* Durability section // Worth putting in? @@ -181,6 +179,7 @@ The method can either be one to return the first result returned -- active or re which is useful if a node is timing out; or to return _all_ of the results, for client code to handle any inconsistency, or to build a consensus answer. +NOTE: Reading from replicas is not supported in the Scala 3 version of the SDK. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. === Preferred Server Group Replica Reads @@ -188,7 +187,7 @@ IMPORTANT: Preferred Server Group Replica Reads are only accessible with the {na xref:server:learn:clusters-and-availability/groups.adoc#understanding-server-group-awareness[Server Groups] can be used to define subsets of nodes within a Couchbase cluster, which contain a complete set of vbuckets (active or replica). -As well as high availability use cases, Servre Groups can also be used to keep much traffic within the same cloud Availability Zone. +As well as high availability use cases, Server Groups can also be used to keep much traffic within the same cloud Availability Zone. For Capella users with high data volumes, egress charges for reads from other Availability Zones (AZ) in AWS can be a significant cost. The {name-sdk}, when making read replica requests, can make a request to a preferred Server Group -- diff --git a/modules/devguide/examples/scala/ErrorHandling.scala b/modules/devguide/examples/scala/ErrorHandling.scala index 985ebd2d..16426ba9 100644 --- a/modules/devguide/examples/scala/ErrorHandling.scala +++ b/modules/devguide/examples/scala/ErrorHandling.scala @@ -16,12 +16,11 @@ // #tag::imports[] import java.util.concurrent.TimeUnit - import com.couchbase.client.core.error._ import com.couchbase.client.scala._ import com.couchbase.client.scala.durability._ import com.couchbase.client.scala.json._ -import com.couchbase.client.scala.kv.MutationResult +import com.couchbase.client.scala.kv.{InsertOptions, MutationResult, ReplaceOptions} import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} @@ -86,7 +85,7 @@ object ErrorHandling { // #tag::cas[] def doOperation(guard: Int = 3): Try[MutationResult] = { collection.get("doc") - .flatMap(doc => collection.replace(doc.id, newJson, cas = doc.cas)) match { + .flatMap(doc => collection.replace(doc.id, newJson, ReplaceOptions().cas(doc.cas))) match { case Success(value) => Success(value) @@ -107,7 +106,7 @@ object ErrorHandling { // #tag::insert[] def doInsert(docId: String, json: JsonObject, guard: Int = InitialGuard): Try[String] = { - val result = collection.insert(docId, json, durability = Durability.Majority) + val result = collection.insert(docId, json, InsertOptions().durability(Durability.Majority)) result match { @@ -143,7 +142,7 @@ object ErrorHandling { json: JsonObject, guard: Int = InitialGuard, delay: Duration = Duration(10, TimeUnit.MILLISECONDS)): Try[String] = { - val result = collection.insert(docId, json, durability = Durability.Majority) + val result = collection.insert(docId, json, InsertOptions().durability(Durability.Majority)) result match { diff --git a/modules/devguide/examples/scala/Queries.scala b/modules/devguide/examples/scala/Queries.scala index 10ab8a4d..d5872a9f 100644 --- a/modules/devguide/examples/scala/Queries.scala +++ b/modules/devguide/examples/scala/Queries.scala @@ -180,8 +180,8 @@ object Queries { // ReactiveQueryResult contains a rows: Flux[QueryRow] .flatMapMany(result => result.rowsAs[JsonObject]) -// Just for example, block on the rows. This is not best practice and apps -// should generally not block. + // Just for example, block on the rows. This is not best practice and apps + // should generally not block. val allRows: Seq[JsonObject] = rows .doOnNext(row => println(row)) .doOnError(err => println(s"Error: $err")) diff --git a/modules/devguide/examples/scala/SubDocument.scala b/modules/devguide/examples/scala/SubDocument.scala index 8bea66c1..021dd59f 100644 --- a/modules/devguide/examples/scala/SubDocument.scala +++ b/modules/devguide/examples/scala/SubDocument.scala @@ -321,7 +321,7 @@ def cas() { val result = collection.get("player432") .flatMap(doc => collection.mutateIn("player432", Array( decrement("gold", 150) - ), cas = doc.cas)) + ), MutateInOptions().cas(doc.cas))) // #end::cas[] } @@ -329,7 +329,7 @@ val result = collection.get("player432") // #tag::durability[] val result = collection.mutateIn("key", Array( insert("name", "andy") - ), durability = Durability.ClientVerified(ReplicateTo.One, PersistTo.One)) + ), MutateInOptions().durability(Durability.ClientVerified(ReplicateTo.One, PersistTo.One))) // #end::durability[] } @@ -337,7 +337,7 @@ val result = collection.get("player432") // #tag::sync-durability[] val result = collection.mutateIn("key", Array( insert("name", "andy") - ), durability = Durability.Majority) + ), MutateInOptions().durability(Durability.Majority)) // #end::sync-durability[] } diff --git a/modules/devguide/examples/scala/Transcoding.scala b/modules/devguide/examples/scala/Transcoding.scala index 6d34638d..12256198 100644 --- a/modules/devguide/examples/scala/Transcoding.scala +++ b/modules/devguide/examples/scala/Transcoding.scala @@ -1,291 +1,291 @@ -// /* -// * Copyright (c) 2020 Couchbase, Inc. -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ - -// import java.nio.charset.StandardCharsets - -// import com.couchbase.client.core.msg.kv.CodecFlags.CommonFlags -// import com.couchbase.client.scala.Cluster -// import com.couchbase.client.scala.codec._ -// import com.couchbase.client.scala.kv.{GetOptions, UpsertOptions} -// import org.msgpack.core.MessagePack -// import org.scalatest.funsuite.AnyFunSuite - -// import scala.reflect.runtime.universe -// import scala.util.{Failure, Success, Try} -// import scala.reflect.runtime.universe._ - -// // #tag::ujson[] -// case class MyUser(name: String, age: Int) - -// object MyUser { -// implicit object UserSerializer extends JsonSerializer[MyUser] { -// override def serialize(content: MyUser): Try[Array[Byte]] = { -// // It's also possible for uPickle to serialize and deserialize -// // case classes directly to/from JSON, but for the purposes of -// // demonstration we will generate the JSON manually. -// val json = ujson.Obj("name" -> content.name, "age" -> content.age) -// Success(ujson.transform(json, ujson.BytesRenderer()).toBytes) -// } -// } - -// implicit object UserDeserializer extends JsonDeserializer[MyUser] { -// override def deserialize(bytes: Array[Byte]): Try[MyUser] = { -// Try({ -// val json = upickle.default.read[ujson.Value](bytes) -// MyUser(json("name").str, json("age").num.toInt) -// }) -// } -// } -// } -// // #end::ujson[] - -// // #tag::overloaded1[] -// case class MyUser2(name: String, age: Int) - -// object MyUser2 { -// // First serializer uses uPickle -// implicit object UserSerializer1 extends JsonSerializer[MyUser2] { -// override def serialize(content: MyUser2): Try[Array[Byte]] = { -// val json = ujson.Obj("name" -> content.name, "age" -> content.age) -// Success(ujson.transform(json, ujson.BytesRenderer()).toBytes) -// } -// } - -// // Second serializer writes the JSON manually -// implicit object UserSerializer2 extends JsonSerializer[MyUser2] { -// override def serialize(content: MyUser2): Try[Array[Byte]] = { -// val sb = new StringBuilder -// sb.append("""{"name":""") -// sb.append(content.name) -// sb.append("""","age":""") -// sb.append(content.age) -// sb.append("}") -// Success(sb.toString.getBytes(StandardCharsets.UTF_8)) -// } -// } -// } -// // #end::overloaded1[] - -// // #tag::msgpack-serializer[] -// object MsgPack { -// implicit object MsgPackSerializer extends JsonSerializer[MyUser] { -// override def serialize(content: MyUser): Try[Array[Byte]] = { -// Try({ -// // MessagePack can automatically generate equivalent code, -// // but for demonstration purposes we will do it manually -// val packer = MessagePack.newDefaultBufferPacker() -// packer.packString(content.name) -// packer.packInt(content.age) -// packer.close() -// packer.toByteArray -// }) -// } -// } - -// implicit object MsgPackDeserializer extends JsonDeserializer[MyUser] { -// override def deserialize(bytes: Array[Byte]): Try[MyUser] = { -// Try({ -// val unpacker = MessagePack.newDefaultUnpacker(bytes) -// MyUser(unpacker.unpackString(), unpacker.unpackInt()) -// }) -// } -// } -// } -// // #end::msgpack-serializer[] - -// // #tag::msgpack-transcoder[] -// class BinaryTranscoder extends TranscoderWithSerializer { -// def encode[A](value: A, serializer: JsonSerializer[A]): Try[EncodedValue] = { -// serializer -// .serialize(value) -// .map(bytes => EncodedValue(bytes, DocumentFlags.Binary)) -// } - -// def decode[A]( -// value: Array[Byte], -// flags: Int, -// serializer: JsonDeserializer[A] -// )(implicit tag: WeakTypeTag[A]): Try[A] = { -// serializer.deserialize(value) -// } -// } -// // #end::msgpack-transcoder[] - -// class Transcoding extends AnyFunSuite { - -// val cluster = Cluster.connect("localhost", "Administrator", "password").get -// val collection = cluster.bucket("default").defaultCollection - -// test("ujson") { -// // #tag::ujson-used[] -// val user = MyUser("John Smith", 27) - -// // The compiler will find our UserSerializer for this -// collection.upsert("john-smith", user) match { - -// case Success(_) => -// collection -// .get("john-smith") - -// // ... and our UserDeserializer for this -// .flatMap(fetched => fetched.contentAs[MyUser]) match { - -// case Success(fetchedUser) => -// assert(fetchedUser == user) - -// case Failure(err) => fail(s"Failed to get doc: $err") -// } - -// case Failure(err) => fail(s"Failed to upsert doc: $err") -// } -// // #end::ujson-used[] -// } - -// test("overloaded serializers") { -// // #tag::overloaded2[] -// val user = MyUser2("John Smith", 27) - -// // This import will cause the compiler to prefer UserSerializer2 -// import MyUser2.UserSerializer2 -// collection.upsert("john-smith", user).get - -// // But the application can override this -// collection.upsert("john-smith", user)(MyUser2.UserSerializer1).get -// // #end::overloaded2[] -// } - -// test("messagepack") { -// // #tag::msgpack-encode[] -// val user = MyUser("John Smith", 27) - -// // Make sure the MessagePack serializers are used -// import MsgPack._ - -// val transcoder = new BinaryTranscoder - -// // The compiler will find and use our MsgPackSerializer here -// collection.upsert( -// "john-smith", -// user, -// UpsertOptions().transcoder(transcoder) -// ) match { -// case Success(_) => - -// collection -// .get("john-smith", GetOptions().transcoder(transcoder)) - -// // ... and our MsgPackDeserializer here -// .flatMap(result => result.contentAs[MyUser]) match { - -// case Success(fetched) => -// assert(fetched == user) - -// case Failure(err) => fail(s"Failed to get or convert doc: $err") -// } - -// case Failure(err) => fail(s"Failed to upsert doc: $err") -// } -// // #end::msgpack-encode[] -// } - -// test("string") { -// // #tag::string[] -// collection.upsert( -// "doc-id", -// "hello world", -// UpsertOptions().transcoder(RawStringTranscoder.Instance) -// ) match { - -// case Success(_) => -// collection -// .get( -// "doc-id", -// GetOptions().transcoder(RawStringTranscoder.Instance) -// ) -// .flatMap(result => result.contentAs[String]) match { - -// case Success(fetched) => -// assert(fetched == "hello world") - -// case Failure(err) => fail(s"Failed to get or convert doc: $err") -// } - -// case Failure(err) => fail(s"Failed to upsert doc: $err") -// } -// // #end::string[] -// } - -// test("binary") { -// // #tag::binary[] -// val content: Array[Byte] = "hello world".getBytes(StandardCharsets.UTF_8) - -// collection.upsert( -// "doc-id", -// content, -// UpsertOptions().transcoder(RawBinaryTranscoder.Instance) -// ) match { -// case Success(_) => -// collection -// .get( -// "doc-id", -// GetOptions().transcoder(RawBinaryTranscoder.Instance) -// ) -// .flatMap(result => result.contentAs[Array[Byte]]) match { -// case Success(fetched) => -// assert(fetched(0) == 'h') -// assert(fetched(1) == 'e') -// assert(fetched(2) == 'l') -// // ... - -// case Failure(err) => fail(s"Failed to get or convert doc: $err") -// } - -// case Failure(err) => fail(s"Failed to upsert doc: $err") -// } -// // #end::binary[] -// } - -// test("json") { -// // #tag::json[] - -// val json = ujson.Obj("name" -> "John Smith", "age" -> 27) -// val bytes: Array[Byte] = ujson.transform(json, ujson.BytesRenderer()).toBytes - -// collection.upsert( -// "doc-id", -// bytes, -// UpsertOptions().transcoder(RawJsonTranscoder.Instance) -// ) match { -// case Success(_) => -// collection -// .get( -// "doc-id", -// GetOptions().transcoder(RawJsonTranscoder.Instance) -// ) -// .flatMap(result => result.contentAs[Array[Byte]]) match { -// case Success(fetched) => -// val jsonFetched = upickle.default.read[ujson.Value](fetched) -// assert(jsonFetched("name").str == "John Smith") -// assert(jsonFetched("age").num == 27) - -// case Failure(err) => fail(s"Failed to get or convert doc: $err") -// } - -// case Failure(err) => fail(s"Failed to upsert doc: $err") -// } -// // #end::json[] -// } -// } \ No newline at end of file + /* + * Copyright (c) 2020 Couchbase, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + import java.nio.charset.StandardCharsets + import com.couchbase.client.core.msg.kv.CodecFlags.CommonFlags + import com.couchbase.client.scala.Cluster + import com.couchbase.client.scala.codec._ + import com.couchbase.client.scala.kv.{GetOptions, UpsertOptions} + import org.msgpack.core.MessagePack + import org.scalatest.funsuite.AnyFunSuite + + import scala.reflect.ClassTag + import scala.reflect.runtime.universe + import scala.util.{Failure, Success, Try} + import scala.reflect.runtime.universe._ + + // #tag::ujson[] + case class MyUser(name: String, age: Int) + + object MyUser { + implicit object UserSerializer extends JsonSerializer[MyUser] { + override def serialize(content: MyUser): Try[Array[Byte]] = { + // It's also possible for uPickle to serialize and deserialize + // case classes directly to/from JSON, but for the purposes of + // demonstration we will generate the JSON manually. + val json = ujson.Obj("name" -> content.name, "age" -> content.age) + Success(ujson.transform(json, ujson.BytesRenderer()).toByteArray) + } + } + + implicit object UserDeserializer extends JsonDeserializer[MyUser] { + override def deserialize(bytes: Array[Byte]): Try[MyUser] = { + Try({ + val json = upickle.default.read[ujson.Value](bytes) + MyUser(json("name").str, json("age").num.toInt) + }) + } + } + } + // #end::ujson[] + + // #tag::overloaded1[] + case class MyUser2(name: String, age: Int) + + object MyUser2 { + // First serializer uses uPickle + implicit object UserSerializer1 extends JsonSerializer[MyUser2] { + override def serialize(content: MyUser2): Try[Array[Byte]] = { + val json = ujson.Obj("name" -> content.name, "age" -> content.age) + Success(ujson.transform(json, ujson.BytesRenderer()).toByteArray) + } + } + + // Second serializer writes the JSON manually + implicit object UserSerializer2 extends JsonSerializer[MyUser2] { + override def serialize(content: MyUser2): Try[Array[Byte]] = { + val sb = new StringBuilder + sb.append("""{"name":""") + sb.append(content.name) + sb.append("""","age":""") + sb.append(content.age) + sb.append("}") + Success(sb.toString.getBytes(StandardCharsets.UTF_8)) + } + } + } + // #end::overloaded1[] + + // #tag::msgpack-serializer[] + object MsgPack { + implicit object MsgPackSerializer extends JsonSerializer[MyUser] { + override def serialize(content: MyUser): Try[Array[Byte]] = { + Try({ + // MessagePack can automatically generate equivalent code, + // but for demonstration purposes we will do it manually + val packer = MessagePack.newDefaultBufferPacker() + packer.packString(content.name) + packer.packInt(content.age) + packer.close() + packer.toByteArray + }) + } + } + + implicit object MsgPackDeserializer extends JsonDeserializer[MyUser] { + override def deserialize(bytes: Array[Byte]): Try[MyUser] = { + Try({ + val unpacker = MessagePack.newDefaultUnpacker(bytes) + MyUser(unpacker.unpackString(), unpacker.unpackInt()) + }) + } + } + } + // #end::msgpack-serializer[] + + // #tag::msgpack-transcoder[] + class BinaryTranscoder extends TranscoderWithSerializer { + def encode[A](value: A, serializer: JsonSerializer[A]): Try[EncodedValue] = { + serializer + .serialize(value) + .map(bytes => EncodedValue(bytes, DocumentFlags.Binary)) + } + + def decode[A]( + value: Array[Byte], + flags: Int, + serializer: JsonDeserializer[A] + )(implicit tag: ClassTag[A]): Try[A] = { + serializer.deserialize(value) + } + } + // #end::msgpack-transcoder[] + + class Transcoding extends AnyFunSuite { + + val cluster = Cluster.connect("localhost", "Administrator", "password").get + val collection = cluster.bucket("default").defaultCollection + + test("ujson") { + // #tag::ujson-used[] + val user = MyUser("John Smith", 27) + + // The compiler will find our UserSerializer for this + collection.upsert("john-smith", user) match { + + case Success(_) => + collection + .get("john-smith") + + // ... and our UserDeserializer for this + .flatMap(fetched => fetched.contentAs[MyUser]) match { + + case Success(fetchedUser) => + assert(fetchedUser == user) + + case Failure(err) => fail(s"Failed to get doc: $err") + } + + case Failure(err) => fail(s"Failed to upsert doc: $err") + } + // #end::ujson-used[] + } + + test("overloaded serializers") { + // #tag::overloaded2[] + val user = MyUser2("John Smith", 27) + + // This import will cause the compiler to prefer UserSerializer2 + import MyUser2.UserSerializer2 + collection.upsert("john-smith", user).get + + // But the application can override this + collection.upsert("john-smith", user)(MyUser2.UserSerializer1).get + // #end::overloaded2[] + } + + test("messagepack") { + // #tag::msgpack-encode[] + val user = MyUser("John Smith", 27) + + // Make sure the MessagePack serializers are used + import MsgPack._ + + val transcoder = new BinaryTranscoder + + // The compiler will find and use our MsgPackSerializer here + collection.upsert( + "john-smith", + user, + UpsertOptions().transcoder(transcoder) + ) match { + case Success(_) => + + collection + .get("john-smith", GetOptions().transcoder(transcoder)) + + // ... and our MsgPackDeserializer here + .flatMap(result => result.contentAs[MyUser]) match { + + case Success(fetched) => + assert(fetched == user) + + case Failure(err) => fail(s"Failed to get or convert doc: $err") + } + + case Failure(err) => fail(s"Failed to upsert doc: $err") + } + // #end::msgpack-encode[] + } + + test("string") { + // #tag::string[] + collection.upsert( + "doc-id", + "hello world", + UpsertOptions().transcoder(RawStringTranscoder.Instance) + ) match { + + case Success(_) => + collection + .get( + "doc-id", + GetOptions().transcoder(RawStringTranscoder.Instance) + ) + .flatMap(result => result.contentAs[String]) match { + + case Success(fetched) => + assert(fetched == "hello world") + + case Failure(err) => fail(s"Failed to get or convert doc: $err") + } + + case Failure(err) => fail(s"Failed to upsert doc: $err") + } + // #end::string[] + } + + test("binary") { + // #tag::binary[] + val content: Array[Byte] = "hello world".getBytes(StandardCharsets.UTF_8) + + collection.upsert( + "doc-id", + content, + UpsertOptions().transcoder(RawBinaryTranscoder.Instance) + ) match { + case Success(_) => + collection + .get( + "doc-id", + GetOptions().transcoder(RawBinaryTranscoder.Instance) + ) + .flatMap(result => result.contentAs[Array[Byte]]) match { + case Success(fetched) => + assert(fetched(0) == 'h') + assert(fetched(1) == 'e') + assert(fetched(2) == 'l') + // ... + + case Failure(err) => fail(s"Failed to get or convert doc: $err") + } + + case Failure(err) => fail(s"Failed to upsert doc: $err") + } + // #end::binary[] + } + + test("json") { + // #tag::json[] + + val json = ujson.Obj("name" -> "John Smith", "age" -> 27) + val bytes: Array[Byte] = ujson.transform(json, ujson.BytesRenderer()).toByteArray + + collection.upsert( + "doc-id", + bytes, + UpsertOptions().transcoder(RawJsonTranscoder.Instance) + ) match { + case Success(_) => + collection + .get( + "doc-id", + GetOptions().transcoder(RawJsonTranscoder.Instance) + ) + .flatMap(result => result.contentAs[Array[Byte]]) match { + case Success(fetched) => + val jsonFetched = upickle.default.read[ujson.Value](fetched) + assert(jsonFetched("name").str == "John Smith") + assert(jsonFetched("age").num == 27) + + case Failure(err) => fail(s"Failed to get or convert doc: $err") + } + + case Failure(err) => fail(s"Failed to upsert doc: $err") + } + // #end::json[] + } + } \ No newline at end of file diff --git a/modules/devguide/examples/scala/pom.xml b/modules/devguide/examples/scala/pom.xml index c555a912..b271eaaf 100644 --- a/modules/devguide/examples/scala/pom.xml +++ b/modules/devguide/examples/scala/pom.xml @@ -2,6 +2,6 @@ com.couchbase.client scala-client_2.13 - 1.8.2 + 3.9.0 diff --git a/modules/hello-world/pages/overview.adoc b/modules/hello-world/pages/overview.adoc index bb37f8e7..df14fad0 100644 --- a/modules/hello-world/pages/overview.adoc +++ b/modules/hello-world/pages/overview.adoc @@ -76,6 +76,11 @@ Install the SDK, and explore in the way that works best for you. libraryDependencies += "com.couchbase.client" %% "scala-client" % "{sdk_current_version}" ---- +The Scala SDK is provided with builds for Scala 2.12, 2.13 and 3. +`%%` takes care of selecting the right version in Scala Build Tool. +If you are using another build tool such as Maven or Gradle, then specify `scala-client_2.12`, `scala-client_2.13` or `scala-client_3` as appropriate. +The Scala 3 build can be used from applications compiled with Scala 3.3 through 3.7 inclusive, and even Scala 2.13. +It is the recommended build for all users, except those on 2.12. The links below will take you where you want to go -- as will the navigation on the left-hand side of this page. But if you don't know exactly where you need to go, try one of the following: diff --git a/modules/howtos/pages/analytics-using-sdk.adoc b/modules/howtos/pages/analytics-using-sdk.adoc index c2682058..386a1a84 100644 --- a/modules/howtos/pages/analytics-using-sdk.adoc +++ b/modules/howtos/pages/analytics-using-sdk.adoc @@ -18,6 +18,7 @@ This is the analytic counterpart to our xref:sqlpp-queries-with-sdk.adoc[operati The analytics service is available in xref:cloud:clusters:analytics-service/analytics-service.adoc[Capella operational] or the Enterprise Edition of self-managed Couchbase Server. +NOTE: The Scala 3 version of the SDK does not carry forward support for analytics. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. == Getting Started diff --git a/modules/howtos/pages/concurrent-async-apis.adoc b/modules/howtos/pages/concurrent-async-apis.adoc index 2b96a02e..a2f9baa2 100644 --- a/modules/howtos/pages/concurrent-async-apis.adoc +++ b/modules/howtos/pages/concurrent-async-apis.adoc @@ -16,6 +16,7 @@ The Scala SDK provides three APIs, which can be freely mixed: * An asynchronous one, that returns `Future`. * A reactive one, that returns reactive primitives from the https://projectreactor.io/[Project Reactor] library, e.g. `Mono` and `Flux`. +NOTE: The Scala 3 version of the SDK does not carry forward support for the reactive API, since it depends on an external library that is end-of-life. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. == Using the Blocking API diff --git a/modules/howtos/pages/json.adoc b/modules/howtos/pages/json.adoc index 1d448b53..a27458e0 100644 --- a/modules/howtos/pages/json.adoc +++ b/modules/howtos/pages/json.adoc @@ -22,6 +22,7 @@ There's a wide range of great, popular JSON libaries for the JVM, and we've supp That is, you can use types from the https://circe.github.io/circe/[Circe library], and many others, when doing any operation. And if we're missing support for your favourite, then please let us know on the http://forums.couchbase.com/[forums]. +NOTE: The Scala 3 version of the SDK does not carry forward direct support for the external JSON libraries, though it remains easy to integrate with them. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. == Optional Dependencies @@ -196,6 +197,8 @@ Note that `JsonObjectSafe`, though presenting a more functional interface, is st It can be very useful to deal directly with Scala case classes, that is to send and retrieve them directly rather than via some interim type, and the Scala SDK includes built-in support for this. +NOTE: The Scala 3 version of the SDK does not carry forward direct support for case classes, though it remains easy to use them directly via libraries such as Jsoniter and Circe. Please contact us if this is functionality that is useful to you. + It's necessary to write a small amount of boilerplate code first. If you try and insert a case class directly, you'll get an error. E.g. this won't work: diff --git a/modules/howtos/pages/kv-operations.adoc b/modules/howtos/pages/kv-operations.adoc index 952d3d19..c9744a5c 100644 --- a/modules/howtos/pages/kv-operations.adoc +++ b/modules/howtos/pages/kv-operations.adoc @@ -65,6 +65,8 @@ You can also directly encode and decode Scala case classes to and from the SDK. To make things easy and to help get you started, the Scala SDK also bundles a home-grown small JSON library, which you are free to use instead of or alongside any of the other supported JSON libraries. The philosophy behind this library is to provide a very easy-to-use API and the fastest JSON implementation possible. +NOTE: The Scala 3 version of the SDK removes direct support for external JSON libraries and direct handling of case classes, though it remains easy to integrate both. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. + === Using JsonObject and JsonArray Using the built-in JSON library here's how to create some simple JSON: @@ -124,7 +126,7 @@ include::devguide:example$scala/KvOperations.scala[indent=0,tag=upsert] [NOTE] ===== All the examples here use the Scala SDK's simplest API, which blocks until the operation is performed. There's also an asynchronous API that is based around Scala `Future`, and a -reactive API. See xref:concurrent-async-apis[Choosing an API] for more details. +reactive API. See xref:concurrent-async-apis#choosing-an-api[Choosing an API] for more details. ===== == Handling Single Errors diff --git a/modules/howtos/pages/sqlpp-queries-with-sdk.adoc b/modules/howtos/pages/sqlpp-queries-with-sdk.adoc index 36f24e4d..8eabec5e 100644 --- a/modules/howtos/pages/sqlpp-queries-with-sdk.adoc +++ b/modules/howtos/pages/sqlpp-queries-with-sdk.adoc @@ -134,6 +134,7 @@ Here's how to perform a query and stream the results using the reactive API: include::devguide:example$scala/Queries.scala[tag=reactive,indent=0] ---- +NOTE: The Scala 3 version of the SDK does not carry forward support for the reactive API, since it depends on an external library that is end-of-life. Please see xref:project-docs:migrating-to-scala-3.adoc[Migrating to Scala 3] for guidance on these and other changes. == Querying at Scope Level diff --git a/modules/howtos/pages/subdocument-operations.adoc b/modules/howtos/pages/subdocument-operations.adoc index a35bbf75..329f8e78 100644 --- a/modules/howtos/pages/subdocument-operations.adoc +++ b/modules/howtos/pages/subdocument-operations.adoc @@ -112,25 +112,6 @@ Multiple operations can be combined, and this can be most neatly done with a for include::devguide:example$scala/SubDocument.scala[tag=combine] ---- - -== Choosing an API - -The Scala SDK provides three APIs for all operations. There's the simple blocking one you've already seen, then this -asynchronous variant that returns Scala `Future`: - -[source,scala] ----- -include::devguide:example$scala/SubDocument.scala[tag=get-future] ----- - -And a third that uses reactive programming primitives from https://projectreactor.io/[Project Reactor]: - -[source,scala] ----- -include::devguide:example$scala/SubDocument.scala[tag=get-reactive] ----- - - == Mutating Mutation operations modify one or more paths in the document. diff --git a/modules/project-docs/pages/compatibility.adoc b/modules/project-docs/pages/compatibility.adoc index 92202693..dc3dba16 100644 --- a/modules/project-docs/pages/compatibility.adoc +++ b/modules/project-docs/pages/compatibility.adoc @@ -131,19 +131,24 @@ It is best to upgrade either the SDK or the Couchbase version you are using. .Recommended SDK per Server Version Matrix [#table_sdk_versions] |=== -| | 1.4, 1.5 | 1.6, 1.7 | 1.8 +| | 1.4, 1.5 | 1.6, 1.7 | 1.8 | 3.9 | *Server 7.0 - 7.2* | *✔* | *✔* | *✔* +| *✔* | *Server 7.6* ① | *✔* | *✔* | *✔* +| *✔* |=== +Note that from 3.9.0 on, all Couchbase JVM SDKs have an aligned version number to make it easier to users to track changes. +So the version has jumped from 1.8.x to 3.9.x. + ① Server 7.6 is compatible with all supported (not yet End-of-Life) versions of the {name-sdk}, but for full support of the latest features you need to upgrade to a recent version of the SDK. See the <<#couchbase-new-feature-availability-matrix,Feature Availablity matrix below>> and the xref:sdk-release-notes.adoc[Release Notes page]. diff --git a/modules/project-docs/pages/migrating-to-scala-3.adoc b/modules/project-docs/pages/migrating-to-scala-3.adoc new file mode 100644 index 00000000..2261b9c6 --- /dev/null +++ b/modules/project-docs/pages/migrating-to-scala-3.adoc @@ -0,0 +1,19 @@ += Differences between Scala 2 and 3 SDK versions +:description: The Scala 3 version of the SDK has some differences from the Scala 2 versions. But we expect the majority of Scala 2 applications will be able to migrate to Scala 3 and the Scala 3 version of the SDK with no-to-minimal differences. + +:nav-title: Migrating to Scala SDK 3.x API +:page-aliases: howtos:view-queries-with-sdk.adoc,concept-docs:understanding-views.adoc,howtos:working-with-collections.adoc + +[abstract] +{description} + +If any of the following changes impact you as either an existing Scala 2 SDK user or an interested Scala 3 user, then please let us know. +For some of these migrations we can offer alternative approaches, or discuss re-adding desired functionality. + +The Scala 3 version of the SDK: + +* Does not carry forward support for the Reactor API, as the Scala Reactor Extensions library that it depends on is long end-of-life. The `Future` API offers most of the same functionality while being considerably easier to use, and more Scala-centric. It is also easily adapted to other Scala libraries such as Cats Effect and ZIO. +* Does not carry forward the built-in support for JSON libraries (Circe, json4s etc), as this has increasingly become problematic to maintain as those libraries migrate to new majors. +* Does not carry forward support for views, analytics, replica reads, management operations, and datastructures. +* There are two overloads for most API methods, one that takes an options block, and one that tries to be a convenience method offering a few popular settings. The latter is removed in the Scala 3 version, significantly simplifying the API. + diff --git a/modules/project-docs/pages/sdk-release-notes.adoc b/modules/project-docs/pages/sdk-release-notes.adoc index 2222ed38..fb1e0293 100644 --- a/modules/project-docs/pages/sdk-release-notes.adoc +++ b/modules/project-docs/pages/sdk-release-notes.adoc @@ -24,13 +24,61 @@ include::hello-world:start-using-sdk.adoc[tag=quick-install] include::{version-common}@sdk:pages:partial$signed.adoc[tag=signed] -(3.8.2 is the JVM core of Scala SDK 1.8.2.) +(3.9.0 is the JVM core of Scala SDK 3.9.0.) // reminder - add spoiler tag to wrappers for cxx notes? [#latest-release] +== Scala SDK 3.9 Releases + +Version 3.9 of the Scala SDK implements the 3.8 xref:compatibility.adoc#api-version[SDK API]. +See the xref:compatibility.adoc#couchbase-feature-availability-matrix[compatibility pages] for more information on feature compatibility with different versions of Couchbase Server. + +Note that from 3.9.0 on, all Couchbase JVM SDKs have an aligned version number to make it easier to users to track changes, and so the Scala SDK version has jumped from 1.8.x to 3.9.x. + +While this is technically an increase in the major, the Scala 2 versions of the SDK have only one very small breaking change made to the API, to remove a long-deprecated transactions method. All other functionality works unchanged and users can upgrade existing applications without issue. + +The major new feature in the 3.9 series is the addition of a new version of the SDK, built for Scala 3. +The Scala 3 version of the SDK has some API differences, documented on xref:project-docs:migrating-to-scala-3.adoc[migrating to Scala 3]. +But we expect the majority of Scala 2 applications will be able to migrate to Scala 3 and the Scala 3 version of the SDK with no-to-minimal differences. + +We always recommend using the latest version of the SDK -- it contains all of the latest security patches and support for new and upcoming features. +All patch releases for each dot minor release should be API compatible, and safe to upgrade; +any changes to expected behavior are noted in the release notes that follow. + +Binary compatibility is not guaranteed for any Scala SDK release, and you should rebuild your application when changing the SDK. + +//// +# get version numbers like this +src=../couchbase-jvm-clients +grep '' $src/pom.xml +grep ' ' $src/pom.xml +//// + +=== Version 3.9.0 (August 2025) + +https://docs.couchbase.com/sdk-api/couchbase-scala-client-3.9.0/com/couchbase/client/scala/index.html[API Reference] | +http://docs.couchbase.com/sdk-api/couchbase-core-io-3.9.0/[Core API Reference] + +Note that the API reference is for the Scala 2.12 version of the SDK. +The Scala 3 version of the SDK has some API differences, documented on xref:project-docs:migrating-to-scala-3.adoc[migrating to Scala 3]. + +The supported and tested dependencies for this release are: + +* io.projectreactor:**reactor-core:3.6.9** +* org.reactivestreams:**reactive-streams:1.0.4** + +==== Improvements + +// TODO: for release captain! + +==== Bug Fixes + +// TODO: for release captain! + + == Scala SDK 1.8 Releases Version 1.8 of the Scala SDK implements the 3.7 xref:compatibility.adoc#api-version[SDK API]. From 97a9da23c005b2694e9b164f2045b56a37e6ee1d Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 09:51:32 +0100 Subject: [PATCH 2/7] Oxford comma --- modules/hello-world/pages/overview.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/hello-world/pages/overview.adoc b/modules/hello-world/pages/overview.adoc index df14fad0..fdcdea97 100644 --- a/modules/hello-world/pages/overview.adoc +++ b/modules/hello-world/pages/overview.adoc @@ -76,9 +76,9 @@ Install the SDK, and explore in the way that works best for you. libraryDependencies += "com.couchbase.client" %% "scala-client" % "{sdk_current_version}" ---- -The Scala SDK is provided with builds for Scala 2.12, 2.13 and 3. +The Scala SDK is provided with builds for Scala 2.12, 2.13, and 3. `%%` takes care of selecting the right version in Scala Build Tool. -If you are using another build tool such as Maven or Gradle, then specify `scala-client_2.12`, `scala-client_2.13` or `scala-client_3` as appropriate. +If you are using another build tool such as Maven or Gradle, then specify `scala-client_2.12`, `scala-client_2.13`, or `scala-client_3`, as appropriate. The Scala 3 build can be used from applications compiled with Scala 3.3 through 3.7 inclusive, and even Scala 2.13. It is the recommended build for all users, except those on 2.12. From 97a317e1efb398c994f465cefc2b8d7e01d55703 Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 09:53:59 +0100 Subject: [PATCH 3/7] monospace --- modules/howtos/pages/json.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/howtos/pages/json.adoc b/modules/howtos/pages/json.adoc index a27458e0..3e4b38d8 100644 --- a/modules/howtos/pages/json.adoc +++ b/modules/howtos/pages/json.adoc @@ -197,7 +197,8 @@ Note that `JsonObjectSafe`, though presenting a more functional interface, is st It can be very useful to deal directly with Scala case classes, that is to send and retrieve them directly rather than via some interim type, and the Scala SDK includes built-in support for this. -NOTE: The Scala 3 version of the SDK does not carry forward direct support for case classes, though it remains easy to use them directly via libraries such as Jsoniter and Circe. Please contact us if this is functionality that is useful to you. +NOTE: The Scala 3 version of the SDK does not carry forward direct support for case classes, though it remains easy to use them directly via libraries such as `Jsoniter` and `Circe`. +Please contact us if this is functionality that is useful to you. It's necessary to write a small amount of boilerplate code first. If you try and insert a case class directly, you'll get an error. From 65de5c542d27f5ad940b9bfcbdc01516d589ace4 Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 09:57:04 +0100 Subject: [PATCH 4/7] Merge conflict fix fix --- modules/project-docs/pages/sdk-release-notes.adoc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/project-docs/pages/sdk-release-notes.adoc b/modules/project-docs/pages/sdk-release-notes.adoc index e9d85edb..86235be8 100644 --- a/modules/project-docs/pages/sdk-release-notes.adoc +++ b/modules/project-docs/pages/sdk-release-notes.adoc @@ -80,11 +80,6 @@ The supported and tested dependencies for this release are: -== Scala SDK 1.8 Releases - -Version 1.8 of the Scala SDK implements the 3.7 xref:compatibility.adoc#api-version[SDK API]. - - == Scala SDK 1.8 Releases Version 1.8 of the Scala SDK implements the 3.7 xref:compatibility.adoc#api-version[SDK API]. From db1354eaf2c541f72aa348e08a923537f042cc5f Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 10:01:06 +0100 Subject: [PATCH 5/7] Gardening --- .../project-docs/pages/migrating-to-scala-3.adoc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/project-docs/pages/migrating-to-scala-3.adoc b/modules/project-docs/pages/migrating-to-scala-3.adoc index 2261b9c6..a8a1cbaa 100644 --- a/modules/project-docs/pages/migrating-to-scala-3.adoc +++ b/modules/project-docs/pages/migrating-to-scala-3.adoc @@ -1,19 +1,22 @@ = Differences between Scala 2 and 3 SDK versions -:description: The Scala 3 version of the SDK has some differences from the Scala 2 versions. But we expect the majority of Scala 2 applications will be able to migrate to Scala 3 and the Scala 3 version of the SDK with no-to-minimal differences. - +:description: The Scala 3 version of the SDK has some differences from the Scala 2 versions. :nav-title: Migrating to Scala SDK 3.x API :page-aliases: howtos:view-queries-with-sdk.adoc,concept-docs:understanding-views.adoc,howtos:working-with-collections.adoc [abstract] {description} +But we expect the majority of Scala 2 applications will be able to migrate to Scala 3 and the Scala 3 version of the SDK with minimal or even no differences. + If any of the following changes impact you as either an existing Scala 2 SDK user or an interested Scala 3 user, then please let us know. For some of these migrations we can offer alternative approaches, or discuss re-adding desired functionality. The Scala 3 version of the SDK: -* Does not carry forward support for the Reactor API, as the Scala Reactor Extensions library that it depends on is long end-of-life. The `Future` API offers most of the same functionality while being considerably easier to use, and more Scala-centric. It is also easily adapted to other Scala libraries such as Cats Effect and ZIO. -* Does not carry forward the built-in support for JSON libraries (Circe, json4s etc), as this has increasingly become problematic to maintain as those libraries migrate to new majors. +* Does not carry forward support for the Reactor API, as the Scala Reactor Extensions library that it depends on is long end-of-life. +The `Future` API offers most of the same functionality while being considerably easier to use, and more Scala-centric. +It is also easily adapted to other Scala libraries such as Cats Effect and ZIO. +* Does not carry forward the built-in support for JSON libraries (`Circe`, `json4s`, etc.), as this has increasingly become problematic to maintain as those libraries migrate to new majors. * Does not carry forward support for views, analytics, replica reads, management operations, and datastructures. -* There are two overloads for most API methods, one that takes an options block, and one that tries to be a convenience method offering a few popular settings. The latter is removed in the Scala 3 version, significantly simplifying the API. - +* There are two overloads for most API methods, one that takes an options block, and one that tries to be a convenience method offering a few popular settings. +The latter is removed in the Scala 3 version, significantly simplifying the API. From e004bb9137a8693424d039329262cead8c2d9c8c Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 10:07:15 +0100 Subject: [PATCH 6/7] migration --- modules/ROOT/nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index b2aa3821..bccd6ae8 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -56,7 +56,7 @@ ** xref:project-docs:sdk-release-notes.adoc[Release Notes] *** https://docs-archive.couchbase.com/home/index.html[Older Versions Archive^] ** xref:project-docs:compatibility.adoc[] -*** xref:project-docs:migrating-sdk-code-to-3.n.adoc[] +*** xref:project-docs:migrating-to-scala-3.adoc[] // *** xref:project-docs:distributed-acid-transactions-migration-guide.adoc[] *** xref:project-docs:third-party-integrations.adoc[] ** xref:project-docs:sdk-full-installation.adoc[] From d2c4479fa42cb564dc52e68ca0053307c859ddec Mon Sep 17 00:00:00 2001 From: Richard Smedley Date: Mon, 4 Aug 2025 10:09:14 +0100 Subject: [PATCH 7/7] Alias form old migration page --- modules/project-docs/pages/migrating-to-scala-3.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project-docs/pages/migrating-to-scala-3.adoc b/modules/project-docs/pages/migrating-to-scala-3.adoc index a8a1cbaa..e026321a 100644 --- a/modules/project-docs/pages/migrating-to-scala-3.adoc +++ b/modules/project-docs/pages/migrating-to-scala-3.adoc @@ -1,7 +1,7 @@ = Differences between Scala 2 and 3 SDK versions :description: The Scala 3 version of the SDK has some differences from the Scala 2 versions. :nav-title: Migrating to Scala SDK 3.x API -:page-aliases: howtos:view-queries-with-sdk.adoc,concept-docs:understanding-views.adoc,howtos:working-with-collections.adoc +:page-aliases: migrating-sdk-code-to-3.n.adoc,howtos:view-queries-with-sdk.adoc,concept-docs:understanding-views.adoc,howtos:working-with-collections.adoc [abstract] {description}