com.couchbase.client.java.transactions.TransactionAttemptContext Maven / Gradle / Ivy
Show all versions of java-client Show documentation
/*
* Copyright 2022 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.
*/
package com.couchbase.client.java.transactions;
import com.couchbase.client.core.annotation.Stability;
import com.couchbase.client.core.api.query.CoreQueryContext;
import com.couchbase.client.core.api.query.CoreQueryOptions;
import com.couchbase.client.core.cnc.CbTracing;
import com.couchbase.client.core.cnc.RequestSpan;
import com.couchbase.client.core.cnc.TracingIdentifiers;
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.node.ObjectNode;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.core.error.EncodingFailureException;
import com.couchbase.client.core.json.Mapper;
import com.couchbase.client.core.msg.query.QueryRequest;
import com.couchbase.client.core.transaction.CoreTransactionAttemptContext;
import com.couchbase.client.core.transaction.log.CoreTransactionLogger;
import com.couchbase.client.core.transaction.support.SpanWrapper;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
import com.couchbase.client.java.codec.JsonSerializer;
import com.couchbase.client.java.codec.Transcoder;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.transactions.config.TransactionGetOptions;
import com.couchbase.client.java.transactions.config.TransactionInsertOptions;
import com.couchbase.client.java.transactions.config.TransactionReplaceOptions;
import reactor.util.annotation.Nullable;
import java.io.IOException;
import java.util.Objects;
import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_INSERT;
import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REMOVE;
import static com.couchbase.client.core.cnc.TracingIdentifiers.TRANSACTION_OP_REPLACE;
import static com.couchbase.client.core.util.Validators.notNull;
import static com.couchbase.client.java.transactions.internal.ConverterUtil.makeCollectionIdentifier;
import static com.couchbase.client.java.transactions.internal.EncodingUtil.encode;
/**
* Provides methods to allow an application's transaction logic to read, mutate, insert and delete documents, as well
* as
* commit or rollback the transaction.
*
* These methods are blocking/synchronous. See {@link CoreTransactionAttemptContext} for the asynchronous version (which is
* the
* preferred option, and which this class largely simply wraps).
*/
public class TransactionAttemptContext {
private final CoreTransactionAttemptContext internal;
private final JsonSerializer serializer;
TransactionAttemptContext(CoreTransactionAttemptContext internal, JsonSerializer serializer) {
this.internal = Objects.requireNonNull(internal);
this.serializer = Objects.requireNonNull(serializer);
}
@Stability.Internal
CoreTransactionAttemptContext ctx() {
return internal;
}
@SuppressWarnings("unused")
@Stability.Internal
CoreTransactionLogger logger() {
return ctx().logger();
}
/**
* Gets a document from the specified Couchbase collection
matching the specified id
. If
* the document is not found, a DocumentNotFoundException
is thrown.
*
* @param collection the Couchbase collection the document exists on
* @param id the document's ID
* @return a TransactionGetResult
containing the document
*/
public TransactionGetResult get(Collection collection, String id) {
return get(collection, id, TransactionGetOptions.DEFAULT);
}
/**
* Gets a document from the specified Couchbase collection
matching the specified id
. If
* the document is not found, a DocumentNotFoundException
is thrown.
*
* @param collection the Couchbase collection the document exists on
* @param id the document's ID
* @param options options controlling the operation
* @return a TransactionGetResult
containing the document
*/
public TransactionGetResult get(Collection collection, String id, TransactionGetOptions options) {
notNull(options, "Options");
TransactionGetOptions.Built built = options.build();
return internal.get(makeCollectionIdentifier(collection.async()), id)
.map(result -> new TransactionGetResult(result, serializer(), built.transcoder()))
.block();
}
/**
* Mutates the specified doc
with new content.
*
* The mutation is staged until the transaction is committed. That is, any read of the document by any Couchbase
* component will see the document's current value, rather than this staged or 'dirty' data. If the attempt is
* rolled back, the staged mutation will be removed.
*
* This staged data effectively locks the document from other transactional writes until the attempt completes
* (commits or rolls back).
*
* If the mutation fails with a CasMismatchException
, or any other exception, the transaction will
* automatically
* rollback this attempt, then retry.
*
* @param doc the doc to be updated
* @param content the content to replace the doc with. This will normally be a {@link JsonObject}.
* @return the doc, updated with its new CAS value. For performance a copy is not created and the original doc
* object is modified.
*/
public TransactionGetResult replace(TransactionGetResult doc, Object content) {
return replace(doc, content, TransactionReplaceOptions.DEFAULT);
}
/**
* Mutates the specified doc
with new content.
*
* The mutation is staged until the transaction is committed. That is, any read of the document by any Couchbase
* component will see the document's current value, rather than this staged or 'dirty' data. If the attempt is
* rolled back, the staged mutation will be removed.
*
* This staged data effectively locks the document from other transactional writes until the attempt completes
* (commits or rolls back).
*
* If the mutation fails with a CasMismatchException
, or any other exception, the transaction will
* automatically rollback this attempt, then retry.
*
* @param doc the doc to be updated
* @param content the content to replace the doc with. This will normally be a {@link JsonObject}.
* @param options options controlling the operation
* @return the doc, updated with its new CAS value. For performance a copy is not created and the original doc
* object is modified.
*/
public TransactionGetResult replace(TransactionGetResult doc, Object content, TransactionReplaceOptions options) {
notNull(options, "Options");
TransactionReplaceOptions.Built built = options.build();
RequestSpan span = CbTracing.newSpan(internal.core().context(), TRANSACTION_OP_REPLACE, internal.span());
span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_REPLACE);
Transcoder.EncodedValue encoded = encode(content, span, serializer, built.transcoder(), internal.core().context());
return internal.replace(doc.internal(), encoded.encoded(), encoded.flags(), new SpanWrapper(span))
.map(result -> new TransactionGetResult(result, serializer(), built.transcoder()))
.doOnError(err -> span.status(RequestSpan.StatusCode.ERROR))
.doOnTerminate(() -> span.end())
.block();
}
private JsonSerializer serializer() {
return serializer;
}
/**
* Inserts a new document into the specified Couchbase collection
.
*
* As with {@link #replace}, the insert is staged until the transaction is committed.
*
* This staged data effectively locks the document from other transactional writes until the attempt completes
* (commits or rolls back).
*
* @param collection the Couchbase collection in which to insert the doc
* @param id the document's unique ID
* @param content the content to insert. Generally this will be a
* {@link com.couchbase.client.java.json.JsonObject}
* @return the doc, updated with its new CAS value and ID, and converted to a TransactionGetResult
*/
public TransactionGetResult insert(Collection collection, String id, Object content) {
return insert(collection, id, content, TransactionInsertOptions.DEFAULT);
}
/**
* Inserts a new document into the specified Couchbase collection
.
*
* As with {@link #replace}, the insert is staged until the transaction is committed.
*
* This staged data effectively locks the document from other transactional writes until the attempt completes
* (commits or rolls back).
*
* @param collection the Couchbase collection in which to insert the doc
* @param id the document's unique ID
* @param content the content to insert. Generally this will be a
* {@link com.couchbase.client.java.json.JsonObject}
* @param options options controlling the operation
* @return the doc, updated with its new CAS value and ID, and converted to a TransactionGetResult
*/
public TransactionGetResult insert(Collection collection, String id, Object content, TransactionInsertOptions options) {
notNull(options, "Options");
TransactionInsertOptions.Built built = options.build();
RequestSpan span = CbTracing.newSpan(internal.core().context(), TRANSACTION_OP_INSERT, internal.span());
span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_INSERT);
Transcoder.EncodedValue encoded = encode(content, span, serializer, built.transcoder(), internal.core().context());
return internal.insert(makeCollectionIdentifier(collection.async()), id, encoded.encoded(), encoded.flags(), new SpanWrapper(span))
.map(result -> new TransactionGetResult(result, serializer(), built.transcoder()))
.doOnError(err -> span.status(RequestSpan.StatusCode.ERROR))
.doOnTerminate(() -> span.end())
.block();
}
/**
* Removes the specified doc
.
*
* As with {@link #replace}, the remove is staged until the transaction is committed. That is, the document will
* continue to exist, and the rest of the Couchbase platform will continue to see it.
*
* This staged data effectively locks the document from other transactional writes until the attempt completes
* (commits or rolls back).
*
* Note that a remove(String id)
method is not possible, as it's necessary to check a provided
* TransactionGetResult
to determine if the document is involved in another transaction.
*
* @param doc the doc to be removed
*/
public void remove(TransactionGetResult doc) {
RequestSpan span = CbTracing.newSpan(internal.core().context(), TRANSACTION_OP_REMOVE, internal.span());
span.lowCardinalityAttribute(TracingIdentifiers.ATTR_OPERATION, TRANSACTION_OP_REMOVE);
internal.remove(doc.internal(), new SpanWrapper(span))
.doOnError(err -> span.status(RequestSpan.StatusCode.ERROR))
.doOnTerminate(() -> span.end())
.block();
}
/**
* Runs a N1QL query and returns the result.
*
* All rows are buffered in-memory.
*
* @throws CouchbaseException or an error derived from it on failure. The application can choose to catch and ignore this error, and the
* transaction attempt is allowed to continue. This differs from Key-Value operations, whose failure will
* cause the attempt to fail.
*/
public TransactionQueryResult query(String statement,
TransactionQueryOptions options) {
return query(null, statement, options);
}
/**
* Calls query() with default options.
*/
public TransactionQueryResult query(String statement) {
return query(null, statement, null);
}
/**
* Runs a N1QL query and returns the result.
*
* All rows are buffered in-memory.
*
* This overload performs a 'scope-level query': that is, one in which a collection may be referenced by name in the
* query statement, without needing to specify the full bucket.scope.collection syntax.
*
* @throws CouchbaseException or an error derived from it on failure. The application can choose to catch and ignore this error, and the
* transaction attempt is allowed to continue. This differs from Key-Value operations, whose failure will
* cause the attempt to fail.
*/
public TransactionQueryResult query(Scope scope,
String statement,
TransactionQueryOptions options) {
CoreQueryOptions opts = options != null ? options.builder().build() : null;
return internal.queryBlocking(statement,
scope == null ? null : CoreQueryContext.of(scope.bucketName(), scope.name()),
opts,
false)
.publishOn(internal.core().context().environment().transactionsSchedulers().schedulerBlocking())
.map(response -> new TransactionQueryResult(response, serializer()))
.block();
}
/**
* Calls query() with default options.
*
* This overload performs a 'scope-level query': that is, one in which a collection may be referenced by name in the
* query statement, without needing to specify the full bucket.scope.collection syntax.
*/
public TransactionQueryResult query(Scope scope, String statement) {
return query(scope, statement, null);
}
}