You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by bl...@apache.org on 2020/11/15 00:28:54 UTC

[avro] branch master updated: AVRO-2750: Add support for enum defaults in c#

This is an automated email from the ASF dual-hosted git repository.

blachniet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new e42b44e  AVRO-2750: Add support for enum defaults in c#
e42b44e is described below

commit e42b44e0bfeaaf1fcfd28bb7396b7f663ec1aa50
Author: Matt Kellogg <ma...@gmail.com>
AuthorDate: Tue Feb 18 15:03:14 2020 -0700

    AVRO-2750: Add support for enum defaults in c#
---
 lang/csharp/src/apache/main/CodeGen/CodeGen.cs     |  4 ++
 lang/csharp/src/apache/main/Generic/GenericEnum.cs | 17 ++++++-
 .../apache/main/Generic/PreresolvingDatumReader.cs |  9 +++-
 lang/csharp/src/apache/main/Schema/EnumSchema.cs   | 25 +++++++++--
 lang/csharp/src/apache/test/CodGen/CodeGenTest.cs  |  3 +-
 .../csharp/src/apache/test/Generic/GenericTests.cs |  9 ++++
 lang/csharp/src/apache/test/Schema/SchemaTests.cs  | 14 ++++++
 .../src/apache/test/Specific/SpecificTests.cs      | 52 ++++++++++++++++++----
 8 files changed, 117 insertions(+), 16 deletions(-)

diff --git a/lang/csharp/src/apache/main/CodeGen/CodeGen.cs b/lang/csharp/src/apache/main/CodeGen/CodeGen.cs
index 09abcab..70ab5bd 100644
--- a/lang/csharp/src/apache/main/CodeGen/CodeGen.cs
+++ b/lang/csharp/src/apache/main/CodeGen/CodeGen.cs
@@ -607,6 +607,10 @@ namespace Avro
                 string privFieldName = string.Concat("_", field.Name);
                 var codeField = new CodeMemberField(ctrfield, privFieldName);
                 codeField.Attributes = MemberAttributes.Private;
+                if (field.Schema is EnumSchema es && es.Default != null)
+                {
+                    codeField.InitExpression = new CodeTypeReferenceExpression($"{es.Name}.{es.Default}");
+                }
 
                 // Process field documentation if it exist and add to the field
                 CodeCommentStatement propertyComment = null;
diff --git a/lang/csharp/src/apache/main/Generic/GenericEnum.cs b/lang/csharp/src/apache/main/Generic/GenericEnum.cs
index 00357f2..168b555 100644
--- a/lang/csharp/src/apache/main/Generic/GenericEnum.cs
+++ b/lang/csharp/src/apache/main/Generic/GenericEnum.cs
@@ -37,8 +37,21 @@ namespace Avro.Generic
             get { return value; }
             set
             {
-                if (! Schema.Contains(value)) throw new AvroException("Unknown value for enum: " + value + "(" + Schema + ")");
-                this.value = value;
+                if (!Schema.Contains(value))
+                {
+                    if (!string.IsNullOrEmpty(Schema.Default))
+                    {
+                        this.value = Schema.Default;
+                    }
+                    else
+                    {
+                        throw new AvroException("Unknown value for enum: " + value + "(" + Schema + ")");
+                    }
+                }
+                else
+                {
+                    this.value = value;
+                }
             }
         }
 
diff --git a/lang/csharp/src/apache/main/Generic/PreresolvingDatumReader.cs b/lang/csharp/src/apache/main/Generic/PreresolvingDatumReader.cs
index c55957a..a4b4aa8 100644
--- a/lang/csharp/src/apache/main/Generic/PreresolvingDatumReader.cs
+++ b/lang/csharp/src/apache/main/Generic/PreresolvingDatumReader.cs
@@ -195,8 +195,10 @@ namespace Avro.Generic
 
             var translator = new int[writerSchema.Symbols.Count];
 
+            var readerDefaultOrdinal = null != readerSchema.Default ? readerSchema.Ordinal(readerSchema.Default) : -1;
+
             foreach (var symbol in writerSchema.Symbols)
