-
Notifications
You must be signed in to change notification settings - Fork 637
Couchbase: Updates the integration to support collections #3396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chedim
wants to merge
11
commits into
akka:main
Choose a base branch
from
chedim:3395-update-couchbase
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
a938d56
Updates Couchbase integration to support collections; Updates Couchba…
chedim 9e40ece
Brings back concurrency settings, updates documentation comments
chedim 824b61b
Fixes issues with transcoder selection and optimizes the code per PR …
chedim 982701c
Updates the integration to not use tuples and addresses other PR comm…
chedim 8ab5baf
formalities
ennru 228f14b
scalafmt
ennru 55c35fc
Updates to couchbase integration:
chedim 8ddfda8
scalafmt and fix for JSON tests
chedim ec0bbed
Addresses PR comments for Couchbase integration
chedim a1c239e
Fixes a missed diamond in CouchbaseExamplesTest
chedim 9d75f8d
mima filter for everything couchbase
johanandren File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
...se/src/main/scala/akka/stream/alpakka/couchbase/impl/CouchbaseCollectionSessionImpl.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| package akka.stream.alpakka.couchbase.impl | ||
|
|
||
| import akka.NotUsed | ||
| import akka.annotation.InternalApi | ||
| import akka.stream.alpakka.couchbase.scaladsl.{CouchbaseCollectionSession, CouchbaseSession} | ||
| import akka.stream.scaladsl.Source | ||
| import com.couchbase.client.java.codec.{JsonTranscoder, RawBinaryTranscoder, RawStringTranscoder, Transcoder} | ||
| import com.couchbase.client.java.json.JsonValue | ||
| import com.couchbase.client.java.kv._ | ||
| import com.couchbase.client.java.manager.query.{CreateQueryIndexOptions, QueryIndex} | ||
| import com.couchbase.client.java.{AsyncCollection, AsyncScope} | ||
| import rx.{Observable, RxReactiveStreams} | ||
|
|
||
| import java.util | ||
| import java.util.concurrent.TimeUnit | ||
| import scala.concurrent.{ExecutionContext, Future} | ||
| import scala.concurrent.duration.FiniteDuration | ||
| import scala.jdk.FutureConverters.CompletionStageOps | ||
|
|
||
| @InternalApi | ||
| class CouchbaseCollectionSessionImpl(bucketSession: CouchbaseSession, scopeName: String, collectionName: String) extends CouchbaseCollectionSession{ | ||
chedim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| override def bucket: CouchbaseSession = bucketSession | ||
|
|
||
| override def scope: AsyncScope = bucket.underlying.scope(scopeName) | ||
| override def underlying: AsyncCollection = scope.collection(collectionName) | ||
|
|
||
| override def asJava = new CouchbaseCollectionSessionJavaAdapter(this) | ||
|
|
||
| override def insert[T] (document: (String, T)): Future[(String, T)] = { | ||
| underlying | ||
| .insert(document._1, document._2, | ||
| InsertOptions.insertOptions() | ||
| .transcoder(chooseTranscoder(document._2.getClass)) | ||
| ) | ||
| .asScala.map(_ => document)(ExecutionContext.parasitic) | ||
chedim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| override def insert[T] (document: (String, T), insertOptions: InsertOptions): Future[(String, T)] = { | ||
| underlying.insert(document._1, document._2, insertOptions) | ||
| .asScala | ||
| .map(r => document)(ExecutionContext.parasitic) | ||
| } | ||
|
|
||
| override def get[T](id: String, target: Class[T]): Future[(String, T)] = { | ||
| underlying.get(id, GetOptions.getOptions.transcoder(chooseTranscoder(target))) | ||
| .thenApply(gr => { | ||
| (id, gr.contentAs(target)) | ||
| }) | ||
| .asScala | ||
|
|
||
| } | ||
|
|
||
| override def getDocument(id: String): Future[(String, JsonValue)] = | ||
| underlying.get(id).thenApply(gr => (id, asJsonValue(gr))).asScala | ||
|
|
||
| private def asJsonValue(gr: GetResult) = | ||
| try { | ||
| gr.contentAsObject().asInstanceOf[JsonValue] | ||
| } catch { | ||
| case ex: Exception => gr.contentAsArray().asInstanceOf[JsonValue] | ||
| } | ||
|
|
||
| override def getBytes(id: String): Future[(String, Array[Byte])] = | ||
| underlying.get(id).thenApply(gr => (id, gr.contentAsBytes())).asScala | ||
|
|
||
| override def getDocument(id: String, timeout: FiniteDuration): Future[(String, JsonValue)] = | ||
| underlying.get(id) | ||
| .orTimeout(timeout.toMillis, TimeUnit.MILLISECONDS) | ||
| .thenApply(gr => (id, asJsonValue(gr))) | ||
| .asScala | ||
|
|
||
| override def getBytes(id: String, timeout: FiniteDuration): Future[(String, Array[Byte])] = | ||
| underlying.get(id) | ||
| .orTimeout(timeout.toMillis, TimeUnit.MILLISECONDS) | ||
| .thenApply(gr => (id, gr.contentAsBytes())) | ||
| .asScala | ||
|
|
||
| override def upsert[T](document: (String, T)): Future[(String, T)] = { | ||
| underlying.upsert(document._1, document._2, | ||
| UpsertOptions.upsertOptions() | ||
| .transcoder(chooseTranscoder(document._2.getClass)) | ||
| ) | ||
| .asScala.map(_ => document)(ExecutionContext.parasitic) | ||
| } | ||
|
|
||
| override def upsert[T](document: (String, T), upsertOptions: UpsertOptions): Future[(String, T)] = | ||
| underlying.upsert(document._1, document._2, upsertOptions) | ||
| .thenApply(_ => document) | ||
| .asScala | ||
|
|
||
| override def upsert[T](document: (String, T), upsertOptions: UpsertOptions, timeout: FiniteDuration): Future[(String, T)] = | ||
| underlying.upsert(document._1, document._2, upsertOptions) | ||
| .orTimeout(timeout.toMillis, TimeUnit.MILLISECONDS) | ||
| .thenApply(_ => document) | ||
| .asScala | ||
|
|
||
|
|
||
| override def replace[T](document: (String, T)): Future[(String, T)] = | ||
| underlying.replace(document._1, document._2) | ||
| .thenApply(_ => document) | ||
| .asScala | ||
|
|
||
| override def replace[T](document: (String, T), replaceOptions: ReplaceOptions): Future[(String, T)] = | ||
| underlying.replace(document._1, document._2, replaceOptions) | ||
| .thenApply(_ => document) | ||
| .asScala | ||
|
|
||
| override def replace[T](document: (String, T), replaceOptions: ReplaceOptions, timeout: FiniteDuration): Future[(String, T)] = | ||
| underlying.replace(document._1, document._2, replaceOptions) | ||
| .orTimeout(timeout.toMillis, TimeUnit.MILLISECONDS) | ||
| .thenApply(_ => document) | ||
| .asScala | ||
|
|
||
| override def remove(id: String): Future[String] = | ||
| underlying.remove(id) | ||
| .thenApply(_ => id) | ||
| .asScala | ||
|
|
||
| override def remove(id: String, removeOptions: RemoveOptions): Future[String] = | ||
| underlying.remove(id, removeOptions) | ||
| .thenApply(_ => id) | ||
| .asScala | ||
|
|
||
| override def remove(id: String, removeOptions: RemoveOptions, timeout: FiniteDuration): Future[String] = | ||
| underlying.remove(id, removeOptions) | ||
| .orTimeout(timeout.toMillis, TimeUnit.MILLISECONDS) | ||
| .thenApply(_ => id) | ||
| .asScala | ||
|
|
||
| override def createIndex(indexName: String, createQueryIndexOptions: CreateQueryIndexOptions, fields: String*): Future[Void] = | ||
chedim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| underlying | ||
| .queryIndexes() | ||
| .createIndex(indexName, util.Arrays.asList(fields: _*), createQueryIndexOptions) | ||
| .asScala | ||
|
|
||
| override def listIndexes(): Source[QueryIndex, NotUsed] = | ||
| Source.fromPublisher( | ||
| RxReactiveStreams.toPublisher( | ||
| Observable.from(underlying.queryIndexes().getAllIndexes()) | ||
| .flatMap(indexes => Observable.from(indexes)) | ||
| ) | ||
| ) | ||
|
|
||
| private def chooseTranscoder[T](target: Class[T]): Transcoder = | ||
| target match { | ||
| case _: Class[Array[Byte]] => RawBinaryTranscoder.INSTANCE | ||
| case _: Class[String] => RawStringTranscoder.INSTANCE | ||
chedim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| case _ => bucketSession.cluster().environment().transcoder() | ||
| } | ||
| } | ||
190 changes: 190 additions & 0 deletions
190
...main/scala/akka/stream/alpakka/couchbase/impl/CouchbaseCollectionSessionJavaAdapter.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| /* | ||
| * Copyright (C) since 2016 Lightbend Inc. <https://www.lightbend.com> | ||
| */ | ||
|
|
||
| package akka.stream.alpakka.couchbase.impl | ||
|
|
||
| import akka.NotUsed | ||
| import akka.annotation.InternalApi | ||
| import akka.stream.alpakka.couchbase.javadsl.CouchbaseSession | ||
| import akka.stream.alpakka.couchbase.{javadsl, scaladsl} | ||
| import akka.stream.javadsl.Source | ||
| import com.couchbase.client.java.json.{JsonArray, JsonObject, JsonValue} | ||
| import com.couchbase.client.java.kv.{InsertOptions, RemoveOptions, ReplaceOptions, UpsertOptions} | ||
| import com.couchbase.client.java.manager.query.{CreateQueryIndexOptions, QueryIndex} | ||
| import com.couchbase.client.java.{AsyncCollection, AsyncScope} | ||
|
|
||
| import java.time.Duration | ||
| import java.util.concurrent.{CompletionStage, TimeUnit} | ||
| import scala.concurrent.duration.FiniteDuration | ||
| import scala.jdk.FutureConverters._ | ||
|
|
||
| /** | ||
| * INTERNAL API | ||
| */ | ||
| @InternalApi | ||
| private[couchbase] final class CouchbaseCollectionSessionJavaAdapter(delegate: scaladsl.CouchbaseCollectionSession) | ||
| extends javadsl.CouchbaseCollectionSession { | ||
|
|
||
| override def asScala: scaladsl.CouchbaseCollectionSession = delegate | ||
|
|
||
| override def bucket: CouchbaseSession = delegate.bucket.asJava | ||
|
|
||
| override def scope: AsyncScope = delegate.scope | ||
|
|
||
| override def underlying: AsyncCollection = delegate.underlying | ||
|
|
||
| /** | ||
| * Insert a JSON document using the default write settings | ||
| * | ||
| * @param document A tuple where first element is id of the document and second is its value | ||
| * @return A Future that completes with the id of the written document when the write is done | ||
| */ | ||
| override def insert[T](document: (String, T)): CompletionStage[(String, T)] = | ||
| delegate.insert(document).asJava | ||
|
|
||
| override def insert[T](document: (String, T), insertOptions: InsertOptions): CompletionStage[(String, T)] = | ||
| delegate.insert(document, insertOptions).asJava | ||
|
|
||
| override def getJsonObject(id: String): CompletionStage[(String, JsonObject)] = | ||
| delegate.getJsonObject(id).asJava | ||
|
|
||
| override def getJsonArray(id: String): CompletionStage[(String, JsonArray)] = | ||
| delegate.getJsonArray(id).asJava | ||
|
|
||
| override def get[T](id: String, target: Class[T]): CompletionStage[(String, T)] = | ||
| delegate.get(id, target).asJava | ||
| /** | ||
| * @return A document if found or none if there is no document for the id | ||
| */ | ||
| override def getDocument(id: String): CompletionStage[(String, JsonValue)] = | ||
| delegate.getDocument(id).asJava | ||
|
|
||
| /** | ||
| * @param id Identifier of the document to fetch | ||
| * @return Raw data for the document or none | ||
| */ | ||
| override def getBytes(id: String): CompletionStage[(String, Array[Byte])] = | ||
| delegate.getBytes(id).asJava | ||
|
|
||
| /** | ||
| * @param timeout fail the returned future with a TimeoutException if it takes longer than this | ||
| * @return A document if found or none if there is no document for the id | ||
| */ | ||
| override def getDocument(id: String, timeout: Duration): CompletionStage[(String, JsonValue)] = | ||
| delegate.getDocument(id, FiniteDuration.apply(timeout.toNanos, TimeUnit.NANOSECONDS)).asJava | ||
|
|
||
| /** | ||
| * @return A raw document data if found or none if there is no document for the id | ||
| */ | ||
| override def getBytes(id: String, timeout: Duration): CompletionStage[(String, Array[Byte])] = | ||
| delegate.getBytes(id, FiniteDuration.apply(timeout.toNanos, TimeUnit.NANOSECONDS)).asJava | ||
|
|
||
| /** | ||
| * Upsert using the default write settings. | ||
| * | ||
| * @return a future that completes when the upsert is done | ||
| */ | ||
| override def upsert[T](document: (String, T)): CompletionStage[(String, T)] = | ||
| delegate.upsert(document).asJava | ||
|
|
||
| /** | ||
| * Upsert using the given write settings | ||
| * | ||
| * Separate from `upsert` to make the most common case smoother with the type inference | ||
| * | ||
| * @return a future that completes when the upsert is done | ||
| */ | ||
| override def upsert[T](document: (String, T), upsertOptions: UpsertOptions): CompletionStage[(String, T)] = | ||
| delegate.upsert(document, upsertOptions).asJava | ||
|
|
||
| /** | ||
| * Upsert using given write settings and timeout | ||
| * | ||
| * @param document document id and value to upsert | ||
| * @param upsertOptions Couchbase UpsertOptions | ||
| * @param timeout timeout for the operation | ||
| * @return the document id and value | ||
| */ | ||
| override def upsert[T](document: (String, T), upsertOptions: UpsertOptions, timeout: Duration): CompletionStage[(String, T)] = | ||
| delegate.upsert(document, upsertOptions, FiniteDuration.apply(timeout.toNanos, TimeUnit.NANOSECONDS)).asJava | ||
|
|
||
| /** | ||
| * Replace using the default write settings. | ||
| * | ||
| * For replacing other types of documents see `replaceDoc`. | ||
| * | ||
| * @return a future that completes when the replace is done | ||
| */ | ||
| override def replace[T](document: (String, T)): CompletionStage[(String, T)] = | ||
| delegate.replace(document).asJava | ||
|
|
||
| /** | ||
| * Replace using the given replace options | ||
| * | ||
| * For replacing other types of documents see `replaceDoc`. | ||
| * | ||
| * @return a future that completes when the replace is done | ||
| */ | ||
| override def replace[T](document: (String, T), replaceOptions: ReplaceOptions): CompletionStage[(String, T)] = | ||
| delegate.replace(document, replaceOptions).asJava | ||
|
|
||
| /** | ||
| * Replace using write settings and timeout | ||
| * | ||
| * @param document document id and value to replace | ||
| * @param replaceOptions Couchbase replace options | ||
| * @param timeout timeout for the operation | ||
| * @return the document id and value | ||
| */ | ||
| override def replace[T](document: (String, T), replaceOptions: ReplaceOptions, timeout: Duration): CompletionStage[(String, T)] = | ||
| delegate.replace(document, replaceOptions, FiniteDuration.apply(timeout.toNanos, TimeUnit.NANOSECONDS)).asJava | ||
|
|
||
| /** | ||
| * Remove a document by id using the default write settings. | ||
| * | ||
| * @return Future that completes when the document has been removed, if there is no such document | ||
| * the future is failed with a `DocumentDoesNotExistException` | ||
| */ | ||
| override def remove(id: String): CompletionStage[String] = | ||
| delegate.remove(id).asJava | ||
|
|
||
| /** | ||
| * Remove a document by id using the remove settings. | ||
| * | ||
| * @return Future that completes when the document has been removed, if there is no such document | ||
| * the future is failed with a `DocumentDoesNotExistException` | ||
| */ | ||
| override def remove(id: String, removeOptions: RemoveOptions): CompletionStage[String] = | ||
| delegate.remove(id, removeOptions).asJava | ||
|
|
||
| /** | ||
| * Removes document with given id, remove options and timeout | ||
| * | ||
| * @param id id of the document to remove | ||
| * @param removeOptions Couchbase remove options | ||
| * @param timeout timeout | ||
| * @return the id | ||
| */ | ||
| override def remove(id: String, removeOptions: RemoveOptions, timeout: Duration) = | ||
| delegate.remove(id, removeOptions, FiniteDuration.apply(timeout.toNanos, TimeUnit.NANOSECONDS)).asJava | ||
|
|
||
| /** | ||
| * Create a secondary index for the current collection. | ||
| * | ||
| * @param indexName the name of the index. | ||
| * @param createQueryIndexOptions Couchbase index options | ||
| * @param fields the JSON fields to index | ||
| * @return a [[scala.concurrent.Future]] of `true` if the index was/will be effectively created, `false` | ||
| * if the index existed and `ignoreIfExist` is `true`. Completion of the future does not guarantee the index is online | ||
| * and ready to be used. | ||
| */ | ||
| override def createIndex(indexName: String, createQueryIndexOptions: CreateQueryIndexOptions, fields: String*): CompletionStage[Void] = | ||
| delegate.createIndex(indexName, createQueryIndexOptions, fields: _*).asJava | ||
|
|
||
| /** | ||
| * List the existing secondary indexes for the collection | ||
| */ | ||
| override def listIndexes(): Source[QueryIndex, NotUsed] = | ||
| delegate.listIndexes().asJava | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's important to only use ExecutionContext.parasitic when it's absolutely certain that there will be no exceptions. That might not be the case here? createClusterClient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've returned the code that was using blockingDispatcher, please lmk if you want to use something else here.
Thank you!