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
60 changes: 60 additions & 0 deletions src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import net.spy.memcached.collection.BTreeGetBulk;
import net.spy.memcached.collection.BTreeGetBulkWithLongTypeBkey;
import net.spy.memcached.collection.BTreeGetBulkWithByteTypeBkey;
import net.spy.memcached.collection.BTreeUpdate;
import net.spy.memcached.collection.BTreeUpsert;
import net.spy.memcached.collection.CollectionUpdate;
import net.spy.memcached.internal.result.GetsResultImpl;
import net.spy.memcached.ops.BTreeGetBulkOperation;
import net.spy.memcached.collection.BTreeSMGet;
Expand Down Expand Up @@ -70,6 +72,7 @@
import net.spy.memcached.v2.vo.BKey;
import net.spy.memcached.v2.vo.BTreeElement;
import net.spy.memcached.v2.vo.BTreeElements;
import net.spy.memcached.v2.vo.BTreeUpdateElement;
import net.spy.memcached.v2.vo.BopGetArgs;
import net.spy.memcached.v2.vo.SMGetElements;

Expand Down Expand Up @@ -623,6 +626,63 @@ public void complete() {
return future;
}

public ArcusFuture<Boolean> bopUpdate(String key, BTreeUpdateElement<T> element) {
BTreeUpdate<T> update = new BTreeUpdate<>(element.getValue(), element.getEFlagUpdate(), false);
return collectionUpdate(key, element.getBkey().toString(), update);
}

