diff --git a/doc/content/en/docs/++version++/Specification/_index.md b/doc/content/en/docs/++version++/Specification/_index.md index d9199c34029..4c474c92657 100755 --- a/doc/content/en/docs/++version++/Specification/_index.md +++ b/doc/content/en/docs/++version++/Specification/_index.md @@ -786,6 +786,8 @@ A logical type is always serialized using its underlying Avro type so that value Language implementations must ignore unknown logical types when reading, and should use the underlying Avro type. If a logical type is invalid, for example a decimal with scale greater than its precision, then implementations should ignore the logical type and use the underlying Avro type. ### Decimal + +#### Fixed precision The `decimal` logical type represents an arbitrary-precision signed decimal number of the form _unscaled × 10-scale_. A `decimal` logical type annotates Avro _bytes_ or _fixed_ types. The byte array must contain the two's-complement representation of the unscaled integer value in big-endian byte order. The scale is fixed, and is specified using an attribute. @@ -809,11 +811,11 @@ Scale must be zero or a positive integer less than or equal to the precision. For the purposes of schema resolution, two schemas that are `decimal` logical types _match_ if their scales and precisions match. -**alternative** +#### Scalable precision As it's not always possible to fix scale and precision in advance for a decimal field, `big-decimal` is another `decimal` logical type restrict to Avro _bytes_. -_Currently only available in Java and Rust_. +_Currently only available in C++, Java and Rust_. ```json { @@ -821,7 +823,7 @@ _Currently only available in Java and Rust_. "logicalType": "big-decimal" } ``` -Here, as scale property is stored in value itself it needs more bytes than preceding `decimal` type, but it allows more flexibility. +Here, bytes array contains two serialized properties. First part is an Avro byte arrays which is the two's-complement representation of the unscaled integer value in big-endian byte order. The second part is the scale property stored as an Avro integer. Scale must be zero or a positive integer less than or equal to the precision. Value itself needs more bytes than preceding `decimal` type, but it allows more flexibility. ### UUID diff --git a/lang/c++/impl/Compiler.cc b/lang/c++/impl/Compiler.cc index f3c2397da24..ad7b7f8c280 100644 --- a/lang/c++/impl/Compiler.cc +++ b/lang/c++/impl/Compiler.cc @@ -359,7 +359,11 @@ static LogicalType makeLogicalType(const Entity &e, const Object &m) { } LogicalType::Type t = LogicalType::NONE; - if (typeField == "date") + if (typeField == "big-decimal" + && !containsField(m, "precision") + && !containsField(m, "scale")) + t = LogicalType::BIG_DECIMAL; + else if (typeField == "date") t = LogicalType::DATE; else if (typeField == "time-millis") t = LogicalType::TIME_MILLIS; diff --git a/lang/c++/impl/LogicalType.cc b/lang/c++/impl/LogicalType.cc index ed6a12f0892..18da72a23a5 100644 --- a/lang/c++/impl/LogicalType.cc +++ b/lang/c++/impl/LogicalType.cc @@ -51,6 +51,9 @@ void LogicalType::setScale(int32_t scale) { void LogicalType::printJson(std::ostream &os) const { switch (type_) { case LogicalType::NONE: break; + case LogicalType::BIG_DECIMAL: + os << R"("logicalType": "big-decimal")"; + break; case LogicalType::DECIMAL: os << R"("logicalType": "decimal")"; os << ", \"precision\": " << precision_; diff --git a/lang/c++/impl/Node.cc b/lang/c++/impl/Node.cc index bde556bf314..696c01fe93b 100644 --- a/lang/c++/impl/Node.cc +++ b/lang/c++/impl/Node.cc @@ -138,6 +138,13 @@ void Node::setLogicalType(LogicalType logicalType) { // Check that the logical type is applicable to the node type. switch (logicalType.type()) { case LogicalType::NONE: break; + case LogicalType::BIG_DECIMAL: { + if (type_ != AVRO_BYTES) { + throw Exception("BIG_DECIMAL logical type can annotate " + "only BYTES type"); + } + break; + } case LogicalType::DECIMAL: { if (type_ != AVRO_BYTES && type_ != AVRO_FIXED) { throw Exception("DECIMAL logical type can annotate " diff --git a/lang/c++/include/avro/LogicalType.hh b/lang/c++/include/avro/LogicalType.hh index b2a7d0294ff..5b274bcb741 100644 --- a/lang/c++/include/avro/LogicalType.hh +++ b/lang/c++/include/avro/LogicalType.hh @@ -29,6 +29,7 @@ class AVRO_DECL LogicalType { public: enum Type { NONE, + BIG_DECIMAL, DECIMAL, DATE, TIME_MILLIS, diff --git a/lang/c++/test/SchemaTests.cc b/lang/c++/test/SchemaTests.cc index 029be79d57e..4484415cb84 100644 --- a/lang/c++/test/SchemaTests.cc +++ b/lang/c++/test/SchemaTests.cc @@ -209,6 +209,7 @@ const char *roundTripSchemas[] = { R"({"type":"fixed","name":"Test","size":1})", // Logical types + R"({"type":"bytes","logicalType":"big-decimal"})", R"({"type":"bytes","logicalType":"decimal","precision":12,"scale":6})", R"({"type":"fixed","name":"test","size":16,"logicalType":"decimal","precision":38,"scale":9})", R"({"type":"fixed","name":"test","size":129,"logicalType":"decimal","precision":310,"scale":155})", @@ -240,6 +241,7 @@ const char *roundTripSchemas[] = { const char *malformedLogicalTypes[] = { // Wrong base type. + R"({"type":"long","logicalType": "big-decimal"})", R"({"type":"long","logicalType": "decimal","precision": 10})", R"({"type":"string","logicalType":"date"})", R"({"type":"string","logicalType":"time-millis"})", @@ -258,7 +260,12 @@ const char *malformedLogicalTypes[] = { R"({"type":"fixed","logicalType":"decimal","size":4,"name":"a","precision":20})", R"({"type":"fixed","logicalType":"decimal","size":129,"name":"a","precision":311})", // Scale is larger than precision. - R"({"type":"bytes","logicalType":"decimal","precision":5,"scale":10})"}; + R"({"type":"bytes","logicalType":"decimal","precision":5,"scale":10})", + // Precision is not supported by the big-decimal logical type + // and scale is integrated in bytes. + R"({"type":"bytes","logicalType": "big-decimal","precision": 9})", + R"({"type":"bytes","logicalType": "big-decimal","scale": 2})", + R"({"type":"bytes","logicalType": "big-decimal","precision": 9,"scale": 2})"}; const char *schemasToCompact[] = { // Schema without any whitespace R"({"type":"record","name":"Test","fields":[]})", @@ -335,6 +342,10 @@ static void testCompactSchemas() { } static void testLogicalTypes() { + const char *bytesBigDecimalType = "{\n\ + \"type\": \"bytes\",\n\ + \"logicalType\": \"big-decimal\"\n\ + }"; const char *bytesDecimalType = "{\n\ \"type\": \"bytes\",\n\ \"logicalType\": \"decimal\",\n\ @@ -390,6 +401,13 @@ static void testLogicalTypes() { const char *unionType = "[\n\ {\"type\":\"string\", \"logicalType\":\"uuid\"},\"null\"\n\ ]"; + { + BOOST_TEST_CHECKPOINT(bytesBigDecimalType); + ValidSchema schema = compileJsonSchemaFromString(bytesBigDecimalType); + BOOST_CHECK(schema.root()->type() == AVRO_BYTES); + LogicalType logicalType = schema.root()->logicalType(); + BOOST_CHECK(logicalType.type() == LogicalType::BIG_DECIMAL); + } { BOOST_TEST_CHECKPOINT(bytesDecimalType); ValidSchema schema1 = compileJsonSchemaFromString(bytesDecimalType);