This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new e1c9b1c4cf IGNITE-20807 Java thin: Implement nullable operations in
ClientKeyValueView (#2855)
e1c9b1c4cf is described below
commit e1c9b1c4cf589c71aecc4815a3c8a14ae8fbf2f3
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Nov 21 14:55:07 2023 +0200
IGNITE-20807 Java thin: Implement nullable operations in ClientKeyValueView
(#2855)
* Implement nullable operations in client KV view: `getNullableAsync`,
`getOrDefaultAsync`, `getNullableAndPutAsync`, `getNullableAndRemoveAsync`,
`getNullableAndReplaceAsync`
* Throw `UnexpectedNullValueException` from all other methods (same way as
we do in embedded mode)
---
.../java/org/apache/ignite/table/KeyValueView.java | 5 +
.../internal/client/table/ClientKeyValueView.java | 69 +++++++++---
.../ignite/client/ClientKeyValueViewTest.java | 123 ++++++++++++++++++++-
3 files changed, 182 insertions(+), 15 deletions(-)
diff --git
a/modules/api/src/main/java/org/apache/ignite/table/KeyValueView.java
b/modules/api/src/main/java/org/apache/ignite/table/KeyValueView.java
index 2ce7d764e8..1d3137bedd 100644
--- a/modules/api/src/main/java/org/apache/ignite/table/KeyValueView.java
+++ b/modules/api/src/main/java/org/apache/ignite/table/KeyValueView.java
@@ -69,6 +69,10 @@ public interface KeyValueView<K, V> extends
DataStreamerTarget<Entry<K, V>> {
/**
* Gets a nullable value associated with a given key.
*
+ * <p>Examples:
+ * {@code getNullable(tx, key)} returns {@code null} after {@code
remove(tx, key)}.
+ * {@code getNullable(tx, key)} returns {@code Nullable.of(null)}
after {@code put(tx, key, null)}.
+ *
* @param tx Transaction or {@code null} to auto commit.
* @param key Key whose value is to be returned. The key cannot be {@code
null}.
* @return Wrapped nullable value or {@code null} if it does not exist.
@@ -82,6 +86,7 @@ public interface KeyValueView<K, V> extends
DataStreamerTarget<Entry<K, V>> {
* @param tx Transaction or {@code null} to auto-commit.
* @param key Key whose value is to be returned. The key cannot be {@code
null}.
* @return Future that represents the pending completion of the operation.
+ * The future returns wrapped nullable value or {@code null} if the
row with the given key does not exist.
* @throws MarshallerException if the key doesn't match the schema.
* @see #getNullable(Transaction, Object)
*/
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
index 2bccc85173..f44b242ceb 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientKeyValueView.java
@@ -45,6 +45,7 @@ import
org.apache.ignite.internal.marshaller.MarshallerException;
import org.apache.ignite.internal.streamer.StreamerBatchSender;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.NullableValue;
+import org.apache.ignite.lang.UnexpectedNullValueException;
import org.apache.ignite.table.DataStreamerOptions;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.mapper.Mapper;
@@ -96,7 +97,7 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
return tbl.doSchemaOutInOpAsync(
ClientOp.TUPLE_GET,
(s, w) -> keySer.writeRec(tx, key, s, w, TuplePart.KEY),
- (s, r) -> valSer.readRec(s, r, TuplePart.VAL),
+ (s, r) -> throwIfNull(valSer.readRec(s, r, TuplePart.VAL)),
null,
ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
@@ -110,8 +111,15 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
/** {@inheritDoc} */
@Override
public CompletableFuture<NullableValue<V>> getNullableAsync(@Nullable
Transaction tx, K key) {
- // TODO IGNITE-20807
- throw new UnsupportedOperationException("Not implemented yet.");
+ Objects.requireNonNull(key);
+
+ // Null means row does not exist, NullableValue.NULL means row exists,
but mapped value column is null.
+ return tbl.doSchemaOutInOpAsync(
+ ClientOp.TUPLE_GET,
+ (s, w) -> keySer.writeRec(tx, key, s, w, TuplePart.KEY),
+ (s, r) -> NullableValue.of(valSer.readRec(s, r,
TuplePart.VAL)),
+ null,
+ ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
/** {@inheritDoc} */
@@ -123,8 +131,14 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
/** {@inheritDoc} */
@Override
public CompletableFuture<V> getOrDefaultAsync(@Nullable Transaction tx, K
key, V defaultValue) {
- // TODO IGNITE-20807
- throw new UnsupportedOperationException("Not implemented yet.");
+ Objects.requireNonNull(key);
+
+ return tbl.doSchemaOutInOpAsync(
+ ClientOp.TUPLE_GET,
+ (s, w) -> keySer.writeRec(tx, key, s, w, TuplePart.KEY),
+ (s, r) -> valSer.readRec(s, r, TuplePart.VAL),
+ defaultValue,
+ ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
/** {@inheritDoc} */
@@ -229,7 +243,7 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
return tbl.doSchemaOutInOpAsync(
ClientOp.TUPLE_GET_AND_UPSERT,
(s, w) -> writeKeyValue(s, w, tx, key, val),
- (s, r) -> valSer.readRec(s, r, TuplePart.VAL),
+ (s, r) -> throwIfNull(valSer.readRec(s, r, TuplePart.VAL)),
null,
ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
@@ -243,8 +257,14 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
/** {@inheritDoc} */
@Override
public CompletableFuture<NullableValue<V>>
getNullableAndPutAsync(@Nullable Transaction tx, K key, V val) {
- // TODO IGNITE-20807
- throw new UnsupportedOperationException("Not implemented yet.");
+ Objects.requireNonNull(key);
+
+ return tbl.doSchemaOutInOpAsync(
+ ClientOp.TUPLE_GET_AND_UPSERT,
+ (s, w) -> writeKeyValue(s, w, tx, key, val),
+ (s, r) -> NullableValue.of(valSer.readRec(s, r,
TuplePart.VAL)),
+ null,
+ ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
/** {@inheritDoc} */
@@ -338,7 +358,7 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
return tbl.doSchemaOutInOpAsync(
ClientOp.TUPLE_GET_AND_DELETE,
(s, w) -> keySer.writeRec(tx, key, s, w, TuplePart.KEY),
- (s, r) -> valSer.readRec(s, r, TuplePart.VAL),
+ (s, r) -> throwIfNull(valSer.readRec(s, r, TuplePart.VAL)),
null,
ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
@@ -352,8 +372,14 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
/** {@inheritDoc} */
@Override
public CompletableFuture<NullableValue<V>>
getNullableAndRemoveAsync(@Nullable Transaction tx, K key) {
- // TODO IGNITE-20807
- throw new UnsupportedOperationException("Not implemented yet.");
+ Objects.requireNonNull(key);
+
+ return tbl.doSchemaOutInOpAsync(
+ ClientOp.TUPLE_GET_AND_DELETE,
+ (s, w) -> keySer.writeRec(tx, key, s, w, TuplePart.KEY),
+ (s, r) -> NullableValue.of(valSer.readRec(s, r,
TuplePart.VAL)),
+ null,
+ ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
/** {@inheritDoc} */
@@ -413,7 +439,7 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
return tbl.doSchemaOutInOpAsync(
ClientOp.TUPLE_GET_AND_REPLACE,
(s, w) -> writeKeyValue(s, w, tx, key, val),
- (s, r) -> valSer.readRec(s, r, TuplePart.VAL),
+ (s, r) -> throwIfNull(valSer.readRec(s, r, TuplePart.VAL)),
null,
ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
@@ -427,8 +453,15 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
/** {@inheritDoc} */
@Override
public CompletableFuture<NullableValue<V>>
getNullableAndReplaceAsync(@Nullable Transaction tx, K key, V val) {
- // TODO IGNITE-20807
- throw new UnsupportedOperationException("Not implemented yet.");
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(val);
+
+ return tbl.doSchemaOutInOpAsync(
+ ClientOp.TUPLE_GET_AND_REPLACE,
+ (s, w) -> writeKeyValue(s, w, tx, key, val),
+ (s, r) -> NullableValue.of(valSer.readRec(s, r,
TuplePart.VAL)),
+ null,
+ ClientTupleSerializer.getPartitionAwarenessProvider(tx,
keySer.mapper(), key));
}
private void writeKeyValue(ClientSchema s, PayloadOutputChannel w,
@Nullable Transaction tx, K key, @Nullable V val) {
@@ -507,4 +540,12 @@ public class ClientKeyValueView<K, V> implements
KeyValueView<K, V> {
return ClientDataStreamer.streamData(publisher, opts, batchSender,
provider, tbl);
}
+
+ private static <T> T throwIfNull(T obj) {
+ if (obj == null) {
+ throw new UnexpectedNullValueException("Got unexpected null value:
use `getNullable` sibling method instead.");
+ }
+
+ return obj;
+ }
}
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
b/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
index b0fa0cd1bb..3af37ae9d8 100644
---
a/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
+++
b/modules/client/src/test/java/org/apache/ignite/client/ClientKeyValueViewTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.client;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
+import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
@@ -38,7 +39,10 @@ import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.NullableValue;
+import org.apache.ignite.lang.UnexpectedNullValueException;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Table;
@@ -288,6 +292,24 @@ public class ClientKeyValueViewTest extends
AbstractClientTableTest {
assertEquals("100", res[1]);
}
+ @Test
+ public void testGetAllNullAndMissingValue() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, DEFAULT_NAME);
+ primitiveView.put(null, -1L, null);
+ primitiveView.remove(null, -2L);
+
+ var res = primitiveView.getAll(null, List.of(DEFAULT_ID, -1L, -2L));
+
+ assertEquals(2, res.size());
+ assertEquals(DEFAULT_NAME, res.get(DEFAULT_ID));
+ assertNull(res.get(-1L));
+
+ assertTrue(res.containsKey(-1L));
+ assertFalse(res.containsKey(-2L));
+ }
+
@Test
public void testPutAll() {
KeyValueView<Long, String> pojoView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
@@ -327,7 +349,7 @@ public class ClientKeyValueViewTest extends
AbstractClientTableTest {
pojoView.put(null, DEFAULT_ID, DEFAULT_NAME);
pojoView.put(null, DEFAULT_ID, null);
- assertNull(pojoView.get(null, DEFAULT_ID));
+ assertNull(pojoView.getNullable(null, DEFAULT_ID).get());
}
@Test
@@ -493,4 +515,103 @@ public class ClientKeyValueViewTest extends
AbstractClientTableTest {
assertTrue(ex.getMessage().contains("null was passed, but column is
not nullable"), ex.getMessage());
assertThat(Arrays.asList(ex.getStackTrace()),
anyOf(hasToString(containsString("ClientKeyValueView"))));
}
+
+ @Test
+ public void testGetNullValueThrows() {
+ testNullValueThrows(view -> view.get(null, DEFAULT_ID));
+ }
+
+ @Test
+ public void testGetAndPutNullValueThrows() {
+ testNullValueThrows(view -> view.getAndPut(null, DEFAULT_ID,
DEFAULT_NAME));
+ }
+
+ @Test
+ public void testGetAndRemoveNullValueThrows() {
+ testNullValueThrows(view -> view.getAndRemove(null, DEFAULT_ID));
+ }
+
+ @Test
+ public void testGetAndReplaceNullValueThrows() {
+ testNullValueThrows(view -> view.getAndReplace(null, DEFAULT_ID,
DEFAULT_NAME));
+ }
+
+ private void testNullValueThrows(Consumer<KeyValueView<Long, String>> run)
{
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+ primitiveView.put(null, DEFAULT_ID, null);
+
+ var ex = assertThrowsWithCause(() -> run.accept(primitiveView),
UnexpectedNullValueException.class);
+ assertEquals(
+ "Failed to deserialize server response: Got unexpected null
value: use `getNullable` sibling method instead.",
+ ex.getMessage());
+ }
+
+ @Test
+ public void testGetNullable() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, null);
+ primitiveView.remove(null, -1L);
+
+ NullableValue<String> nullVal = primitiveView.getNullable(null,
DEFAULT_ID);
+ NullableValue<String> missingVal = primitiveView.getNullable(null,
-1L);
+
+ assertNull(nullVal.get());
+ assertNull(missingVal);
+ }
+
+ @Test
+ public void testGetNullableAndPut() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, null);
+ primitiveView.remove(null, -1L);
+
+ NullableValue<String> nullVal = primitiveView.getNullableAndPut(null,
DEFAULT_ID, DEFAULT_NAME);
+ NullableValue<String> missingVal =
primitiveView.getNullableAndPut(null, -1L, DEFAULT_NAME);
+
+ assertNull(nullVal.get());
+ assertNull(missingVal);
+ }
+
+ @Test
+ public void testGetNullableAndRemove() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, null);
+ primitiveView.remove(null, -1L);
+
+ NullableValue<String> nullVal =
primitiveView.getNullableAndRemove(null, DEFAULT_ID);
+ NullableValue<String> missingVal =
primitiveView.getNullableAndRemove(null, -1L);
+
+ assertNull(nullVal.get());
+ assertNull(missingVal);
+ }
+
+ @Test
+ public void testGetNullableAndReplace() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, null);
+ primitiveView.remove(null, -1L);
+
+ NullableValue<String> nullVal =
primitiveView.getNullableAndReplace(null, DEFAULT_ID, DEFAULT_NAME);
+ NullableValue<String> missingVal =
primitiveView.getNullableAndReplace(null, -1L, DEFAULT_NAME);
+
+ assertNull(nullVal.get());
+ assertNull(missingVal);
+ }
+
+ @Test
+ public void testGetOrDefault() {
+ KeyValueView<Long, String> primitiveView =
defaultTable().keyValueView(Mapper.of(Long.class), Mapper.of(String.class));
+
+ primitiveView.put(null, DEFAULT_ID, DEFAULT_NAME);
+ primitiveView.put(null, -1L, null);
+ primitiveView.remove(null, -2L);
+
+ assertNull(primitiveView.getOrDefault(null, -1L, "default"));
+ assertEquals(DEFAULT_NAME, primitiveView.getOrDefault(null,
DEFAULT_ID, "default"));
+ assertEquals("default", primitiveView.getOrDefault(null, -2L,
"default"));
+ }
}