private ArcusFuture<Boolean> collectionUpdate(String key,
String internalKey,
CollectionUpdate<T> collectionUpdate) {
AbstractArcusResult<Boolean> result = new AbstractArcusResult<>(new AtomicReference<>());
ArcusFutureImpl<Boolean> future = new ArcusFutureImpl<>(result);
CachedData co = null;
if (collectionUpdate.getNewValue() != null) {
co = tcForCollection.encode(collectionUpdate.getNewValue());
collectionUpdate.setFlags(co.getFlags());
}
ArcusClient client = arcusClientSupplier.get();

OperationCallback cb = new OperationCallback() {
@Override
public void receivedStatus(OperationStatus status) {
switch (status.getStatusCode()) {
case SUCCESS:
result.set(true);
break;
case ERR_NOT_FOUND_ELEMENT:
result.set(false);
break;
case ERR_NOT_FOUND:
result.set(null);
break;
case CANCELLED:
future.internalCancel();
break;
default:
/*
* TYPE_MISMATCH / BKEY_MISMATCH / EFLAG_MISMATCH / NOTHING_TO_UPDATE /
* OVERFLOWED / OUT_OF_RANGE / NOT_SUPPORTED or unknown statement
*/
result.addError(key, status);
break;
}
}

@Override
public void complete() {
future.complete();
}
};
Operation op = client.getOpFact()
.collectionUpdate(key, internalKey, collectionUpdate,
(co == null) ? null : co.getData(), cb);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collectionUpdate์™€ CachedData๋ฅผ ๋”ฐ๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ ๋ณด๋‹ค
CachedData ์ƒ์„ฑํ•˜์—ฌ ๋‹ค์‹œ collectionUpdate์— setํ•˜๊ณ 
collectionUpdate๋งŒ ๋„˜๊ธฐ๋Š” ๊ฒƒ์ด ์ข‹์ง€ ์•Š๋‚˜ ์ƒ๊ฐ๋ฉ๋‹ˆ๋‹ค.
ํ–ฅํ›„ ๋ฆฌํŒฉํ† ๋ง ๋Œ€์ƒ์ด ๋˜๋Š” ์ง€ ๊ฒ€ํ†  ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋ณธ PR์—์„œ๋Š” ์ผ๋‹จ ๋ฌธ์ œ ์—†์Šต๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Collaborator Author

@f1v3-dev f1v3-dev Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

as-is

AsyncArcusCommands.java

// collectionUpdate
    CachedData co = null;
    if (collectionUpdate.getNewValue() != null) {
      co = tcForCollection.encode(collectionUpdate.getNewValue());
      collectionUpdate.setFlags(co.getFlags()); // flag ์„ค์ •๋งŒ ํ•ด์ฃผ๋Š” ์ƒํ™ฉ
    }

    // collectionUpdate + co.getData() ๋ฅผ ๊ฐ™์ด ๋„˜๊ธฐ๋Š” ํ˜•ํƒœ
    Operation op = client.getOpFact()
            .collectionUpdate(key, internalKey, collectionUpdate,
                    (co == null) ? null : co.getData(), cb); 

AsciiOperationFactory.java

  public CollectionUpdateOperation collectionUpdate(String key,
                                                    String subkey,
                                                    CollectionUpdate<?> collectionUpdate,
                                                    byte[] data,
                                                    OperationCallback cb) {
    return new CollectionUpdateOperationImpl(key, subkey, collectionUpdate,
            data, cb);
  }

to-be

AsyncArcusCommands.java

// collectionUpdate
    CachedData co = null;
    if (collectionUpdate.getNewValue() != null) {
      co = tcForCollection.encode(collectionUpdate.getNewValue());
      collectionUpdate.setFlags(co.getFlags());
      collectionUpdate.setData(co.getData()); // ๋ณ€๊ฒฝ๋  ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ์ €์žฅ (๋ณ„๋„์˜ data ํ•„๋“œ ์ถ”๊ฐ€ ํ•„์š”)
    }


    // Operation ์ƒ์„ฑ ์‹œ collectionUpdate ๋งŒ์„ ๋„˜๊น€
    Operation op = client.getOpFact()
            .collectionUpdate(key, internalKey, collectionUpdate, cb);

AsciiOperationFactory.java

  public CollectionUpdateOperation collectionUpdate(String key,
                                                    String subkey,
                                                    CollectionUpdate<?> collectionUpdate,
                                                    OperationCallback cb) {  // โ† data ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ
    return new CollectionUpdateOperationImpl(key, subkey, collectionUpdate,
            collectionUpdate.getData(), cb);  // โ† collectionUpdate์—์„œ ๊บผ๋ƒ„
  }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๋ง์”€ํ•ด์ฃผ์‹  ๋ฐฉ์‹๋Œ€๋กœ ๋ณ€๊ฒฝํ•  ๊ฒฝ์šฐ CollectionInsert ๋˜ํ•œ ๋™์ผํ•˜๊ฒŒ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๋ฏผ๊ฒฝ๋‹˜๊ณผ ์˜๊ฒฌ์„ ๋…ผ์˜ํ•ด๋ณธ ๊ฒฐ๊ณผ ์œ„์—์„œ ์–ธ๊ธ‰ํ–ˆ๋˜ collectionUpdate ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์˜๊ฒฌ์ด ๋ชจ์•„์กŒ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ, ํ˜„์žฌ v2 ์ž‘์—… ๋งˆ๋ฌด๋ฆฌ๊ฐ€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋” ๋†’๋‹ค๊ณ  ๋ณด์—ฌ์ง€๊ณ  ํ•ด๋‹น ๋ฆฌํŒฉํ† ๋ง ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ v2 ๋ฟ ๋งŒ ์•„๋‹ˆ๋ผ ๊ธฐ์กด API ๋‚ด๋ถ€ ๊ตฌํ˜„์— ๊ฑธ์นœ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ, v2 ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ „๋ฐ˜์ ์ธ ๊ฒ€ํ†  ๊ณผ์ •์„ ์ง„ํ–‰ํ•  ๋•Œ ๋ฆฌํŒฉํ† ๋ง ์ž‘์—…์„ ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

future.setOp(op);
client.addOp(key, op);

return future;
}

