diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java index f7de015..7eb6606 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowBuffer.java @@ -1215,7 +1215,7 @@ public final class RowBuffer { * Produce a new scope from the current iterator position. * * @param edit An initialized iterator pointing at a scope. - * @param immutable True if the new scope should be marked immutable (read-only). + * @param immutable {@code true} if the new scope should be marked immutable (read-only). * @return A new scope beginning at the current iterator position. */ public RowCursor sparseIteratorReadScope(@Nonnull final RowCursor edit, boolean immutable) { diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java index 6ddba18..3e737c9 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/RowCursors.java @@ -108,13 +108,17 @@ public final class RowCursors { } public static void skip( - @Nonnull final RowCursor edit, @Nonnull final RowBuffer row, @Nonnull final RowCursor childScope) { + @Nonnull final RowCursor edit, @Nonnull final RowBuffer buffer, @Nonnull final RowCursor childScope) { + + checkNotNull(edit, "expected non-null edit"); + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(childScope, "expected non-null childScope"); checkArgument(childScope.start() == edit.valueOffset()); if (!(childScope.cellType() instanceof LayoutEndScope)) { //noinspection StatementWithEmptyBody - while (row.sparseIteratorMoveNext(childScope)) { + while (buffer.sparseIteratorMoveNext(childScope)) { } } diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java index 2ff162b..3c8b872 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReader.java @@ -3,6 +3,7 @@ package com.azure.data.cosmos.serialization.hybridrow.io; +import com.azure.data.cosmos.core.Json; import com.azure.data.cosmos.core.Out; import com.azure.data.cosmos.core.Utf8String; import com.azure.data.cosmos.serialization.hybridrow.Float128; @@ -41,10 +42,15 @@ import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarInt; import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutVarUInt; import com.azure.data.cosmos.serialization.hybridrow.layouts.TypeArgumentList; import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import io.netty.buffer.ByteBuf; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.IOException; import java.math.BigDecimal; import java.time.OffsetDateTime; import java.util.List; @@ -65,6 +71,7 @@ import static com.google.common.base.Strings.lenientFormat; * Modifying a {@link RowBuffer} invalidates any reader or child reader associated with it. In general{@link RowBuffer}s * should not be mutated while being enumerated. */ +@JsonSerialize(using = RowReader.JsonSerializer.class) public final class RowReader { private int columnIndex; @@ -818,7 +825,10 @@ public final class RowReader { * @param value On success, receives the value, undefined otherwise. * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. */ - public Result readUInt8(Out value) { + @Nonnull + public Result readUInt8(@Nonnull Out value) { + + checkNotNull(value, "expected non-null value"); switch (this.state) { @@ -839,6 +849,32 @@ public final class RowReader { } } + /** + * Returns a string representation of the object. In general, the + * {@code toString} method returns a string that + * "textually represents" this object. The result should + * be a concise but informative representation that is easy for a + * person to read. + * It is recommended that all subclasses override this method. + *

+ * The {@code toString} method for class {@code Object} + * returns a string consisting of the name of the class of which the + * object is an instance, the at-sign character `{@code @}', and + * the unsigned hexadecimal representation of the hash code of the + * object. In other words, this method returns a string equal to the + * value of: + *

+ *
+     * getClass().getName() + '@' + Integer.toHexString(hashCode())
+     * 
+ * + * @return a string representation of the object. + */ + @Override + public String toString() { + return Json.toString(this); + } + /** * Read the current field as a fixed length {@link UnixDateTime} value. * @@ -999,7 +1035,8 @@ public final class RowReader { * @param value On success, receives the value, undefined otherwise * @return {@link Result#SUCCESS} if the read is successful, an error {@link Result} otherwise. */ - private Result readPrimitiveValue(Out value) { + @Nonnull + private Result readPrimitiveValue(@Nonnull Out value) { final LayoutColumn column = this.columns.get(this.columnIndex); final LayoutType type = this.columns.get(this.columnIndex).type(); @@ -1017,9 +1054,8 @@ public final class RowReader { case VARIABLE: return type.>typeAs().readVariable(this.row, this.cursor, column, value); default: - assert false : lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); - value.set(null); - return Result.FAILURE; + String message = lenientFormat("expected FIXED or VARIABLE column storage, not %s", storage); + throw new IllegalStateException(message); } } @@ -1115,11 +1151,30 @@ public final class RowReader { DONE; public static final int BYTES = Byte.BYTES; + private final String friendlyName; + + States() { + this.friendlyName = this.name().toLowerCase(); + } + + public String friendlyName() { + return this.friendlyName; + } public static States from(byte value) { return values()[value]; } + /** + * Returns the friendly name of this enum constant, as returned by {@link #friendlyName()}. + * + * @return the friendly name of this enum constant + */ + @Override + public String toString() { + return this.friendlyName; + } + public byte value() { return (byte) this.ordinal(); } @@ -1186,4 +1241,30 @@ public final class RowReader { return this; } } + + static final class JsonSerializer extends StdSerializer { + + JsonSerializer() { + super(RowReader.class); + } + + @Override + public void serialize(RowReader value, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeStartObject(); + generator.writeStringField("path", value.path().toUtf16()); + generator.writeStringField("state", value.state == null ? null : value.state.friendlyName); + generator.writeObjectFieldStart("span"); + generator.writeNumberField("start", value.cursor.start()); + generator.writeNumberField("end", value.cursor.endOffset()); + generator.writeEndObject(); + LayoutColumn column = value.columns.get(value.columnIndex); + generator.writeObjectFieldStart("column"); + generator.writeStringField("fullPath", column.fullPath().toUtf16()); + generator.writeStringField("type", column.type().name()); + generator.writeNumberField("index", column.index()); + generator.writeNumberField("offset", column.offset()); + generator.writeEndObject(); + generator.writeEndObject(); + } + } } \ No newline at end of file diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java index cd976ac..99a0f1e 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutBuilder.java @@ -6,10 +6,12 @@ package com.azure.data.cosmos.serialization.hybridrow.layouts; import com.azure.data.cosmos.serialization.hybridrow.SchemaId; import com.azure.data.cosmos.serialization.hybridrow.schemas.StorageKind; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Stack; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; public final class LayoutBuilder { private LayoutBit.Allocator bitAllocator; @@ -36,27 +38,37 @@ public final class LayoutBuilder { this.reset(); } - public void addFixedColumn(String path, LayoutType type, boolean nullable, int length) { + public void addFixedColumn(@Nonnull String path, @Nonnull LayoutType type, boolean nullable, int length) { + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); checkArgument(length >= 0); checkArgument(!type.isVarint()); - LayoutColumn column; + final LayoutColumn column; + if (type.isNull()) { checkArgument(nullable); - LayoutBit nullBit = this.bitAllocator.allocate(); - column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), - this.fixedCount, 0, nullBit, LayoutBit.INVALID, 0); + LayoutBit boolBit = this.bitAllocator.allocate(); + LayoutBit nullBit = LayoutBit.INVALID; + int offset = 0; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + boolBit, nullBit, 0); } else if (type.isBoolean()) { LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; - LayoutBit boolbit = this.bitAllocator.allocate(); - column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), - this.fixedCount, 0, nullBit, boolbit, 0); + LayoutBit boolBit = this.bitAllocator.allocate(); + int offset = 0; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + nullBit, boolBit, 0); } else { + LayoutBit boolBit = LayoutBit.INVALID; LayoutBit nullBit = nullable ? this.bitAllocator.allocate() : LayoutBit.INVALID; - column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), - this.fixedCount, this.fixedSize, nullBit, LayoutBit.INVALID, length); - + int offset = this.fixedSize; + column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.FIXED, this.parent(), this.fixedCount, offset, + nullBit, boolBit, length); this.fixedSize += type.isFixed() ? type.size() : length; } @@ -74,31 +86,40 @@ public final class LayoutBuilder { this.scope.push(column); } - public void addSparseColumn(String path, LayoutType type) { + public void addSparseColumn(@Nonnull final String path, @Nonnull final LayoutType type) { - LayoutColumn column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), - this.sparseCount, -1, LayoutBit.INVALID, LayoutBit.INVALID, 0); + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); + + final LayoutColumn column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, + LayoutBit.INVALID, LayoutBit.INVALID, 0); this.sparseCount++; this.sparseColumns.add(column); } - public void addTypedScope(String path, LayoutType type, TypeArgumentList typeArgs) { + public void addTypedScope( + @Nonnull final String path, @Nonnull final LayoutType type, @Nonnull final TypeArgumentList typeArgs) { - LayoutColumn col = new LayoutColumn(path, type, typeArgs, StorageKind.SPARSE, this.parent(), this.sparseCount, - -1, LayoutBit.INVALID, LayoutBit.INVALID, 0); + final LayoutColumn column = new LayoutColumn( + path, type, typeArgs, StorageKind.SPARSE, this.parent(), this.sparseCount, -1, LayoutBit.INVALID, + LayoutBit.INVALID, 0); this.sparseCount++; - this.sparseColumns.add(col); + this.sparseColumns.add(column); } public void addVariableColumn(String path, LayoutType type, int length) { + checkNotNull(path, "expected non-null path"); + checkNotNull(type, "expected non-null type"); checkArgument(length >= 0); checkArgument(type.allowVariable()); - LayoutColumn column = new LayoutColumn(path, type, TypeArgumentList.EMPTY, StorageKind.VARIABLE, this.parent(), - this.varCount, this.varCount, this.bitAllocator.allocate(), LayoutBit.INVALID, length); + final LayoutColumn column = new LayoutColumn( + path, type, TypeArgumentList.EMPTY, StorageKind.VARIABLE, this.parent(), this.varCount, this.varCount, + this.bitAllocator.allocate(), LayoutBit.INVALID, length); this.varCount++; this.varColumns.add(column); diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java index 1dcfe92..6cfb1ca 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutFloat64.java @@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkArgument; public final class LayoutFloat64 extends LayoutTypePrimitive { public LayoutFloat64() { - super(LayoutCode.FLOAT_64, Double.BYTES / Byte.SIZE); + super(LayoutCode.FLOAT_64, Double.BYTES); } public boolean isFixed() { diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java index 974e65f..815a97d 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutInt64.java @@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkArgument; public final class LayoutInt64 extends LayoutTypePrimitive { public LayoutInt64() { - super(LayoutCode.INT_64, Long.BYTES / Byte.SIZE); + super(LayoutCode.INT_64, Long.BYTES); } public boolean isFixed() { diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java index a18ad2f..bb5da7d 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUDT.java @@ -33,9 +33,9 @@ public final class LayoutUDT extends LayoutPropertyScope { @Override @Nonnull - public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer row, int offset, @Nonnull Out lenInBytes) { + public TypeArgumentList readTypeArgumentList(@Nonnull RowBuffer row, int offset, @Nonnull Out lengthInBytes) { SchemaId schemaId = row.readSchemaId(offset); - lenInBytes.set(SchemaId.BYTES); + lengthInBytes.set(SchemaId.BYTES); return new TypeArgumentList(schemaId); } diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java index 62df625..af9787a 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt32.java @@ -11,6 +11,7 @@ import com.azure.data.cosmos.serialization.hybridrow.RowCursor; import javax.annotation.Nonnull; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; public final class LayoutUInt32 extends LayoutTypePrimitive { @@ -31,6 +32,11 @@ public final class LayoutUInt32 extends LayoutTypePrimitive { @Nonnull public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + checkArgument(scope.scopeType() instanceof LayoutUDT); if (!buffer.readBit(scope.start(), column.nullBit())) { diff --git a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java index 36280c0..1d678a5 100644 --- a/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java +++ b/java/src/main/java/com/azure/data/cosmos/serialization/hybridrow/layouts/LayoutUInt8.java @@ -11,6 +11,7 @@ import com.azure.data.cosmos.serialization.hybridrow.RowCursor; import javax.annotation.Nonnull; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; public final class LayoutUInt8 extends LayoutTypePrimitive { @@ -31,6 +32,11 @@ public final class LayoutUInt8 extends LayoutTypePrimitive { @Nonnull public Result readFixed(@Nonnull RowBuffer buffer, @Nonnull RowCursor scope, @Nonnull LayoutColumn column, @Nonnull Out value) { + checkNotNull(buffer, "expected non-null buffer"); + checkNotNull(scope, "expected non-null scope"); + checkNotNull(column, "expected non-null column"); + checkNotNull(value, "expected non-null value"); + checkArgument(scope.scopeType() instanceof LayoutUDT); if (!buffer.readBit(scope.start(), column.nullBit())) { diff --git a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java b/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java index 52d4916..1e13e0d 100644 --- a/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java +++ b/java/src/test/java/com/azure/data/cosmos/serialization/hybridrow/io/RowReaderTest.java @@ -73,21 +73,45 @@ public class RowReaderTest { private static Result visitFields(RowReader reader, int level) { + Out out = new Out(); + while (reader.read()) { - final Utf8String path = reader.path(); + Utf8String path = reader.path(); LayoutType type = reader.type(); - if (type == null) { - fail(lenientFormat("path: %s, type: null", path)); + if (path.isNull() || type == null) { + fail(lenientFormat("path: %s, type: %s", path, type)); } - System.out.println(lenientFormat("%s%s:\"%s\"", Strings.repeat(" ", level), path, type.layoutCode())); - Out out = new Out(); + System.out.println(lenientFormat("%s%s : %s", Strings.repeat(" ", level), path, type.name())); + + Result result = Result.SUCCESS; + out.set(null); switch (type.layoutCode()) { + case INT_32: { + result = reader.readInt32(out); + break; + } + case INT_64: { + result = reader.readInt64(out); + break; + } + case UINT_8: { + result = reader.readUInt8(out); + break; + } + case UINT_32: { + result = reader.readUInt32(out); + break; + } case UINT_64: { - Result result = reader.readUInt64(out); + result = reader.readUInt64(out); + break; + } + case GUID: { + result = reader.readGuid(out); break; } case NULL: @@ -95,11 +119,7 @@ public class RowReaderTest { case BOOLEAN_FALSE: case INT_8: case INT_16: - case INT_32: - case INT_64: - case UINT_8: case UINT_16: - case UINT_32: case VAR_INT: case VAR_UINT: case FLOAT_32: @@ -108,7 +128,6 @@ public class RowReaderTest { case DECIMAL: case DATE_TIME: case UNIX_DATE_TIME: - case GUID: case UTF_8: case BINARY: { break; @@ -155,11 +174,7 @@ public class RowReaderTest { case TYPED_TUPLE_SCOPE: case IMMUTABLE_TYPED_TUPLE_SCOPE: { - Result result = reader.readScope(null, (RowReader child, Object ignored) -> visitFields(child, level + 1)); - - if (result != Result.SUCCESS) { - return result; - } + result = reader.readScope(null, (RowReader child, Object ignored) -> visitFields(child, level + 1)); break; } case END_SCOPE: @@ -173,6 +188,9 @@ public class RowReaderTest { break; } } + if (result != Result.SUCCESS) { + return result; + } } return Result.SUCCESS; diff --git a/layout.xlsx b/layout.xlsx new file mode 100644 index 0000000..67d1a45 Binary files /dev/null and b/layout.xlsx differ