-            {
+            { 
                 var writerOrdinal = writerSchema.Ordinal(symbol);
                 if (readerSchema.Contains(symbol))
                 {
@@ -212,6 +214,11 @@ namespace Avro.Generic
                         {
                             var writerOrdinal = d.ReadEnum();
                             var readerOrdinal = translator[writerOrdinal];
+                            if (readerOrdinal == -1 && readerDefaultOrdinal != -1) //the symbol doesn't exist, but the default does
+                            {
+                                return enumAccess.CreateEnum(r, readerDefaultOrdinal);
+                            }
+
                             if (readerOrdinal == -1)
                             {
                                 throw new AvroException("No such symbol: " + writerSchema[writerOrdinal]);
diff --git a/lang/csharp/src/apache/main/Schema/EnumSchema.cs b/lang/csharp/src/apache/main/Schema/EnumSchema.cs
index 5a14afd..3fd1450 100644
--- a/lang/csharp/src/apache/main/Schema/EnumSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/EnumSchema.cs
@@ -33,6 +33,11 @@ namespace Avro
         public IList<string> Symbols { get; private set;  }
 
         /// <summary>
+        /// The default token to use when deserializing an enum when the provided token is not found
+        /// </summary>
+        public string Default { get; private set; }
+
+        /// <summary>
         /// Map of enum symbols and it's corresponding ordinal number
         /// </summary>
         private readonly IDictionary<string, int> symbolMap;
@@ -74,7 +79,7 @@ namespace Avro
             try
             {
                 return new EnumSchema(name, aliases, symbols, symbolMap, props, names,
-                    JsonHelper.GetOptionalString(jtok, "doc"));
+                    JsonHelper.GetOptionalString(jtok, "doc"), JsonHelper.GetOptionalString(jtok, "default"));
             }
             catch (SchemaParseException e)
             {
@@ -92,14 +97,19 @@ namespace Avro
         /// <param name="props">custom properties on this schema</param>
         /// <param name="names">list of named schema already read</param>
         /// <param name="doc">documentation for this named schema</param>
+        /// <param name="defaultSymbol">default symbol</param>
         private EnumSchema(SchemaName name, IList<SchemaName> aliases, List<string> symbols,
                             IDictionary<String, int> symbolMap, PropertyMap props, SchemaNames names,
-                            string doc)
+                            string doc, string defaultSymbol)
                             : base(Type.Enumeration, name, aliases, props, names, doc)
         {
             if (null == name.Name) throw new SchemaParseException("name cannot be null for enum schema.");
             this.Symbols = symbols;
             this.symbolMap = symbolMap;
+
+            if (null != defaultSymbol && !symbolMap.ContainsKey(defaultSymbol))
+                throw new SchemaParseException($"Default symbol: {defaultSymbol} not found in symbols");
+            Default = defaultSymbol;
         }
 
         /// <summary>
@@ -117,6 +127,11 @@ namespace Avro
             foreach (string s in this.Symbols)
                 writer.WriteValue(s);
             writer.WriteEndArray();
+            if (null != Default) 
+            {
+                writer.WritePropertyName("default");
+                writer.WriteValue(Default);
+            }
         }
 
         /// <summary>
@@ -128,7 +143,11 @@ namespace Avro
         public int Ordinal(string symbol)
         {
             int result;
-            if (symbolMap.TryGetValue(symbol, out result)) return result;
+            if (symbolMap.TryGetValue(symbol, out result))
+                return result;
+            if (null != Default)
+                return symbolMap[Default];
+
             throw new AvroException("No such symbol: " + symbol);
         }
 
diff --git a/lang/csharp/src/apache/test/CodGen/CodeGenTest.cs b/lang/csharp/src/apache/test/CodGen/CodeGenTest.cs
index 34358d6..c288989 100644
--- a/lang/csharp/src/apache/test/CodGen/CodeGenTest.cs
+++ b/lang/csharp/src/apache/test/CodGen/CodeGenTest.cs
@@ -18,7 +18,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.CodeDom.Compiler;
 using Microsoft.CSharp;
 using NUnit.Framework;
@@ -45,7 +44,7 @@ namespace Avro.Test
 			{ ""name"" : ""internal"", ""type"" : ""bytes"" },
 			{ ""name"" : ""while"", ""type"" : ""string"" },
 			{ ""name"" : ""return"", ""type"" : ""null"" },
-			{ ""name"" : ""enum"", ""type"" : { ""type"" : ""enum"", ""name"" : ""class"", ""symbols"" : [ ""A"", ""B"" ] } },
+			{ ""name"" : ""enum"", ""type"" : { ""type"" : ""enum"", ""name"" : ""class"", ""symbols"" : [ ""Unknown"", ""A"", ""B"" ], ""default"" : ""Unknown"" } },
 			{ ""name"" : ""string"", ""type"" : { ""type"": ""fixed"", ""size"": 16, ""name"": ""static"" } }
 		]
 }
diff --git a/lang/csharp/src/apache/test/Generic/GenericTests.cs b/lang/csharp/src/apache/test/Generic/GenericTests.cs
index f9817f9..05aa5bc 100644
--- a/lang/csharp/src/apache/test/Generic/GenericTests.cs
+++ b/lang/csharp/src/apache/test/Generic/GenericTests.cs
@@ -108,6 +108,15 @@ namespace Avro.Test.Generic
             test(schema, mkMap(values));
         }
 