public ArcusFuture<Map.Entry<Boolean, BTreeElement<T>>> bopInsertAndGetTrimmed(
String key, BTreeElement<T> element, CollectionAttributes attributes) {
return bopInsertOrUpsertAndGetTrimmed(key, element, false, attributes);
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import net.spy.memcached.v2.vo.BKey;
import net.spy.memcached.v2.vo.BTreeElement;
import net.spy.memcached.v2.vo.BTreeElements;
import net.spy.memcached.v2.vo.BTreeUpdateElement;
import net.spy.memcached.v2.vo.BopGetArgs;
import net.spy.memcached.v2.vo.SMGetElements;

Expand Down Expand Up @@ -207,6 +208,16 @@ ArcusFuture<Boolean> bopUpsert(String key, BTreeElement<T> element,
*/
ArcusFuture<Boolean> bopUpsert(String key, BTreeElement<T> element);

/**
* Update an element in a btree item
*
* @param key key to update
* @param element btree element to update.
* @return {@code Boolean.True} if updated, {@code Boolean.False} if element does not exist,
* {@code null} if key is not found
*/
ArcusFuture<Boolean> bopUpdate(String key, BTreeUpdateElement<T> element);

/**
* Insert an element into a btree item and get trimmed element if overflow trim occurs.
*
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/net/spy/memcached/v2/vo/BTreeUpdateElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package net.spy.memcached.v2.vo;

import net.spy.memcached.collection.ElementFlagUpdate;

public final class BTreeUpdateElement<V> {
private final BKey bkey;
private final V value;
private final ElementFlagUpdate eFlagUpdate;

private BTreeUpdateElement(BKey bkey, V value, ElementFlagUpdate eFlagUpdate) {
if (bkey == null) {
throw new IllegalArgumentException("BKey cannot be null.");
}

this.bkey = bkey;
this.value = value;
this.eFlagUpdate = eFlagUpdate;
}

public static <V> BTreeUpdateElement<V> withValue(BKey bkey, V value) {
if (value == null) {
throw new IllegalArgumentException("Value cannot be null.");
}
return new BTreeUpdateElement<>(bkey, value, null);
}

public static <V> BTreeUpdateElement<V> withEFlagUpdate(BKey bkey,
ElementFlagUpdate eFlagUpdate) {
if (eFlagUpdate == null) {
throw new IllegalArgumentException("EFlagUpdate cannot be null.");
}
return new BTreeUpdateElement<>(bkey, null, eFlagUpdate);
}

public static <V> BTreeUpdateElement<V> withValueAndEFlag(BKey bkey,
V value,
ElementFlagUpdate eFlagUpdate) {
if (value == null || eFlagUpdate == null) {
throw new IllegalArgumentException("Both value and eFlagUpdate cannot be null.");
}
return new BTreeUpdateElement<>(bkey, value, eFlagUpdate);
}

public BKey getBkey() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค.
getEFlagUpdate() ์šฉ์–ด์™€ ์ผ๊ด€๋˜๊ฒŒ getBkey() => getBKey() ์ด์–ด์•ผ ๋˜์ง€ ์•Š๋Š” ์ง€?
๋ณด๋‹ˆ, ๊ธฐ์กด ๊ตฌํ˜„์—์„œ ๋ชจ๋‘ getBkey() ์šฉ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBkey() ์šฉ์–ด์— ๊ด€ํ•œ ๋ฌธ์ œ์ด๋‹ˆ,
ํ–ฅํ›„ ๋ฆฌํŒฉํ† ๋ง ์‹œ์— ๊ฒ€ํ† ํ•ด ์ฃผ์‹œ๊ณ , ๋ณธ PR์—์„œ๋Š” skip ํ•ฉ๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ํ˜„์žฌ v2์˜ vo ํŒจํ‚ค์ง€ ๋‚ด์—์„œ๋„ getBkey() ์™€ getBKey()๊ฐ€ ๊ฐ™์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ „์ฒด์ ์œผ๋กœ bKey ๋ผ๋Š” ๋ณ€์ˆ˜๋ช…๊ณผ getBKey() ๋ผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ํ†ต์ผ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์šฉ์–ด ํ†ต์ผ์€ ๋ณ„๋„ PR๋กœ ์ง„ํ–‰ํ•ด ์ฃผ์‹œ์ฃ .

return bkey;
}

public V getValue() {
return value;
}

public ElementFlagUpdate getEFlagUpdate() {
return eFlagUpdate;
}
}
147 changes: 147 additions & 0 deletions src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import net.spy.memcached.collection.CollectionAttributes;
import net.spy.memcached.collection.CollectionOverflowAction;
import net.spy.memcached.collection.ElementFlagUpdate;
import net.spy.memcached.collection.ElementValueType;
import net.spy.memcached.ops.StatusCode;
import net.spy.memcached.v2.vo.BKey;
import net.spy.memcached.v2.vo.BTreeElement;
import net.spy.memcached.v2.vo.BTreeElements;
import net.spy.memcached.v2.vo.BTreeUpdateElement;
import net.spy.memcached.v2.vo.BopGetArgs;
import net.spy.memcached.v2.vo.SMGetElements;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
Expand Down Expand Up @@ -827,4 +832,146 @@ void bopSortMergeGetNotHaveTrimmedKeysOutOfElementsRangeAscending() throws Excep
.toCompletableFuture()
.get(300, TimeUnit.MILLISECONDS);
}

@Test
void bopUpdateSuccess() throws ExecutionException, InterruptedException, TimeoutException {
// given
String key = keys.get(0);

async.bopInsert(key, ELEMENTS.get(0), new CollectionAttributes())
.thenCompose(result -> {
assertTrue(result);
return async.bopGet(key, BKey.of(1L), BopGetArgs.DEFAULT);
})
.thenAccept(element -> {
assertNotNull(element);
assertEquals(ELEMENTS.get(0).getBkey(), element.getBkey());
assertEquals(ELEMENTS.get(0).getValue(), element.getValue());
assertNull(element.getEFlag());
})
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);

ElementFlagUpdate eFlagUpdate = new ElementFlagUpdate(new byte[]{1, 2, 3});
BTreeUpdateElement<Object> updatedElement = BTreeUpdateElement
.withValueAndEFlag(BKey.of(1L), "updated_value", eFlagUpdate);

// when
async.bopUpdate(key, updatedElement)
// then
.thenCompose(result -> {
assertTrue(result);
return async.bopGet(key, BKey.of(1L), BopGetArgs.DEFAULT);
})
.thenAccept(result -> {
assertNotNull(result);
assertEquals(updatedElement.getBkey(), result.getBkey());
assertEquals(updatedElement.getValue(), result.getValue());
assertArrayEquals(eFlagUpdate.getElementFlag(), result.getEFlag());
})
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);
}

@Test
void bopUpdateValueSuccess() throws ExecutionException, InterruptedException, TimeoutException {
// given
String key = keys.get(0);

async.bopInsert(key, ELEMENTS.get(0), new CollectionAttributes())
.thenAccept(Assertions::assertTrue)
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);

BTreeUpdateElement<Object> updatedElement = BTreeUpdateElement
.withValue(BKey.of(1L), "updated_value");

// when
async.bopUpdate(key, updatedElement)
// then
.thenCompose(result -> {
assertTrue(result);
return async.bopGet(key, BKey.of(1L), BopGetArgs.DEFAULT);
})
.thenAccept(result -> {
assertNotNull(result);
assertEquals(updatedElement.getBkey(), result.getBkey());
assertEquals(updatedElement.getValue(), result.getValue());
assertNull(result.getEFlag());
})
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);
}

@Test
void bopUpdateEFlagSuccess() throws ExecutionException, InterruptedException, TimeoutException {
// given
String key = keys.get(0);

async.bopInsert(key, ELEMENTS.get(0), new CollectionAttributes())
.thenAccept(Assertions::assertTrue)
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);

ElementFlagUpdate eFlagUpdate = new ElementFlagUpdate(new byte[]{1, 2, 3});
BTreeUpdateElement<Object> updatedElement = BTreeUpdateElement
.withEFlagUpdate(BKey.of(1L), eFlagUpdate);

// when
async.bopUpdate(key, updatedElement)
// then
.thenCompose(result -> {
assertTrue(result);
return async.bopGet(key, BKey.of(1L), BopGetArgs.DEFAULT);
})
.thenAccept(result -> {
assertNotNull(result);
assertEquals(updatedElement.getBkey(), result.getBkey());
assertEquals(ELEMENTS.get(0).getValue(), result.getValue());
assertArrayEquals(eFlagUpdate.getElementFlag(), result.getEFlag());
})
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);
}

@Test
void bopUpdateNotFoundElement() throws ExecutionException, InterruptedException,
TimeoutException {

// given
String key = keys.get(0);

async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes())
.thenAccept(Assertions::assertTrue)
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);

ElementFlagUpdate eFlagUpdate = new ElementFlagUpdate(new byte[]{1, 2, 3});
BTreeUpdateElement<Object> updatedElement = BTreeUpdateElement
.withValueAndEFlag(BKey.of(1L), "updated_value", eFlagUpdate);

// when
async.bopUpdate(key, updatedElement)
// then
.thenAccept(Assertions::assertFalse)
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);
}

@Test
void bopUpdateNotFound() throws ExecutionException, InterruptedException, TimeoutException {
// given
String key = keys.get(0);

ElementFlagUpdate eFlagUpdate = new ElementFlagUpdate(new byte[]{1, 2, 3});
BTreeUpdateElement<Object> updatedElement = BTreeUpdateElement
.withValueAndEFlag(BKey.of(1L), "updated_value", eFlagUpdate);

// when
async.bopUpdate(key, updatedElement)
// then
.thenAccept(Assertions::assertNull)
.toCompletableFuture()
.get(300L, TimeUnit.MILLISECONDS);
}
}
Loading