Progressed on deserializing schemas.

This commit is contained in:
David Noble
2019-09-17 20:29:21 -07:00
parent 5a73b29347
commit 60d7d73b14
16 changed files with 411 additions and 136 deletions

View File

@@ -3,29 +3,53 @@
package com.azure.data.cosmos.core; package com.azure.data.cosmos.core;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Optional; import java.util.Optional;
import static com.google.common.base.Strings.lenientFormat; import static com.google.common.base.Strings.lenientFormat;
public final class Json { public final class Json {
private static final ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(Json.class);
private static final ObjectReader reader = mapper.reader();
private static final ObjectWriter writer = mapper.writer(); private static final ObjectMapper mapper = new ObjectMapper(new JsonFactory()
.enable(JsonParser.Feature.ALLOW_COMMENTS));
private static final ObjectReader reader = mapper.reader()
.withFeatures(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
private static final ObjectWriter writer = mapper.writer()
.withFeatures(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
private Json() { private Json() {
} }
public static <T> Optional<T> parse(String value) { public static <T> Optional<T> parse(InputStream stream, Class<T> type) {
try { try {
return Optional.of(reader.<T>readValue(value)); return Optional.of(reader.forType(type).readValue(stream));
} catch (IOException e) { } catch (IOException error) {
logger.error("", error);
return Optional.empty();
}
}
public static <T> Optional<T> parse(String value, Class<T> type) {
try {
return Optional.of(reader.forType(type).readValue(value));
} catch (IOException error) {
logger.error("", error);
return Optional.empty(); return Optional.empty();
} }
} }

View File

@@ -61,15 +61,15 @@ public final class LayoutCompiler {
for (Property p : properties) { for (Property p : properties) {
LayoutType type = LayoutCompiler.logicalToPhysicalType(ns, p.propertyType(), typeArgs); LayoutType type = LayoutCompiler.logicalToPhysicalType(ns, p.type(), typeArgs);
switch (LayoutCodeTraits.clearImmutableBit(type.layoutCode())) { switch (LayoutCodeTraits.clearImmutableBit(type.layoutCode())) {
case OBJECT_SCOPE: { case OBJECT_SCOPE: {
if (!p.propertyType().nullable()) { if (!p.type().nullable()) {
throw new LayoutCompilationException("Non-nullable sparse column are not supported."); throw new LayoutCompilationException("Non-nullable sparse column are not supported.");
} }
ObjectPropertyType op = (ObjectPropertyType)p.propertyType(); ObjectPropertyType op = (ObjectPropertyType)p.type();
builder.addObjectScope(p.path(), type); builder.addObjectScope(p.path(), type);
LayoutCompiler.addProperties(builder, ns, type.layoutCode(), op.properties()); LayoutCompiler.addProperties(builder, ns, type.layoutCode(), op.properties());
builder.EndObjectScope(); builder.EndObjectScope();
@@ -87,7 +87,7 @@ public final class LayoutCompiler {
case TAGGED_SCOPE: case TAGGED_SCOPE:
case TAGGED2_SCOPE: case TAGGED2_SCOPE:
case SCHEMA: { case SCHEMA: {
if (!p.propertyType().nullable()) { if (!p.type().nullable()) {
throw new LayoutCompilationException("Non-nullable sparse column are not supported."); throw new LayoutCompilationException("Non-nullable sparse column are not supported.");
} }
builder.addTypedScope(p.path(), type, typeArgs.get()); builder.addTypedScope(p.path(), type, typeArgs.get());
@@ -100,9 +100,9 @@ public final class LayoutCompiler {
default: { default: {
if (p.propertyType() instanceof PrimitivePropertyType) { if (p.type() instanceof PrimitivePropertyType) {
PrimitivePropertyType pp = (PrimitivePropertyType) p.propertyType(); PrimitivePropertyType pp = (PrimitivePropertyType) p.type();
switch (pp.storage()) { switch (pp.storage()) {

View File

@@ -9,9 +9,13 @@ import com.google.common.base.Suppliers;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.CodeSource;
import java.util.Enumeration;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -41,17 +45,20 @@ public final class SystemSchema {
final String json; final String json;
try (final InputStream stream = SystemSchema.class.getResourceAsStream("SystemSchema.json")) { try (final InputStream stream = getResourceAsStream("SystemSchema.json")) {
Optional<Namespace> namespace = Namespace.parse(stream);
ByteBuf buffer = Unpooled.buffer(); ByteBuf buffer = Unpooled.buffer();
while (buffer.writeBytes(stream, 8192) == 8192) { } while (buffer.writeBytes(stream, 8192) == 8192) { }
json = buffer.readCharSequence(buffer.readableBytes(), StandardCharsets.UTF_8).toString(); json = buffer.readCharSequence(buffer.readableBytes(), StandardCharsets.UTF_8).toString();
} catch (IOException cause) { } catch (IOException cause) {
String message = lenientFormat("failed to load SystemSchema.json due to %s", cause); String message = lenientFormat("Failed to load SystemSchema.json due to %s", cause);
throw new IllegalStateException(message, cause); throw new IllegalStateException(message, cause);
} }
Optional<Namespace> namespace = Namespace.parse(json); Optional<Namespace> namespace = Namespace.parse(json);
checkState(namespace.isPresent(), "failed to load SystemSchema.json"); checkState(namespace.isPresent(), "Failed to parse SystemSchema.json");
return new LayoutResolverNamespace(namespace.get()); return new LayoutResolverNamespace(namespace.get());
}); });
@@ -62,4 +69,23 @@ public final class SystemSchema {
public static LayoutResolver layoutResolver() { public static LayoutResolver layoutResolver() {
return layoutResolver.get(); return layoutResolver.get();
} }
private static InputStream getResourceAsStream(final String name) throws IOException {
final CodeSource codeSource = SystemSchema.class.getProtectionDomain().getCodeSource();
final ClassLoader classLoader = SystemSchema.class.getClassLoader();
final String location = codeSource.getLocation().toString();
final Enumeration<URL> urls;
urls = classLoader.getResources(name);
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
if (url.toString().startsWith(location)) {
return url.openStream();
}
}
throw new FileNotFoundException(lenientFormat("cannot find resource at code source location %s", location));
}
} }

View File

@@ -4,31 +4,39 @@
package com.azure.data.cosmos.serialization.hybridrow.schemas; package com.azure.data.cosmos.serialization.hybridrow.schemas;
import com.azure.data.cosmos.core.Json; import com.azure.data.cosmos.core.Json;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public class Namespace { public class Namespace {
@JsonProperty(required = true)
private String name; private String name;
private SchemaLanguageVersion version = SchemaLanguageVersion.values()[0];
@JsonProperty(required = true)
private ArrayList<Schema> schemas; private ArrayList<Schema> schemas;
/** @JsonProperty(required = true)
* Initializes a new instance of the {@link Namespace} class. private SchemaLanguageVersion version;
*/
public Namespace() {
this.schemas(new ArrayList<Schema>());
}
/** /**
* The fully qualified identifier of the namespace. * The fully qualified name of the namespace.
*
* @return fully qualified name of the {@linkplain Namespace namespace}.
*/ */
public final String name() { public final String name() {
return this.name; return this.name;
} }
/**
* Sets the fully qualified name of the namespace.
*
* @param value fully qualified name of the {@linkplain Namespace namespace}.
* @return a reference to this {@linkplain Namespace namespace}.
*/
public final Namespace name(String value) { public final Namespace name(String value) {
this.name = value; this.name = value;
return this; return this;
@@ -54,11 +62,21 @@ public class Namespace {
/** /**
* The version of the HybridRow Schema Definition Language used to encode this namespace. * The version of the HybridRow Schema Definition Language used to encode this namespace.
*
* @return {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language used to encode this
* {@linkplain Namespace namespace}.
*/ */
public final SchemaLanguageVersion version() { public final SchemaLanguageVersion version() {
return this.version; return this.version;
} }
/**
* Sets the version of the HybridRow Schema Definition Language used to encode this namespace.
*
* @param value {linkplain SchemaLanguageVersion version} of the HybridRow Schema Definition Language that will be
* used to encode this {@linkplain Namespace namespace}.
* @return a reference to this {@linkplain Namespace namespace}.
*/
public final Namespace version(SchemaLanguageVersion value) { public final Namespace version(SchemaLanguageVersion value) {
this.version = value; this.version = value;
return this; return this;
@@ -67,12 +85,24 @@ public class Namespace {
/** /**
* Parse a JSON document and return a full namespace. * Parse a JSON document and return a full namespace.
* *
* @param value The JSON text to parse * @param value The JSON text to parse.
* @return A namespace containing a set of logical schemas. * @return A namespace containing a set of logical schemas.
*/ */
public static Optional<Namespace> parse(String value) { public static Optional<Namespace> parse(String value) {
Optional<Namespace> ns = Json.<Namespace>parse(value); Optional<Namespace> namespace = Json.parse(value, Namespace.class);
ns.ifPresent(SchemaValidator::validate); namespace.ifPresent(SchemaValidator::validate);
return ns; return namespace;
}
/**
* Parse a JSON document and return a full namespace.
*
* @param stream The JSON input stream to parse.
* @return A namespace containing a set of logical schemas.
*/
public static Optional<Namespace> parse(InputStream stream) {
Optional<Namespace> namespace = Json.parse(stream, Namespace.class);
namespace.ifPresent(SchemaValidator::validate);
return namespace;
} }
} }

View File

@@ -3,22 +3,26 @@
package com.azure.data.cosmos.serialization.hybridrow.schemas; package com.azure.data.cosmos.serialization.hybridrow.schemas;
import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* A primitive property. * A primitive property.
* <p> * <p>
* Primitive properties map to columns one-to-one. Primitive properties indicate how the * Primitive properties map to columns one-to-one. Primitive properties indicate how the column should be represented
* column should be represented within the row. * within the row.
*/ */
public class PrimitivePropertyType extends PropertyType { public class PrimitivePropertyType extends PropertyType {
@JsonProperty
private int length; private int length;
private StorageKind storage = StorageKind.values()[0];
@JsonProperty
private StorageKind storage;
/** /**
* The maximum allowable length in bytes. * The maximum allowable length in bytes.
* <p> * <p>
* This annotation is only valid for non-fixed length types. A value of 0 means the maximum * This annotation is only valid for non-fixed length types. A value of 0 means the maximum allowable length.
* allowable length.
*/ */
public final int length() { public final int length() {
return this.length; return this.length;

View File

@@ -3,56 +3,102 @@
package com.azure.data.cosmos.serialization.hybridrow.schemas; package com.azure.data.cosmos.serialization.hybridrow.schemas;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.annotation.Nonnull;
/** /**
* Describes a single property definition. * Describes a single property definition.
*/ */
public class Property { public class Property {
@JsonProperty(required = false)
private String comment; private String comment;
@JsonProperty(required = true)
private String path; private String path;
private PropertyType propertyType;
@JsonProperty(required = true)
private PropertyType type;
/** /**
* An (optional) comment describing the purpose of this property * An (optional) comment describing the purpose of this {@linkplain Property property}.
* <p> * <p>
* Comments are for documentary purpose only and do not affect the property at runtime. * Comments are for documentary purpose only and do not affect the property at runtime.
*
* @return the comment on this {@linkplain Schema property} or {@code null}, if there is no comment.
*/ */
public final String comment() { public final String comment() {
return this.comment; return this.comment;
} }
/**
* Sets the (optional) comment describing the purpose of this {@linkplain Property property}.
* <p>
* Comments are for documentary purpose only and do not affect the property at runtime.
*
* @param value a comment on this {@linkplain Property property} or {@code null} to remove the comment, if any, on
* this {@linkplain Property property}.
* @return a reference to this {@linkplain Property property}.
*/
public final Property comment(String value) { public final Property comment(String value) {
this.comment = value; this.comment = value;
return this; return this;
} }
/** /**
* The logical path of this property. * The logical path of this {@linkplain Property property}.
* <p> * <p>.
* For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined * For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined
* within nested structures. * within nested structures.
* <p> * <p>
* See the logical path specification for full details on both relative and absolute paths. * See the logical path specification for full details on both relative and absolute paths.
*
* @return the logical path of this {@linkplain Property property}.
*/ */
public final String path() { public final String path() {
return this.path; return this.path;
} }
public final void setPath(String value) { /**
* Sets the logical path of this {@linkplain Property property}.
* <p>.
* For complex properties (e.g. objects) the logical path forms a prefix to relative paths of properties defined
* within nested structures.
* <p>
* See the logical path specification for full details on both relative and absolute paths.
*
* @param value the logical path of this {@linkplain Property property}.
* @return a reference to this {@linkplain Property property}.
*/
public final Property path(@Nonnull String value) {
this.path = value; this.path = value;
return this;
} }
/** /**
* The type of the property. * The type of this {@linkplain Property property}.
* <p> * <p>
* Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex * Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex
* types may define one or more columns depending on their structure. * types may define one or more columns depending on their structure.
*
* @return the type of this {@linkplain Property property}.
*/ */
public final PropertyType propertyType() { public final PropertyType type() {
return this.propertyType; return this.type;
} }
public final void setPropertyType(PropertyType value) { /**
this.propertyType = value; * Sets the type of this {@linkplain Property property}.
* <p>
* Types may be simple (e.g. int8) or complex (e.g. object). Simple types always define a single column. Complex
* types may define one or more columns depending on their structure.
*
* @param value the type of this {@linkplain Property property}.
* @return a reference to this {@linkplain Property property}.
*/
public final Property type(PropertyType value) {
this.type = value;
return this;
} }
} }

View File

@@ -3,52 +3,104 @@
package com.azure.data.cosmos.serialization.hybridrow.schemas; package com.azure.data.cosmos.serialization.hybridrow.schemas;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.util.PrimitiveIterator;
/** /**
* The base class for property types both primitive and complex. * The base class for property types both primitive and complex.
*/ */
@JsonTypeInfo(use = Id.NAME, property = "type")
@JsonSubTypes({
// Composite types
@Type(value = ArrayPropertyType.class, name = "array"),
@Type(value = MapPropertyType.class, name = "map"),
@Type(value = ObjectPropertyType.class, name="object"),
@Type(value = ScopePropertyType.class, name="scope"),
@Type(value = SetPropertyType.class, name="set"),
@Type(value = TaggedPropertyType.class, name="tagged"),
@Type(value = TuplePropertyType.class, name="tuple"),
// Primitive types
@Type(value = PrimitivePropertyType.class, name="int32"),
@Type(value = PrimitivePropertyType.class, name="uint32"),
@Type(value = PrimitivePropertyType.class, name="utf8")
})
public abstract class PropertyType { public abstract class PropertyType {
@JsonProperty(required = true)
private TypeKind type;
@JsonProperty
private String apiType; private String apiType;
@JsonProperty(defaultValue = "true")
private boolean nullable; private boolean nullable;
private TypeKind type = TypeKind.values()[0];
protected PropertyType() { protected PropertyType() {
this.nullable(true); this.nullable(true);
} }
/** /**
* Api-specific type annotations for the property. * API-specific type annotations for this {@linkplain Property property}.
*
* @return API-specific type annotations for this {@linkplain Property property}.
*/ */
public final String apiType() { public final String apiType() {
return this.apiType; return this.apiType;
} }
/**
* Sets API-specific type annotations for this {@linkplain Property property}.
*
* @param value API-specific type annotations for this {@linkplain Property property}.
* @return a reference to this {@linkplain Property property}.
*/
public final PropertyType apiType(String value) { public final PropertyType apiType(String value) {
this.apiType = value; this.apiType = value;
return this; return this;
} }
/** /**
* {@code true} if the property can be {@code null} * {@code true} if the {@linkplain Property property} can be {@code null}.
* <p> * <p>
* Default: {@code true} * Default: {@code true}
*
* @return {@code true} if the {@linkplain Property property} can be {@code null, otherwise {@code false}}.
*/ */
public final boolean nullable() { public final boolean nullable() {
return this.nullable; return this.nullable;
} }
/**
* Sets a flag indicating whether the {@linkplain Property property} can be {@code null}.
*
* @param value {@code true} indicates that this {@linkplain Property property} can be {@code null}.
* @return a reference to this {@linkplain Property property}.
*/
public final PropertyType nullable(boolean value) { public final PropertyType nullable(boolean value) {
this.nullable = value; this.nullable = value;
return this; return this;
} }
/** /**
* The logical type of the property * The logical type of this {@linkplain Property property}.
*
* @return the logical type of this {@linkplain Property property}.
*/ */
public final TypeKind type() { public final TypeKind type() {
return this.type; return this.type;
} }
/**
* Sets the logical type of this {@linkplain Property property}.
*
* @param value the logical type of this {@linkplain Property property}.
* @return a reference to this {@linkplain Property property}.
*/
public final PropertyType type(TypeKind value) { public final PropertyType type(TypeKind value) {
this.type = value; this.type = value;
return this; return this;

View File

@@ -7,6 +7,7 @@ import com.azure.data.cosmos.core.Json;
import com.azure.data.cosmos.serialization.hybridrow.SchemaId; import com.azure.data.cosmos.serialization.hybridrow.SchemaId;
import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout; import com.azure.data.cosmos.serialization.hybridrow.layouts.Layout;
import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCompiler; import com.azure.data.cosmos.serialization.hybridrow.layouts.LayoutCompiler;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -27,23 +28,42 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public class Schema { public class Schema {
private String comment; // Required fields
@JsonProperty(required = true)
private SchemaId id;
@JsonProperty(required = true)
private String name; private String name;
@JsonProperty(defaultValue = "schema", required = true)
private TypeKind type;
// Optional fields
@JsonProperty
private String comment;
@JsonProperty
private SchemaOptions options; private SchemaOptions options;
@JsonProperty
private List<Property> properties;
@JsonProperty
private SchemaLanguageVersion version;
// TODO: DANOBLE: how do these properties serialize?
private List<PartitionKey> partitionKeys; private List<PartitionKey> partitionKeys;
private List<PrimarySortKey> primaryKeys; private List<PrimarySortKey> primaryKeys;
private List<Property> properties;
private SchemaId schemaId = SchemaId.NONE;
private List<StaticKey> staticKeys; private List<StaticKey> staticKeys;
private TypeKind type = TypeKind.values()[0];
private SchemaLanguageVersion version = SchemaLanguageVersion.values()[0];
/** /**
* Initializes a new instance of the {@link Schema} class. * Initializes a new instance of the {@link Schema} class.
*/ */
public Schema() { private Schema() {
this.type(TypeKind.SCHEMA); this.type = TypeKind.SCHEMA;
this.properties = Collections.emptyList();
this.partitionKeys = Collections.emptyList(); this.partitionKeys = Collections.emptyList();
this.primaryKeys = Collections.emptyList(); this.primaryKeys = Collections.emptyList();
this.staticKeys = Collections.emptyList(); this.staticKeys = Collections.emptyList();
@@ -51,12 +71,24 @@ public class Schema {
/** /**
* An (optional) comment describing the purpose of this schema. * An (optional) comment describing the purpose of this schema.
* <p>
* Comments are for documentary purpose only and do not affect the schema at runtime. * Comments are for documentary purpose only and do not affect the schema at runtime.
*
* @return the comment on this {@linkplain Schema schema} or {@code null}, if there is no comment.
*/ */
public final String comment() { public final String comment() {
return this.comment; return this.comment;
} }
/**
* Sets the (optional) comment describing the purpose of this schema.
* <p>
* Comments are for documentary purpose only and do not affect the schema at runtime.
*
* @param value a comment on this {@linkplain Schema schema} or {@code null} to remove the comment, if any, on this
* {@linkplain Schema schema}.
* @return a reference to this {@linkplain Schema schema}.
*/
public final Schema comment(String value) { public final Schema comment(String value) {
this.comment = value; this.comment = value;
return this; return this;
@@ -65,30 +97,41 @@ public class Schema {
/** /**
* Compiles this logical schema into a physical layout that can be used to read and write rows. * Compiles this logical schema into a physical layout that can be used to read and write rows.
* *
* @param ns The namespace within which this schema is defined. * @param namespace The namespace within which this schema is defined.
* @return The layout for the schema. * @return The layout for the schema.
*/ */
public final Layout compile(Namespace ns) { public final Layout compile(Namespace namespace) {
checkNotNull(ns, "expected non-null ns"); checkNotNull(namespace, "expected non-null ns");
checkArgument(ns.schemas().contains(this)); checkArgument(namespace.schemas().contains(this));
return LayoutCompiler.compile(ns, this); return LayoutCompiler.compile(namespace, this);
} }
/** /**
* The name of the schema. * The name of this {@linkplain Schema schema}.
* <p> * <p>
* The name of a schema MUST be unique within its namespace. * The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and
* <para /> * can only contain alpha-numeric characters and underscores.
* Names must begin with an alpha-numeric character and can only contain alpha-numeric characters and *
* underscores. * @return the name of this {@linkplain Schema schema} or {@code null}, if the name has not yet been set.
*/ */
public final String name() { public final String name() {
return this.name; return this.name;
} }
public final Schema name(String value) { /**
* Sets the name of this {@linkplain Schema schema}.
* <p>
* The name of a schema MUST be unique within its namespace. Names must begin with an alpha-numeric character and
* can only contain alpha-numeric characters and underscores.
*
* @param value a name for this {@linkplain Schema schema}.
* @return a reference to this {@linkplain Schema schema}.
*/
@Nonnull
public final Schema name(@Nonnull String value) {
checkNotNull(value);
this.name = value; this.name = value;
return this; return this;
} }
@@ -112,8 +155,8 @@ public class Schema {
* @return A logical schema, if the value parses. * @return A logical schema, if the value parses.
*/ */
public static Optional<Schema> parse(String value) { public static Optional<Schema> parse(String value) {
return Json.<Schema>parse(value); // TODO: DANOBLE: perform structural validation on the Schema after JSON return Json.parse(value, Schema.class);
// parsing // TODO: DANOBLE: perform structural validation on the Schema after JSON parsing
} }
/** /**
@@ -173,11 +216,11 @@ public class Schema {
* Identifiers must be unique within the scope of the database in which they are used. * Identifiers must be unique within the scope of the database in which they are used.
*/ */
public final SchemaId schemaId() { public final SchemaId schemaId() {
return this.schemaId; return this.id;
} }
public final Schema schemaId(SchemaId value) { public final Schema schemaId(SchemaId value) {
this.schemaId = value; this.id = value;
return this; return this;
} }

View File

@@ -73,7 +73,7 @@ public final class SchemaHash {
HashCode128 hash = seed; HashCode128 hash = seed;
hash = Murmur3Hash.Hash128(p.path(), hash); hash = Murmur3Hash.Hash128(p.path(), hash);
hash = SchemaHash.computeHash(ns, p.propertyType(), hash); hash = SchemaHash.computeHash(ns, p.type(), hash);
return hash; return hash;
} }

View File

@@ -12,26 +12,49 @@ public enum SchemaLanguageVersion {
/** /**
* Initial version of the HybridRow Schema Description Lanauge. * Initial version of the HybridRow Schema Description Lanauge.
*/ */
V1((byte)0); V1((byte) 0, "v1");
public static final int BYTES = Byte.BYTES; public static final int BYTES = Byte.BYTES;
private static HashMap<Byte, SchemaLanguageVersion> mappings; private static HashMap<Byte, SchemaLanguageVersion> mappings;
private String friendlyName;
private byte value; private byte value;
SchemaLanguageVersion(byte value) { SchemaLanguageVersion(byte value, String text) {
this.value = value; this.value = value;
this.friendlyName = text;
mappings().put(value, this); mappings().put(value, this);
} }
public byte getValue() { /**
return this.value; * Returns the friendly name of this enum constant.
*
* @return the friendly name of this enum constant.
* @see #toString()
*/
public String friendlyName() {
return this.friendlyName;
} }
public static SchemaLanguageVersion forValue(byte value) { public static SchemaLanguageVersion from(byte value) {
return mappings().get(value); return mappings().get(value);
} }
/**
* Returns the friendly name of this enum constant.
*
* @return the friendly name of this enum constant.
* @see #friendlyName()
*/
@Override
public String toString() {
return this.friendlyName;
}
public byte value() {
return this.value;
}
private static HashMap<Byte, SchemaLanguageVersion> mappings() { private static HashMap<Byte, SchemaLanguageVersion> mappings() {
if (mappings == null) { if (mappings == null) {
synchronized (SchemaLanguageVersion.class) { synchronized (SchemaLanguageVersion.class) {
@@ -42,4 +65,6 @@ public enum SchemaLanguageVersion {
} }
return mappings; return mappings;
} }
} }

View File

@@ -110,7 +110,7 @@ public final class SchemaValidator {
Property p, Schema s, Map<SchemaIdentification, Schema> schemas, Map<SchemaId, Schema> ids) { Property p, Schema s, Map<SchemaIdentification, Schema> schemas, Map<SchemaId, Schema> ids) {
Assert.isValidIdentifier(p.path(), "Property path"); Assert.isValidIdentifier(p.path(), "Property path");
SchemaValidator.visit(p.propertyType(), null, schemas, ids); SchemaValidator.visit(p.type(), null, schemas, ids);
} }
private static void visit( private static void visit(
@@ -164,7 +164,7 @@ public final class SchemaValidator {
Map<String, Property> pathDupCheck = new HashMap<>(op.properties().size()); Map<String, Property> pathDupCheck = new HashMap<>(op.properties().size());
for (Property nested : op.properties()) { for (Property nested : op.properties()) {
Assert.duplicateCheck(nested.path(), nested, pathDupCheck, "Property path", "Object"); Assert.duplicateCheck(nested.path(), nested, pathDupCheck, "Property path", "Object");
SchemaValidator.visit(nested.propertyType(), p, schemas, ids); SchemaValidator.visit(nested.type(), p, schemas, ids);
} }
return; return;
} }

View File

@@ -19,7 +19,7 @@ public enum StorageKind {
* <p> * <p>
* This is indicative of an error in the the column specification. * This is indicative of an error in the the column specification.
*/ */
NONE(-1), NONE(-1, "none"),
/** /**
* The property defines a sparse column * The property defines a sparse column
@@ -28,14 +28,14 @@ public enum StorageKind {
* linked list at the end of the row. Access time for sparse columns is proportional to the number of sparse columns * linked list at the end of the row. Access time for sparse columns is proportional to the number of sparse columns
* in the row. * in the row.
*/ */
SPARSE(0), SPARSE(0, "sparse"),
/** /**
* The property is a fixed-length, space-reserved column * The property is a fixed-length, space-reserved column
* <p> * <p>
* The column will consume 1 null-bit, and its byte-width regardless of whether the value is present in the row. * The column will consume 1 null-bit, and its byte-width regardless of whether the value is present in the row.
*/ */
FIXED(1), FIXED(1, "fixed"),
/** /**
* The property is a variable-length column. * The property is a variable-length column.
@@ -46,7 +46,7 @@ public enum StorageKind {
* When a <em>long</em> value is marked variable then a null-bit is reserved and the value is optionally encoded as * When a <em>long</em> value is marked variable then a null-bit is reserved and the value is optionally encoded as
* variable if small enough to fit, otherwise the null-bit is set and the value is encoded as sparse. * variable if small enough to fit, otherwise the null-bit is set and the value is encoded as sparse.
*/ */
VARIABLE(2); VARIABLE(2, "variable");
public static final int BYTES = Integer.BYTES; public static final int BYTES = Integer.BYTES;
@@ -57,17 +57,28 @@ public enum StorageKind {
return new Int2ObjectArrayMap<StorageKind>(values, storageKinds); return new Int2ObjectArrayMap<StorageKind>(values, storageKinds);
}); });
private int value; private final String friendlyName;
private final int value;
StorageKind(int value) { StorageKind(int value, String friendlyName) {
this.friendlyName = friendlyName;
this.value = value; this.value = value;
} }
public int value() { public String friendlyName() {
return this.value; return this.friendlyName;
} }
public static StorageKind from(int value) { public static StorageKind from(int value) {
return mappings.get().get(value); return mappings.get().get(value);
} }
@Override
public String toString() {
return this.friendlyName;
}
public int value() {
return this.value;
}
} }

View File

@@ -5,6 +5,7 @@ package com.azure.data.cosmos.serialization.hybridrow.schemas;
// TODO: DANOBLE: Fixup JSON-serialized naming for agreement with the dotnet code // TODO: DANOBLE: Fixup JSON-serialized naming for agreement with the dotnet code
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -19,7 +20,8 @@ public enum TypeKind {
/** /**
* Reserved. * Reserved.
*/ */
INVALID(0), @JsonEnumDefaultValue
INVALID(0, "invalid"),
/** /**
* The literal null. * The literal null.
@@ -27,166 +29,166 @@ public enum TypeKind {
* When used as a fixed column, only a presence bit is allocated. When used as a sparse column, a sparse value with * When used as a fixed column, only a presence bit is allocated. When used as a sparse column, a sparse value with
* 0-length payload is written. * 0-length payload is written.
*/ */
NULL(1), NULL(1, "null"),
/** /**
* A boolean property. * A boolean property.
* <p> * <p>
* Boolean properties are allocated a single bit when schematized within a row. * Boolean properties are allocated a single bit when schematized within a row.
*/ */
BOOLEAN(2), BOOLEAN(2, "bool"),
/** /**
* 8-bit signed integer. * 8-bit signed integer.
*/ */
INT_8(3), INT_8(3, "int8"),
/** /**
* 16-bit signed integer. * 16-bit signed integer.
*/ */
INT_16(4), INT_16(4, "int16"),
/** /**
* 32-bit signed integer. * 32-bit signed integer.
*/ */
INT_32(5), INT_32(5, "int32"),
/** /**
* 64-bit signed integer. * 64-bit signed integer.
*/ */
INT_64(6), INT_64(6, "int64"),
/** /**
* 8-bit unsigned integer. * 8-bit unsigned integer.
*/ */
UINT_8(7), UINT_8(7, "uint8"),
/** /**
* 16-bit unsigned integer. * 16-bit unsigned integer.
*/ */
UINT_16(8), UINT_16(8, "uint16"),
/** /**
* 32-bit unsigned integer. * 32-bit unsigned integer.
*/ */
UINT_32(9), UINT_32(9, "uint32"),
/** /**
* 64-bit unsigned integer. * 64-bit unsigned integer.
*/ */
UINT_64(10), UINT_64(10, "uint64"),
/** /**
* Variable length encoded signed integer. * Variable length encoded signed integer.
*/ */
VAR_INT(11), VAR_INT(11, "varint"),
/** /**
* Variable length encoded unsigned integer. * Variable length encoded unsigned integer.
*/ */
VAR_UINT(12), VAR_UINT(12, "varuint"),
/** /**
* 32-bit IEEE 754 floating point value. * 32-bit IEEE 754 floating point value.
*/ */
FLOAT_32(13), FLOAT_32(13, "float32"),
/** /**
* 64-bit IEEE 754 floating point value. * 64-bit IEEE 754 floating point value.
*/ */
FLOAT_64(14), FLOAT_64(14, "float64"),
/** /**
* 128-bit IEEE 754-2008 floating point value. * 128-bit IEEE 754-2008 floating point value.
*/ */
FLOAT_128(15), FLOAT_128(15, "float128"),
/** /**
* 128-bit floating point value. * 128-bit floating point value.
* *
* @see java.math.BigDecimal * @see java.math.BigDecimal
*/ */
DECIMAL(16), DECIMAL(16, "decimal"),
/** /**
* 64-bit date/time value in 100ns increments from C# epoch. * 64-bit date/time value in 100ns increments from C# epoch.
* *
* @see java.time.OffsetDateTime * @see java.time.OffsetDateTime
*/ */
DATE_TIME(17), DATE_TIME(17, "datetime"),
/** /**
* 64-bit date/time value in milliseconds increments from Unix epoch. * 64-bit date/time value in milliseconds increments from Unix epoch.
* *
* @see com.azure.data.cosmos.serialization.hybridrow.UnixDateTime * @see com.azure.data.cosmos.serialization.hybridrow.UnixDateTime
*/ */
UNIX_DATE_TIME(18), UNIX_DATE_TIME(18, "unixdatetime"),
/** /**
* 128-bit globally unique identifier (in little-endian byte order). * 128-bit globally unique identifier (in little-endian byte order).
*/ */
GUID(19), GUID(19, "guid"),
/** /**
* 12-byte MongoDB Object Identifier (in little-endian byte order). * 12-byte MongoDB Object Identifier (in little-endian byte order).
*/ */
MONGODB_OBJECT_ID(20), MONGODB_OBJECT_ID(20, "mongodb.objectid"),
/** /**
* Zero to MAX_ROW_SIZE bytes encoded as UTF-8 code points. * Zero to MAX_ROW_SIZE bytes encoded as UTF-8 code points.
*/ */
UTF_8(21), UTF_8(21, "utf8"),
/** /**
* Zero to MAX_ROW_SIZE untyped bytes. * Zero to MAX_ROW_SIZE untyped bytes.
*/ */
BINARY(22), BINARY(22, "binary"),
/** /**
* An object property. * An object property.
*/ */
OBJECT(23), OBJECT(23, "object"),
/** /**
* An array property, either typed or untyped. * An array property, either typed or untyped.
*/ */
ARRAY(24), ARRAY(24, "array"),
/** /**
* A set property, either typed or untyped. * A set property, either typed or untyped.
*/ */
SET(25), SET(25, "set"),
/** /**
* A map property, either typed or untyped. * A map property, either typed or untyped.
*/ */
MAP(26), MAP(26, "map"),
/** /**
* A tuple property. Tuples are typed, finite, ordered, sets. * A tuple property. Tuples are typed, finite, ordered, sets.
*/ */
TUPLE(27), TUPLE(27, "tuple"),
/** /**
* A tagged property. * A tagged property.
* <p> * <p>
* Tagged properties pair one or more typed values with an API-specific uint8 type code. * Tagged properties pair one or more typed values with an API-specific uint8 type code.
*/ */
TAGGED(28), TAGGED(28, "tagged"),
/** /**
* A row with schema. * A row with schema.
* <p> * <p>
* May define either a top-level table schema or a UDT (nested row). * May define either a top-level table schema or a UDT (nested row).
*/ */
SCHEMA(29), SCHEMA(29, "schema"),
/** /**
* An untyped sparse field. * An untyped sparse field.
* <p> * <p>
* May only be used to define the type within a nested scope. * May only be used to define the type within a nested scope.
*/ */
ANY(30); ANY(30, "any");
public static final int BYTES = Integer.BYTES; public static final int BYTES = Integer.BYTES;
@@ -197,12 +199,35 @@ public enum TypeKind {
return new Int2ObjectOpenHashMap<>(values, typeKinds); return new Int2ObjectOpenHashMap<>(values, typeKinds);
}); });
private int value; private final String friendlyName;
private final int value;
TypeKind(int value) { TypeKind(final int value, final String friendlyName) {
this.friendlyName = friendlyName;
this.value = value; this.value = value;
} }
/**
* Returns the friendly name of this enum constant.
*
* @return the friendly name of this enum constant.
* @see #toString()
*/
public String friendlyName() {
return this.friendlyName;
}
/**
* Returns the friendly name of this enum constant.
*
* @return the friendly name of this enum constant.
* @see #friendlyName()
*/
@Override
public String toString() {
return this.friendlyName;
}
public int value() { public int value() {
return this.value; return this.value;
} }

View File

@@ -27,8 +27,7 @@ public class UdtPropertyType extends ScopePropertyType {
/** /**
* The identifier of the UDT schema defining the structure for the nested row. * The identifier of the UDT schema defining the structure for the nested row.
* <p> * <p>
* The UDT schema MUST be defined within the same {@link Namespace} as the schema that * The UDT schema MUST be defined within the same {@link Namespace} as the schema that references it.
* references it.
*/ */
public final String name() { public final String name() {
return this.name; return this.name;

View File

@@ -13,9 +13,9 @@ import static org.testng.Assert.*;
@Test(groups = "unit") @Test(groups = "unit")
public class SystemSchemaTest { public class SystemSchemaTest {
private static final Path SchemaFile = Paths.get("data", "CustomerSchema.json"); private static final Path SCHEMA_FILE = Paths.get("test-data", "CustomerSchema.json");
@Test(enabled = false) @Test
public void testLoadSchema() { public void testLoadSchema() {
final LayoutResolver layoutResolver = SystemSchema.layoutResolver(); final LayoutResolver layoutResolver = SystemSchema.layoutResolver();

View File

@@ -1,16 +1,6 @@
# this is the log4j configuration for tests # log4j configuration for tests
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=INFO, A1 log4j.rootLogger=INFO, A1
# Set HTTP components' logger to INFO
log4j.category.io.netty=INFO
log4j.category.io.reactivex=INFO
log4j.category.com.microsoft.azure.cosmosdb=INFO
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5X{pid} [%t] %-5p %c - %m%n log4j.appender.A1.layout.ConversionPattern=%d %5X{pid} [%t] %-5p %c - %m%n