This is an automated email from the ASF dual-hosted git repository.
amashenkov 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 c7d44a5 IGNITE-15519: Make tuples serializable (#343)
c7d44a5 is described below
commit c7d44a54548f2225962261e2a5d8c535bfdf5a25
Author: Andrew V. Mashenkov <[email protected]>
AuthorDate: Fri Sep 17 14:21:08 2021 +0300
IGNITE-15519: Make tuples serializable (#343)
---
.../java/org/apache/ignite/table/TupleImpl.java | 55 ++++---
.../handler/requests/table/ClientTableCommon.java | 6 +-
.../java/org/apache/ignite/client/CustomTuple.java | 28 ++--
.../schema/registry/UpgradingRowAdapter.java | 3 +-
.../org/apache/ignite/internal/schema/row/Row.java | 8 +-
.../internal/table/AbstractRowTupleAdapter.java | 41 ++---
.../internal/table/MutableRowTupleAdapter.java | 88 ++++++++--
.../internal/table/MutableRowTupleAdapterTest.java | 179 ++++++++++++++++++++-
.../internal/table/impl/TestTupleBuilder.java | 28 ++--
.../org/apache/ignite/table/TupleImplTest.java | 62 ++++++-
10 files changed, 396 insertions(+), 102 deletions(-)
diff --git a/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java
b/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java
index 279dd27..d6481cf 100644
--- a/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java
+++ b/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java
@@ -17,6 +17,8 @@
package org.apache.ignite.table;
+import java.io.IOException;
+import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -35,9 +37,16 @@ import org.jetbrains.annotations.NotNull;
/**
* Simple tuple implementation.
*/
-public class TupleImpl implements Tuple {
- /** Column name -> index mapping. */
- private final Map<String, Integer> colIdxMap;
+public class TupleImpl implements Tuple, Serializable {
+ /** Version UID. */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Column name -> index mapping.
+ * <p>
+ * Note: Transient because it's recoverable from {@link #colNames}.
+ */
+ private transient Map<String, Integer> colIdxMap;
/** Columns names. */
private final List<String> colNames;
@@ -49,7 +58,7 @@ public class TupleImpl implements Tuple {
* Creates tuple.
*/
public TupleImpl() {
- this(new HashMap<>(), new ArrayList(), new ArrayList());
+ this(new HashMap<>(), new ArrayList<>(), new ArrayList<>());
}
/**
@@ -58,16 +67,7 @@ public class TupleImpl implements Tuple {
* @param capacity Initial capacity.
*/
public TupleImpl(int capacity) {
- this(new HashMap<>(capacity), new ArrayList(capacity), new
ArrayList(capacity));
- }
-
- /**
- * Copying constructor.
- *
- * @param tuple Tuple to copy.
- */
- public TupleImpl(@NotNull TupleImpl tuple) {
- this(new HashMap<>(tuple.colIdxMap), new ArrayList<>(tuple.colNames),
new ArrayList<>(tuple.vals));
+ this(new HashMap<>(capacity), new ArrayList<>(capacity), new
ArrayList<>(capacity));
}
/**
@@ -97,9 +97,7 @@ public class TupleImpl implements Tuple {
/** {@inheritDoc} */
@Override public Tuple set(@NotNull String columnName, Object val) {
- Objects.nonNull(columnName);
-
- int idx = colIdxMap.computeIfAbsent(columnName, name ->
colIdxMap.size());
+ int idx =
colIdxMap.computeIfAbsent(Objects.requireNonNull(columnName), name ->
colIdxMap.size());
if (idx == colNames.size()) {
colNames.add(idx, columnName);
@@ -137,7 +135,7 @@ public class TupleImpl implements Tuple {
@Override public <T> T valueOrDefault(@NotNull String columnName, T def) {
int idx = columnIndex(columnName);
- return (idx == -1) ? def : (T) vals.get(idx);
+ return (idx == -1) ? def : (T)vals.get(idx);
}
/** {@inheritDoc} */
@@ -147,14 +145,14 @@ public class TupleImpl implements Tuple {
if (idx == -1)
throw new IllegalArgumentException("Column not found: columnName="
+ columnName);
- return (T) vals.get(idx);
+ return (T)vals.get(idx);
}
/** {@inheritDoc} */
@Override public <T> T value(int columnIndex) {
Objects.checkIndex(columnIndex, vals.size());
- return (T) vals.get(columnIndex);
+ return (T)vals.get(columnIndex);
}
/** {@inheritDoc} */
@@ -314,4 +312,21 @@ public class TupleImpl implements Tuple {
}
};
}
+
+ /**
+ * Deserializes object.
+ *
+ * @param in Input object stream.
+ * @throws IOException If failed.
+ * @throws ClassNotFoundException If failed.
+ */
+ private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ in.defaultReadObject();
+
+ // Recover column name->index mapping.
+ colIdxMap = new HashMap<>(colNames.size());
+
+ for (int i = 0; i < colNames.size(); i++)
+ colIdxMap.put(colNames.get(i), i);
+ }
}
diff --git
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
index eca5f0f..ac5580a 100644
---
a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
+++
b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
@@ -287,10 +287,10 @@ class ClientTableCommon {
boolean keyOnly,
SchemaDescriptor schema
) {
- var tuple = Tuple.create();
-
var cnt = keyOnly ? schema.keyColumns().length() : schema.length();
+ var tuple = Tuple.create(cnt);
+
for (int i = 0; i < cnt; i++) {
if (unpacker.getNextFormat() == MessageFormat.NIL) {
unpacker.skipValue();
@@ -311,7 +311,7 @@ class ClientTableCommon {
*/
public static Tuple readTupleSchemaless(ClientMessageUnpacker unpacker) {
var cnt = unpacker.unpackMapHeader();
- var tuple = Tuple.create();
+ var tuple = Tuple.create(cnt);
for (int i = 0; i < cnt; i++) {
var colName = unpacker.unpackString();
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
index 55bdf4e..8d3db6e 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
@@ -64,7 +64,7 @@ public class CustomTuple implements Tuple {
return null;
}
- @Override public int columnIndex(String columnName) {
+ @Override public int columnIndex(@NotNull String columnName) {
switch (columnName) {
case "id":
return 0;
@@ -75,7 +75,7 @@ public class CustomTuple implements Tuple {
return -1;
}
- @Override public <T> T valueOrDefault(String columnName, T def) {
+ @Override public <T> T valueOrDefault(@NotNull String columnName, T def) {
switch (columnName) {
case "id":
return (T)id;
@@ -86,11 +86,11 @@ public class CustomTuple implements Tuple {
return def;
}
- @Override public Tuple set(String columnName, Object value) {
+ @Override public Tuple set(@NotNull String columnName, Object value) {
throw new UnsupportedOperationException("Tuple is immutable.");
}
- @Override public <T> T value(String columnName) {
+ @Override public <T> T value(@NotNull String columnName) {
return valueOrDefault(columnName, null);
}
@@ -105,7 +105,7 @@ public class CustomTuple implements Tuple {
return null;
}
- @Override public BinaryObject binaryObjectValue(String columnName) {
+ @Override public BinaryObject binaryObjectValue(@NotNull String
columnName) {
throw new UnsupportedOperationException();
}
@@ -113,7 +113,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public byte byteValue(String columnName) {
+ @Override public byte byteValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -121,7 +121,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public short shortValue(String columnName) {
+ @Override public short shortValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -129,7 +129,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public int intValue(String columnName) {
+ @Override public int intValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -137,7 +137,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public long longValue(String columnName) {
+ @Override public long longValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -145,7 +145,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public float floatValue(String columnName) {
+ @Override public float floatValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -153,7 +153,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public double doubleValue(String columnName) {
+ @Override public double doubleValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -161,7 +161,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public String stringValue(String columnName) {
+ @Override public String stringValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -169,7 +169,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public UUID uuidValue(String columnName) {
+ @Override public UUID uuidValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
@@ -177,7 +177,7 @@ public class CustomTuple implements Tuple {
throw new UnsupportedOperationException();
}
- @Override public BitSet bitmaskValue(String columnName) {
+ @Override public BitSet bitmaskValue(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
diff --git
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
index f990e5c..9da0a54 100644
---
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
+++
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.SchemaException;
import org.apache.ignite.internal.schema.mapping.ColumnMapper;
import org.apache.ignite.internal.schema.row.Row;
+import org.jetbrains.annotations.NotNull;
/**
* Adapter for row of older schema.
@@ -58,7 +59,7 @@ class UpgradingRowAdapter extends Row {
}
/** {@inheritDoc} */
- @Override public SchemaDescriptor schema() {
+ @Override @NotNull public SchemaDescriptor schema() {
return schema;
}
diff --git
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
index 9d141a7..91fb5be 100644
---
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
+++
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
@@ -37,6 +37,7 @@ import org.apache.ignite.internal.schema.NativeTypeSpec;
import org.apache.ignite.internal.schema.SchemaAware;
import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.TemporalNativeType;
+import org.jetbrains.annotations.NotNull;
/**
* Schema-aware row.
@@ -71,7 +72,7 @@ public class Row implements BinaryRow, SchemaAware {
/**
* @return Row schema.
*/
- @Override public SchemaDescriptor schema() {
+ @Override @NotNull public SchemaDescriptor schema() {
return schema;
}
@@ -486,13 +487,10 @@ public class Row implements BinaryRow, SchemaAware {
*
* @param colIdx Column index.
* @param type Expected column type.
- * @return {@code -1} if value is {@code null} for a column,
- * or {@link Long#MAX_VALUE} if column is unknown,
- * otherwise encoded offset + length of the column.
+ * @return {@code -1} if value is {@code null} for a column, otherwise
encoded offset + length of the column.
* @see #offset(long)
* @see #length(long)
* @see InvalidTypeException If actual column type does not match the
requested column type.
- * @see org.apache.ignite.internal.schema.registry.UpgradingRowAdapter
*/
protected long findColumn(int colIdx, NativeTypeSpec type) throws
InvalidTypeException {
// Get base offset (key start or value start) for the given column.
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java
index 51891ec..f1062f9 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java
@@ -33,14 +33,16 @@ import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.row.Row;
import org.apache.ignite.table.Tuple;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
/**
* Abstract row tuple adapter.
*/
public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware {
- /** Underlying row. */
- protected Row row;
+ /**
+ * Underlying row.
+ * Note: Marked transient to prevent unwanted serialization of the schema
and\or other context.
+ */
+ protected transient Row row;
/**
* Creates Tuple adapter for row.
@@ -52,8 +54,8 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override @Nullable public SchemaDescriptor schema() {
- return row == null ? null : row.schema();
+ @Override public SchemaDescriptor schema() {
+ return row.schema();
}
/** {@inheritDoc} */
@@ -81,25 +83,25 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
final Column col = row.schema().column(columnName);
- return col == null ? defaultValue : (T)
col.type().spec().objectValue(row, col.schemaIndex());
+ return col == null ? defaultValue :
(T)col.type().spec().objectValue(row, col.schemaIndex());
}
/** {@inheritDoc} */
@Override public <T> T value(@NotNull String columnName) {
final Column col = rowColumnByName(columnName);
- return (T) col.type().spec().objectValue(row, col.schemaIndex());
+ return (T)col.type().spec().objectValue(row, col.schemaIndex());
}
@Override public <T> T value(int columnIndex) {
Column col = rowColumnByIndex(columnIndex);
- return (T) col.type().spec().objectValue(row, col.schemaIndex());
+ return (T)col.type().spec().objectValue(row, col.schemaIndex());
}
/** {@inheritDoc} */
- @Override public BinaryObject binaryObjectValue(String columnName) {
+ @Override public BinaryObject binaryObjectValue(@NotNull String
columnName) {
Column col = rowColumnByName(columnName);
return BinaryObjects.wrap(row.bytesValue(col.schemaIndex()));
@@ -113,7 +115,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public byte byteValue(String columnName) {
+ @Override public byte byteValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.byteValue(col.schemaIndex());
@@ -127,7 +129,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public short shortValue(String columnName) {
+ @Override public short shortValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.shortValue(col.schemaIndex());
@@ -141,7 +143,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public int intValue(String columnName) {
+ @Override public int intValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.intValue(col.schemaIndex());
@@ -155,7 +157,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public long longValue(String columnName) {
+ @Override public long longValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.longValue(col.schemaIndex());
@@ -169,7 +171,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public float floatValue(String columnName) {
+ @Override public float floatValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.floatValue(col.schemaIndex());
@@ -183,7 +185,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public double doubleValue(String columnName) {
+ @Override public double doubleValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.doubleValue(col.schemaIndex());
@@ -197,7 +199,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public String stringValue(String columnName) {
+ @Override public String stringValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.stringValue(col.schemaIndex());
@@ -211,7 +213,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public UUID uuidValue(String columnName) {
+ @Override public UUID uuidValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.uuidValue(col.schemaIndex());
@@ -225,7 +227,7 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/** {@inheritDoc} */
- @Override public BitSet bitmaskValue(String columnName) {
+ @Override public BitSet bitmaskValue(@NotNull String columnName) {
Column col = rowColumnByName(columnName);
return row.bitmaskValue(col.schemaIndex());
@@ -330,7 +332,8 @@ public abstract class AbstractRowTupleAdapter implements
Tuple, SchemaAware {
}
/**
- * Returns row column for index.
+ * Returns row column for given column index.
+ *
* @param columnIndex Column index.
* @return Column.
*/
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/MutableRowTupleAdapter.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/MutableRowTupleAdapter.java
index 6c46ca5..12a0aea 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/MutableRowTupleAdapter.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/MutableRowTupleAdapter.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.table;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -25,15 +27,42 @@ import java.util.BitSet;
import java.util.Iterator;
import java.util.UUID;
import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.row.Row;
import org.apache.ignite.table.Tuple;
import org.apache.ignite.table.TupleImpl;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
- * Mutable tuple adapter for a row.
+ * Adapter provides Tuple access methods and mutability for a Row.
+ * <p>
+ * Once adapter get created, it delegates all data access methods to the
{@link #row}.
+ * <p>
+ * Mutability.
+ * Underlying Row is immutable serialized object and can't be mutated directly.
+ * Because of this fact, first call of {@link #set(String, Object)} method
implies the Row conversion to a Tuple.
+ * <p>
+ * After the first mutation the {@link #tuple} becomes a full copy of {@link
#row},
+ * all data access methods delegate to a {@link #tuple}, and the {@link #row}
one is no longer useful.
+ * The adapter acts as simple schema-less tuple {@link TupleImpl} and {@link
#schema()} return null.
+ * <p>
+ * Serialization.
+ * Row access methods implicitly require a context (schema) for a binary data
reading, The context may be huge
+ * comparing to a row data, and its serialization is unwanted.
+ * So, Row firstly is converted to Tuple.
+ * <p>
+ * Because of after that the adapter will act as underlying tuple {@link
TupleImpl},
+ * the adapter will be substituted unconditionally with the tuple itself
during deserialization.
+ *
+ * @see TupleImpl
+ * @see #unmarshalRow()
+ * @see #writeReplace()
*/
-public class MutableRowTupleAdapter extends AbstractRowTupleAdapter {
+public class MutableRowTupleAdapter extends AbstractRowTupleAdapter implements
Serializable {
+ // Default constructor and serialVersionUID not needed because, actually,
+ // this object never get serialized, it's unconditionally substituted
during serialization.
+
/** Tuple with overwritten data. */
protected TupleImpl tuple;
@@ -47,6 +76,11 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
+ @Override public @Nullable SchemaDescriptor schema() {
+ return tuple != null ? null : super.schema();
+ }
+
+ /** {@inheritDoc} */
@Override public int columnCount() {
return tuple != null ? tuple.columnCount() : super.columnCount();
}
@@ -77,7 +111,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public BinaryObject binaryObjectValue(String columnName) {
+ @Override public BinaryObject binaryObjectValue(@NotNull String
columnName) {
return tuple != null ? tuple.binaryObjectValue(columnName) :
super.binaryObjectValue(columnName);
}
@@ -87,7 +121,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public byte byteValue(String columnName) {
+ @Override public byte byteValue(@NotNull String columnName) {
return tuple != null ? tuple.byteValue(columnName) :
super.byteValue(columnName);
}
@@ -97,7 +131,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public short shortValue(String columnName) {
+ @Override public short shortValue(@NotNull String columnName) {
return tuple != null ? tuple.shortValue(columnName) :
super.shortValue(columnName);
}
@@ -107,7 +141,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public int intValue(String columnName) {
+ @Override public int intValue(@NotNull String columnName) {
return tuple != null ? tuple.intValue(columnName) :
super.intValue(columnName);
}
@@ -117,7 +151,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public long longValue(String columnName) {
+ @Override public long longValue(@NotNull String columnName) {
return tuple != null ? tuple.longValue(columnName) :
super.longValue(columnName);
}
@@ -127,7 +161,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public float floatValue(String columnName) {
+ @Override public float floatValue(@NotNull String columnName) {
return tuple != null ? tuple.floatValue(columnName) :
super.floatValue(columnName);
}
@@ -137,7 +171,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public double doubleValue(String columnName) {
+ @Override public double doubleValue(@NotNull String columnName) {
return tuple != null ? tuple.doubleValue(columnName) :
super.doubleValue(columnName);
}
@@ -147,7 +181,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public String stringValue(String columnName) {
+ @Override public String stringValue(@NotNull String columnName) {
return tuple != null ? tuple.stringValue(columnName) :
super.stringValue(columnName);
}
@@ -157,7 +191,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public UUID uuidValue(String columnName) {
+ @Override public UUID uuidValue(@NotNull String columnName) {
return tuple != null ? tuple.uuidValue(columnName) :
super.uuidValue(columnName);
}
@@ -167,7 +201,7 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
}
/** {@inheritDoc} */
- @Override public BitSet bitmaskValue(String columnName) {
+ @Override public BitSet bitmaskValue(@NotNull String columnName) {
return tuple != null ? tuple.bitmaskValue(columnName) :
super.bitmaskValue(columnName);
}
@@ -223,15 +257,37 @@ public class MutableRowTupleAdapter extends
AbstractRowTupleAdapter {
/** {@inheritDoc} */
@Override public Tuple set(@NotNull String columnName, Object value) {
+ unmarshalRow();
+
+ tuple.set(columnName, value);
+
+ return this;
+ }
+
+ /**
+ * Converts immutable row to mutable tuple.
+ */
+ private void unmarshalRow() {
if (tuple == null) {
- TupleImpl tuple0 = new TupleImpl(this);
+ tuple = new TupleImpl(this);
- tuple = tuple0;
row = null;
}
+ }
- tuple.set(columnName, value);
+ /**
+ * Overrides default serialization flow.
+ * Serialized {@code tuple} instead of {@code this}, as wrapper make no
sense after tuple is fully materialized.
+ * <p>
+ * Note: The method has protected modifier and subclass access to this
method follows java accessibility rules.
+ * Thus, class successors may obtain similar behaviour.
+ *
+ * @return Tuple to serialize.
+ * @throws ObjectStreamException If failed.
+ */
+ protected Object writeReplace() throws ObjectStreamException {
+ unmarshalRow();
- return this;
+ return tuple;
}
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/MutableRowTupleAdapterTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/MutableRowTupleAdapterTest.java
index c902c1b..f914750 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/MutableRowTupleAdapterTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/MutableRowTupleAdapterTest.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.table;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
@@ -371,17 +375,182 @@ public class MutableRowTupleAdapterTest {
checkTuples(schema, tuple, rowTuple);
}
- private void checkTuples(SchemaDescriptor schema, Tuple tuple, Tuple
rowTuple) {
+ @Test
+ public void testSerialization() throws Exception {
+ Random rnd = new Random();
+
+ SchemaDescriptor schema = new SchemaDescriptor(tbl.tableId(), 42,
+ new Column[]{new Column("keyUuidCol", NativeTypes.UUID, true)},
+ new Column[]{
+ new Column("valByteCol", INT8, true),
+ new Column("valShortCol", INT16, true),
+ new Column("valIntCol", INT32, true),
+ new Column("valLongCol", INT64, true),
+ new Column("valFloatCol", FLOAT, true),
+ new Column("valDoubleCol", DOUBLE, true),
+ new Column("valDateCol", DATE, true),
+ new Column("valTimeCol", time(), true),
+ new Column("valDateTimeCol", datetime(), true),
+ new Column("valTimeStampCol", timestamp(), true),
+ new Column("valBitmask1Col", NativeTypes.bitmaskOf(22), true),
+ new Column("valBytesCol", BYTES, false),
+ new Column("valStringCol", STRING, false),
+ new Column("valNumberCol", NativeTypes.numberOf(20), false),
+ new Column("valDecimalCol", NativeTypes.decimalOf(25, 5),
false),
+ }
+ );
+
+ Tuple tup1 = new TupleImpl()
+ .set("valByteCol", (byte)1)
+ .set("valShortCol", (short)2)
+ .set("valIntCol", 3)
+ .set("valLongCol", 4L)
+ .set("valFloatCol", 0.055f)
+ .set("valDoubleCol", 0.066d)
+ .set("keyUuidCol", UUID.randomUUID())
+ .set("valDateCol", LocalDate.now())
+ .set("valDateTimeCol", LocalDateTime.now())
+ .set("valTimeCol", LocalTime.now())
+ .set("valTimeStampCol", Instant.now())
+ .set("valBitmask1Col", randomBitSet(rnd, 12))
+ .set("valBytesCol", IgniteTestUtils.randomBytes(rnd,
13))
+ .set("valStringCol",
IgniteTestUtils.randomString(rnd, 14))
+ .set("valNumberCol",
BigInteger.valueOf(rnd.nextLong()))
+ .set("valDecimalCol",
BigDecimal.valueOf(rnd.nextLong(), 5));
+
+ TupleMarshaller marshaller = new TupleMarshallerImpl(null, tbl, new
DummySchemaManagerImpl(schema));
+
+ Row row = new Row(schema, new
ByteBufferRow(marshaller.marshal(tup1).bytes()));
+
+ Tuple tup2 = deserializeTuple(serializeTuple(TableRow.tuple(row)));
+
+ assertTupleEquals(tup1, tup2);
+ }
+
+ @Test
+ public void testKeyValueSerialization() throws Exception {
+ Random rnd = new Random();
+
+ SchemaDescriptor schema = new SchemaDescriptor(tbl.tableId(), 42,
+ new Column[]{new Column("keyUuidCol", NativeTypes.UUID, true)},
+ new Column[]{
+ new Column("valByteCol", INT8, true),
+ new Column("valShortCol", INT16, true),
+ new Column("valIntCol", INT32, true),
+ new Column("valLongCol", INT64, true),
+ new Column("valFloatCol", FLOAT, true),
+ new Column("valDoubleCol", DOUBLE, true),
+ new Column("valDateCol", DATE, true),
+ new Column("valTimeCol", time(), true),
+ new Column("valDateTimeCol", datetime(), true),
+ new Column("valTimeStampCol", timestamp(), true),
+ new Column("valBitmask1Col", NativeTypes.bitmaskOf(22), true),
+ new Column("valBytesCol", BYTES, false),
+ new Column("valStringCol", STRING, false),
+ new Column("valNumberCol", NativeTypes.numberOf(20), false),
+ new Column("valDecimalCol", NativeTypes.decimalOf(25, 5),
false),
+ }
+ );
+
+ Tuple key1 = new TupleImpl().set("keyUuidCol", UUID.randomUUID());
+ Tuple val1 = new TupleImpl()
+ .set("valByteCol", (byte)1)
+ .set("valShortCol", (short)2)
+ .set("valIntCol", 3)
+ .set("valLongCol", 4L)
+ .set("valFloatCol", 0.055f)
+ .set("valDoubleCol", 0.066d)
+ .set("valDateCol", LocalDate.now())
+ .set("valDateTimeCol", LocalDateTime.now())
+ .set("valTimeCol", LocalTime.now())
+ .set("valTimeStampCol", Instant.now())
+ .set("valBitmask1Col", randomBitSet(rnd, 12))
+ .set("valBytesCol", IgniteTestUtils.randomBytes(rnd,
13))
+ .set("valStringCol",
IgniteTestUtils.randomString(rnd, 14))
+ .set("valNumberCol",
BigInteger.valueOf(rnd.nextLong()))
+ .set("valDecimalCol",
BigDecimal.valueOf(rnd.nextLong(), 5));
+
+ TupleMarshaller marshaller = new TupleMarshallerImpl(null, tbl, new
DummySchemaManagerImpl(schema));
+
+ Row row = new Row(schema, new ByteBufferRow(marshaller.marshal(key1,
val1).bytes()));
+
+ Tuple key2 = deserializeTuple(serializeTuple(TableRow.keyTuple(row)));
+ Tuple val2 =
deserializeTuple(serializeTuple(TableRow.valueTuple(row)));
+
+ assertTupleEquals(key1, key2);
+ assertTupleEquals(val1, val2);
+ }
+
+ /**
+ * Deserializes tuple.
+ *
+ * @param data Tuple bytes.
+ * @return Tuple.
+ * @throws Exception If failed.
+ */
+ private Tuple deserializeTuple(byte[] data) throws Exception {
+ try (ObjectInputStream is = new ObjectInputStream(new
ByteArrayInputStream(data))) {
+ return (Tuple)is.readObject();
+ }
+ }
+
+ /**
+ * Serailizes tuple.
+ *
+ * @param tup Tuple.
+ * @return Tuple bytes.
+ * @throws Exception If failed.
+ */
+ private byte[] serializeTuple(Tuple tup) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
+ os.writeObject(tup);
+ }
+
+ return baos.toByteArray();
+ }
+
+ /**
+ * Assert that {@code expected} and {@code actual} tuples are equal.
+ *
+ * @param expected Expected tuple.
+ * @param actual Actual tuple.
+ */
+ private void assertTupleEquals(Tuple expected, Tuple actual) {
+ assertEquals(expected.columnCount(), actual.columnCount(), "Tuple size
mismatch");
+
+ for (int i = 0; i < expected.columnCount(); i++) {
+ String name = expected.columnName(i);
+
+ if (expected.value(i) instanceof byte[]) {
+ assertArrayEquals((byte[])expected.value(i),
actual.value(actual.columnIndex(name)), "columnIdx=" + i);
+ assertArrayEquals((byte[])expected.value(name),
actual.value(name), "columnName=" + name);
+ } else {
+ assertEquals((Object)expected.value(i),
actual.value(actual.columnIndex(name)), "columnIdx=" + i);
+ assertEquals((Object)expected.value(name), actual.value(name),
"columnName=" + name);
+ }
+ }
+ }
+
+ /**
+ * Check tuple column accessors.
+ *
+ * @param schema Schema to check against.
+ * @param expected Tuple with expected values.
+ * @param actual Tuple to check.
+ */
+ private void checkTuples(SchemaDescriptor schema, Tuple expected, Tuple
actual) {
for (int i = 0; i < schema.length(); i++) {
Column col = schema.column(i);
String name = col.name();
if (col.type().spec() == NativeTypeSpec.BYTES) {
-
assertArrayEquals((byte[])tuple.value(tuple.columnIndex(name)),
rowTuple.value(rowTuple.columnIndex(name)), "columnIdx=" + i);
- assertArrayEquals((byte[])tuple.value(name),
rowTuple.value(name), "columnName=" + name);
+
assertArrayEquals((byte[])expected.value(expected.columnIndex(name)),
actual.value(actual.columnIndex(name)), "columnIdx=" + i);
+ assertArrayEquals((byte[])expected.value(name),
actual.value(name), "columnName=" + name);
} else {
- assertEquals((Object)tuple.value(tuple.columnIndex(name)),
rowTuple.value(rowTuple.columnIndex(name)), "columnIdx=" + i);
- assertEquals((Object)tuple.value(name), rowTuple.value(name),
"columnName=" + name);
+
assertEquals((Object)expected.value(expected.columnIndex(name)),
actual.value(actual.columnIndex(name)), "columnIdx=" + i);
+ assertEquals((Object)expected.value(name), actual.value(name),
"columnName=" + name);
}
}
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
index f83cf2a..50b3933 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
@@ -39,19 +39,19 @@ public class TestTupleBuilder implements Tuple {
private final Map<String, Object> map = new HashMap<>();
/** {@inheritDoc} */
- @Override public TestTupleBuilder set(String columnName, Object value) {
+ @Override public TestTupleBuilder set(@NotNull String columnName, Object
value) {
map.put(columnName, value);
return this;
}
/** {@inheritDoc} */
- @Override public <T> T valueOrDefault(String columnName, T def) {
+ @Override public <T> T valueOrDefault(@NotNull String columnName, T def) {
return (T)map.getOrDefault(columnName, def);
}
/** {@inheritDoc} */
- @Override public <T> T value(String columnName) {
+ @Override public <T> T value(@NotNull String columnName) {
return (T)map.get(columnName);
}
@@ -71,12 +71,12 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public int columnIndex(String columnName) {
+ @Override public int columnIndex(@NotNull String columnName) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
- @Override public BinaryObject binaryObjectValue(String columnName) {
+ @Override public BinaryObject binaryObjectValue(@NotNull String
columnName) {
byte[] data = value(columnName);
return BinaryObjects.wrap(data);
@@ -88,7 +88,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public byte byteValue(String columnName) {
+ @Override public byte byteValue(@NotNull String columnName) {
return value(columnName);
}
@@ -98,7 +98,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public short shortValue(String columnName) {
+ @Override public short shortValue(@NotNull String columnName) {
return value(columnName);
}
@@ -108,7 +108,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public int intValue(String columnName) {
+ @Override public int intValue(@NotNull String columnName) {
return value(columnName);
}
@@ -118,7 +118,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public long longValue(String columnName) {
+ @Override public long longValue(@NotNull String columnName) {
return value(columnName);
}
@@ -128,7 +128,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public float floatValue(String columnName) {
+ @Override public float floatValue(@NotNull String columnName) {
return value(columnName);
}
@@ -138,7 +138,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public double doubleValue(String columnName) {
+ @Override public double doubleValue(@NotNull String columnName) {
return value(columnName);
}
@@ -148,7 +148,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public String stringValue(String columnName) {
+ @Override public String stringValue(@NotNull String columnName) {
return value(columnName);
}
@@ -158,7 +158,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public UUID uuidValue(String columnName) {
+ @Override public UUID uuidValue(@NotNull String columnName) {
return value(columnName);
}
@@ -168,7 +168,7 @@ public class TestTupleBuilder implements Tuple {
}
/** {@inheritDoc} */
- @Override public BitSet bitmaskValue(String columnName) {
+ @Override public BitSet bitmaskValue(@NotNull String columnName) {
return value(columnName);
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java
b/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java
index 9841602..ef32e7d 100644
--- a/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java
@@ -17,6 +17,11 @@
package org.apache.ignite.table;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
@@ -36,13 +41,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests server tuple builder implementation.
- *
+ * <p>
* Should be in sync with org.apache.ignite.client.ClientTupleBuilderTest.
*/
public class TupleImplTest {
@Test
public void testValueReturnsValueByName() {
- assertEquals(3L, (Long) getTuple().value("id"));
+ assertEquals(3L, (Long)getTuple().value("id"));
assertEquals("Shirt", getTuple().value("name"));
}
@@ -54,7 +59,7 @@ public class TupleImplTest {
@Test
public void testValueReturnsValueByIndex() {
- assertEquals(3L, (Long) getTuple().value(0));
+ assertEquals(3L, (Long)getTuple().value(0));
assertEquals("Shirt", getTuple().value(1));
}
@@ -169,13 +174,60 @@ public class TupleImplTest {
}
}
+ @Test
+ public void testSerialization() throws IOException, ClassNotFoundException
{
+ Random rnd = new Random();
+
+ Tuple tup1 = new TupleImpl()
+ .set("valByteCol", (byte)1)
+ .set("valShortCol", (short)2)
+ .set("valIntCol", 3)
+ .set("valLongCol", 4L)
+ .set("valFloatCol", 0.055f)
+ .set("valDoubleCol", 0.066d)
+ .set("keyUuidCol", UUID.randomUUID())
+ .set("valDateCol", LocalDate.now())
+ .set("valDateTimeCol", LocalDateTime.now())
+ .set("valTimeCol", LocalTime.now())
+ .set("valTimeStampCol", Instant.now())
+ .set("valBitmask1Col", randomBitSet(rnd, 12))
+ .set("valBytesCol", IgniteTestUtils.randomBytes(rnd,
13))
+ .set("valStringCol",
IgniteTestUtils.randomString(rnd, 14))
+ .set("valNumberCol",
BigInteger.valueOf(rnd.nextLong()))
+ .set("valDecimalCol",
BigDecimal.valueOf(rnd.nextLong(), 5));
+
+ Tuple tup2;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
+ os.writeObject(tup1);
+ }
+
+ try (ObjectInputStream is = new ObjectInputStream(new
ByteArrayInputStream(baos.toByteArray()))) {
+ tup2 = (Tuple)is.readObject();
+ }
+
+ for (int i = 0; i < tup1.columnCount(); i++) {
+ String name = tup1.columnName(i);
+
+ if (tup1.value(i) instanceof byte[]) {
+ assertArrayEquals((byte[])tup1.value(i), tup2.value(i),
"columnIdx=" + i);
+ assertArrayEquals((byte[])tup1.value(name), tup2.value(name),
"columnName=" + name);
+ } else {
+ assertEquals((Object)tup1.value(i), tup1.value(i),
"columnIdx=" + i);
+ assertEquals((Object)tup1.value(name), tup1.value(name),
"columnName=" + name);
+ }
+ }
+ }
+
private static TupleImpl createTuple() {
return new TupleImpl();
}
private static Tuple getTuple() {
return new TupleImpl()
- .set("id", 3L)
- .set("name", "Shirt");
+ .set("id", 3L)
+ .set("name", "Shirt");
}
}