Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,19 @@ public class CopycatSchema implements Schema {
private final Integer version;
// Optional human readable documentation describing this schema.
private final String doc;
private final Map<String, String> parameters;

/**
* Construct a Schema. Most users should not construct schemas manually, preferring {@link SchemaBuilder} instead.
*/
public CopycatSchema(Type type, boolean optional, Object defaultValue, String name, Integer version, String doc, List<Field> fields, Schema keySchema, Schema valueSchema) {
public CopycatSchema(Type type, boolean optional, Object defaultValue, String name, Integer version, String doc, Map<String, String> parameters, List<Field> fields, Schema keySchema, Schema valueSchema) {
this.type = type;
this.optional = optional;
this.defaultValue = defaultValue;
this.name = name;
this.version = version;
this.doc = doc;
this.parameters = parameters;

this.fields = fields;
if (this.fields != null && this.type == Type.STRUCT) {
Expand All @@ -100,6 +102,21 @@ public CopycatSchema(Type type, boolean optional, Object defaultValue, String na
this.valueSchema = valueSchema;
}

/**
* Construct a Schema for a primitive type, setting schema parameters, struct fields, and key and value schemas to null.
*/
public CopycatSchema(Type type, boolean optional, Object defaultValue, String name, Integer version, String doc) {
this(type, optional, defaultValue, name, version, doc, null, null, null, null);
}

/**
* Construct a default schema for a primitive type. The schema is required, has no default value, name, version,
* or documentation.
*/
public CopycatSchema(Type type) {
this(type, false, null, null, null, null);
}

@Override
public Type type() {
return type;
Expand Down Expand Up @@ -130,7 +147,10 @@ public String doc() {
return doc;
}


@Override
public Map<String, String> parameters() {
return parameters;
}

@Override
public List<Field> fields() {
Expand Down Expand Up @@ -163,7 +183,7 @@ public Schema valueSchema() {

/**
* Validate that the value can be used with the schema, i.e. that its type matches the schema type and nullability
* requirements. Throws a DataException if the value is invalid. Returns
* requirements. Throws a DataException if the value is invalid.
* @param schema Schema to test
* @param value value to test
*/
Expand Down Expand Up @@ -239,12 +259,13 @@ public boolean equals(Object o) {
Objects.equals(valueSchema, schema.valueSchema) &&
Objects.equals(name, schema.name) &&
Objects.equals(version, schema.version) &&
Objects.equals(doc, schema.doc);
Objects.equals(doc, schema.doc) &&
Objects.equals(parameters, schema.parameters);
}

@Override
public int hashCode() {
return Objects.hash(type, optional, defaultValue, fields, keySchema, valueSchema, name, version, doc);
return Objects.hash(type, optional, defaultValue, fields, keySchema, valueSchema, name, version, doc, parameters);
}

@Override
Expand Down
76 changes: 76 additions & 0 deletions copycat/api/src/main/java/org/apache/kafka/copycat/data/Date.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

package org.apache.kafka.copycat.data;

import org.apache.kafka.copycat.errors.DataException;

import java.util.Calendar;
import java.util.TimeZone;

/**
* <p>
* A date representing a calendar day with no time of day or timezone. The corresponding Java type is a java.util.Date
* with hours, minutes, seconds, milliseconds set to 0. The underlying representation is an integer representing the
* number of standardized days (based on a number of milliseconds with 24 hours/day, 60 minutes/hour, 60 seconds/minute,
* 1000 milliseconds/second with n) since Unix epoch.
* </p>
*/
public class Date {
public static final String LOGICAL_NAME = "org.apache.kafka.copycat.data.Date";

private static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

/**
* Returns a SchemaBuilder for a Date. By returning a SchemaBuilder you can override additional schema settings such
* as required/optional, default value, and documentation.
* @return a SchemaBuilder
*/
public static SchemaBuilder builder() {
return SchemaBuilder.int32()
.name(LOGICAL_NAME)
.version(1);
}

public static final Schema SCHEMA = builder().schema();

/**
* Convert a value from its logical format (Date) to it's encoded format.
* @param value the logical value
* @return the encoded value
*/
public static int fromLogical(Schema schema, java.util.Date value) {
if (schema.name() == null || !(schema.name().equals(LOGICAL_NAME)))
throw new DataException("Requested conversion of Date object but the schema does not match.");
Calendar calendar = Calendar.getInstance(UTC);
calendar.setTime(value);
if (calendar.get(Calendar.HOUR_OF_DAY) != 0 || calendar.get(Calendar.MINUTE) != 0 ||
calendar.get(Calendar.SECOND) != 0 || calendar.get(Calendar.MILLISECOND) != 0) {
throw new DataException("Copycat Date type should not have any time fields set to non-zero values.");
}
long unixMillis = calendar.getTimeInMillis();
return (int) (unixMillis / MILLIS_PER_DAY);
}

public static java.util.Date toLogical(Schema schema, int value) {
if (schema.name() == null || !(schema.name().equals(LOGICAL_NAME)))
throw new DataException("Requested conversion of Date object but the schema does not match.");
return new java.util.Date(value * MILLIS_PER_DAY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

package org.apache.kafka.copycat.data;

import org.apache.kafka.copycat.errors.DataException;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
* <p>
* An arbitrary-precision signed decimal number. The value is unscaled * 10 ^ -scale where:
* <ul>
* <li>unscaled is an integer </li>
* <li>scale is an integer representing how many digits the decimal point should be shifted on the unscaled value</li>
* </ul>
* </p>
* <p>
* Decimal does not provide a fixed schema because it is parameterized by the scale, which is fixed on the schema
* rather than being part of the value.
* </p>
* <p>
* The underlying representation of this type is bytes containing a two's complement integer
* </p>
*/
public class Decimal {
public static final String LOGICAL_NAME = "org.apache.kafka.copycat.data.Decimal";
public static final String SCALE_FIELD = "scale";

/**
* Returns a SchemaBuilder for a Decimal with the given scale factor. By returning a SchemaBuilder you can override
* additional schema settings such as required/optional, default value, and documentation.
* @param scale the scale factor to apply to unscaled values
* @return a SchemaBuilder
*/
public static SchemaBuilder builder(int scale) {
return SchemaBuilder.bytes()
.name(LOGICAL_NAME)
.parameter(SCALE_FIELD, ((Integer) scale).toString())
.version(1);
}

public static Schema schema(int scale) {
return builder(scale).build();
}

/**
* Convert a value from its logical format (BigDecimal) to it's encoded format.
* @param value the logical value
* @return the encoded value
*/
public static byte[] fromLogical(Schema schema, BigDecimal value) {
if (value.scale() != scale(schema))
throw new DataException("BigDecimal has mismatching scale value for given Decimal schema");
return value.unscaledValue().toByteArray();
}

public static BigDecimal toLogical(Schema schema, byte[] value) {
return new BigDecimal(new BigInteger(value), scale(schema));
}

private static int scale(Schema schema) {
String scaleString = schema.parameters().get(SCALE_FIELD);
if (scaleString == null)
throw new DataException("Invalid Decimal schema: scale parameter not found.");
try {
return Integer.parseInt(scaleString);
} catch (NumberFormatException e) {
throw new DataException("Invalid scale parameter found in Decimal schema: ", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.kafka.copycat.data;

import java.util.List;
import java.util.Map;

/**
* <p>
Expand Down Expand Up @@ -123,6 +124,12 @@ public boolean isPrimitive() {
*/
String doc();

/**
* Get a map of schema parameters.
* @return Map containing parameters for this schema, or null if there are no parameters
*/
Map<String, String> parameters();

/**
* Get the key schema for this map schema. Throws a DataException if this schema is not a map.
* @return the key schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* <p>
Expand Down Expand Up @@ -71,7 +73,8 @@ public class SchemaBuilder implements Schema {
private Integer version;
// Optional human readable documentation describing this schema.
private String doc;

// Additional parameters for logical types.
private Map<String, String> parameters;

private SchemaBuilder(Type type) {
this.type = type;
Expand Down Expand Up @@ -176,6 +179,41 @@ public SchemaBuilder doc(String doc) {
return this;
}

@Override
public Map<String, String> parameters() {
return Collections.unmodifiableMap(parameters);
}

/**
* Set a schema parameter.
* @param propertyName name of the schema property to define
* @param propertyValue value of the schema property to define, as a String
* @return the SchemaBuilder
*/
public SchemaBuilder parameter(String propertyName, String propertyValue) {
// Preserve order of insertion with a LinkedHashMap. This isn't strictly necessary, but is nice if logical types
// can print their properties in a consistent order.
if (parameters == null)
parameters = new LinkedHashMap<>();
parameters.put(propertyName, propertyValue);
return this;
}

/**
* Set schema parameters. This operation is additive; it does not remove existing parameters that do not appear in
* the set of properties pass to this method.
* @param props Map of properties to set
* @return the SchemaBuilder
*/
public SchemaBuilder parameters(Map<String, String> props) {
// Avoid creating an empty set of properties so we never have an empty map
if (props.isEmpty())
return this;
if (parameters == null)
parameters = new LinkedHashMap<>();
parameters.putAll(props);
return this;
}

@Override
public Type type() {
Expand Down Expand Up @@ -347,7 +385,9 @@ public Schema valueSchema() {
* @return the {@link Schema}
*/
public Schema build() {
return new CopycatSchema(type, isOptional(), defaultValue, name, version, doc, fields == null ? null : Collections.unmodifiableList(fields), keySchema, valueSchema);
return new CopycatSchema(type, isOptional(), defaultValue, name, version, doc,
parameters == null ? null : Collections.unmodifiableMap(parameters),
fields == null ? null : Collections.unmodifiableList(fields), keySchema, valueSchema);
}

/**
Expand Down
Loading