+        [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\": [\"Unknown\", \"A\", \"B\"], \"default\": \"Unknown\" }", "C", "Unknown")]
+        [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\": [\"Unknown\", \"A\", \"B\"], \"default\": \"Unknown\" }", "A", "A")]
+        public void TestEnumDefault(string schema, string attemptedValue, string expectedValue)
+        {
+            var newEnum = mkEnum(schema, attemptedValue) as GenericEnum;
+            Assert.NotNull(newEnum);
+            Assert.AreEqual(newEnum.Value, expectedValue);
+        }
+
         [TestCase()]
         public void TestLogical_Date()
         {
diff --git a/lang/csharp/src/apache/test/Schema/SchemaTests.cs b/lang/csharp/src/apache/test/Schema/SchemaTests.cs
index 50d54b7..1d67742 100644
--- a/lang/csharp/src/apache/test/Schema/SchemaTests.cs
+++ b/lang/csharp/src/apache/test/Schema/SchemaTests.cs
@@ -249,6 +249,20 @@ namespace Avro.Test
             Assert.AreEqual(expectedDoc, es.Documentation);
         }
 
+        [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\": [\"Unknown\", \"A\", \"B\"], \"default\": \"Unknown\" }", "Unknown")]
+        public void TestEnumDefault(string s, string expectedToken) 
+        {
+            var es = Schema.Parse(s) as EnumSchema;
+            Assert.IsNotNull(es);
+            Assert.AreEqual(es.Default, expectedToken);
+        }
+
+        [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\": [\"Unknown\", \"A\", \"B\"], \"default\": \"Something\" }")]
+        public void TestEnumDefaultSymbolDoesntExist(string s)
+        {
+            Assert.Throws<SchemaParseException>(() => Schema.Parse(s));
+        }
+
         [TestCase("{\"type\": \"array\", \"items\": \"long\"}", "long")]
         public void TestArray(string s, string item)
         {
diff --git a/lang/csharp/src/apache/test/Specific/SpecificTests.cs b/lang/csharp/src/apache/test/Specific/SpecificTests.cs
index 2602f6c..9756b2d 100644
--- a/lang/csharp/src/apache/test/Specific/SpecificTests.cs
+++ b/lang/csharp/src/apache/test/Specific/SpecificTests.cs
@@ -18,7 +18,6 @@
 using System;
 using System.Collections;
 using System.IO;
-using System.Linq;
 using NUnit.Framework;
 using Avro.IO;
 using System.CodeDom;
@@ -249,6 +248,24 @@ namespace Avro.Test
         }
 
         [Test]
+        public void TestEnumDefault()
+        {
+            //writerSchema has "SECOND"
+            Schema writerSchema = Schema.Parse("{ \"type\": \"record\", \"name\": \"EnumRecord\", \"fields\": [ { \"name\": \"enumType\", \"type\": { \"type\": \"enum\", \"name\": \"EnumType\", \"symbols\": [ \"DEFAULT\", \"FIRST\", \"SECOND\", \"THIRD\" ], \"default\": \"DEFAULT\" } } ] }");
+            Schema readerSchema = Schema.Parse("{ \"type\": \"record\", \"name\": \"EnumRecord\", \"fields\": [ { \"name\": \"enumType\", \"type\": { \"type\": \"enum\", \"name\": \"EnumType\", \"symbols\": [ \"DEFAULT\", \"FIRST\", \"THIRD\" ], \"default\": \"DEFAULT\" } } ] }");
+
+            //readerSchema is missing "SECOND" so should therefore be "DEFAULT"
+            var testRecord = new EnumRecord {enumType = EnumType.SECOND};
+
+            // serialize
+            var stream = serialize(writerSchema, testRecord);
+
+            // deserialize
+            var rec2 = deserialize<EnumRecord>(stream, writerSchema, readerSchema);
+            Assert.AreEqual(EnumType.DEFAULT, rec2.enumType);
+        }
+
+        [Test]
         public void TestEmbeddedGenerics()
         {
             var srcRecord = new EmbeddedGenericsRecord
@@ -528,11 +545,12 @@ namespace Avro.Test
         }
     }
 
-    enum EnumType
+    public enum EnumType
     {
-        THIRD,
+        DEFAULT, //putting the default first here so there isn't an ordinal collision for testing defaults
         FIRST,
-        SECOND
+        SECOND,
+        THIRD,
     }
 
     class EnumRecord : ISpecificRecord
@@ -541,10 +559,28 @@ namespace Avro.Test
         public Schema Schema
         {
             get
-            {
-                return Schema.Parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"Avro.Test\"," +
-                                        "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\":" +
-                                        " \"EnumType\", \"symbols\": [\"THIRD\", \"FIRST\", \"SECOND\"]} }]}");
+            { 
+                return Schema.Parse(@"{
+   ""type"":""record"",
+   ""name"":""EnumRecord"",
+   ""namespace"":""Avro.Test"",
+   ""fields"":[
+      {
+         ""name"":""enumType"",
+         ""type"":{
+            ""type"":""enum"",
+            ""name"":""EnumType"",
+            ""symbols"":[
+               ""DEFAULT"",
+               ""FIRST"",
+               ""SECOND"",
+               ""THIRD""
+            ]
+         },
+         ""default"": ""DEFAULT""
+      }
+   ]
+}");
             }
         }