diff --git a/lang/csharp/src/apache/main/AvroDecimal.cs b/lang/csharp/src/apache/main/AvroDecimal.cs index ed748ae35f0..563056af021 100644 --- a/lang/csharp/src/apache/main/AvroDecimal.cs +++ b/lang/csharp/src/apache/main/AvroDecimal.cs @@ -917,7 +917,7 @@ TypeCode IConvertible.GetTypeCode() /// bool IConvertible.ToBoolean(IFormatProvider provider) { - return Convert.ToBoolean(this, provider); + return (bool)((IConvertible)this).ToType(typeof(bool), provider); } /// @@ -929,7 +929,7 @@ bool IConvertible.ToBoolean(IFormatProvider provider) /// byte IConvertible.ToByte(IFormatProvider provider) { - return Convert.ToByte(this, provider); + return (byte)((IConvertible)this).ToType(typeof(byte), provider); } /// @@ -967,7 +967,7 @@ DateTime IConvertible.ToDateTime(IFormatProvider provider) /// decimal IConvertible.ToDecimal(IFormatProvider provider) { - return Convert.ToDecimal(this, provider); + return (decimal)((IConvertible)this).ToType(typeof(decimal), provider); } /// @@ -979,7 +979,7 @@ decimal IConvertible.ToDecimal(IFormatProvider provider) /// double IConvertible.ToDouble(IFormatProvider provider) { - return Convert.ToDouble(this, provider); + return (double)((IConvertible)this).ToType(typeof(double), provider); } /// @@ -991,7 +991,7 @@ double IConvertible.ToDouble(IFormatProvider provider) /// short IConvertible.ToInt16(IFormatProvider provider) { - return Convert.ToInt16(this, provider); + return (short)((IConvertible)this).ToType(typeof(short), provider); } /// @@ -1003,7 +1003,7 @@ short IConvertible.ToInt16(IFormatProvider provider) /// int IConvertible.ToInt32(IFormatProvider provider) { - return Convert.ToInt32(this, provider); + return (int)((IConvertible)this).ToType(typeof(int), provider); } /// @@ -1015,7 +1015,7 @@ int IConvertible.ToInt32(IFormatProvider provider) /// long IConvertible.ToInt64(IFormatProvider provider) { - return Convert.ToInt64(this, provider); + return (long)((IConvertible)this).ToType(typeof(long), provider); } /// @@ -1027,7 +1027,7 @@ long IConvertible.ToInt64(IFormatProvider provider) /// sbyte IConvertible.ToSByte(IFormatProvider provider) { - return Convert.ToSByte(this, provider); + return (sbyte)((IConvertible)this).ToType(typeof(sbyte), provider); } /// @@ -1039,7 +1039,7 @@ sbyte IConvertible.ToSByte(IFormatProvider provider) /// float IConvertible.ToSingle(IFormatProvider provider) { - return Convert.ToSingle(this, provider); + return (float)((IConvertible)this).ToType(typeof(float), provider); } /// @@ -1051,7 +1051,7 @@ float IConvertible.ToSingle(IFormatProvider provider) /// string IConvertible.ToString(IFormatProvider provider) { - return Convert.ToString(this, provider); + return ToString(); } /// @@ -1063,7 +1063,7 @@ string IConvertible.ToString(IFormatProvider provider) /// ushort IConvertible.ToUInt16(IFormatProvider provider) { - return Convert.ToUInt16(this, provider); + return (ushort)((IConvertible)this).ToType(typeof(ushort), provider); } /// @@ -1075,7 +1075,7 @@ ushort IConvertible.ToUInt16(IFormatProvider provider) /// uint IConvertible.ToUInt32(IFormatProvider provider) { - return Convert.ToUInt32(this, provider); + return (uint)((IConvertible)this).ToType(typeof(uint), provider); } /// @@ -1087,7 +1087,7 @@ uint IConvertible.ToUInt32(IFormatProvider provider) /// ulong IConvertible.ToUInt64(IFormatProvider provider) { - return Convert.ToUInt64(this, provider); + return (ulong)((IConvertible)this).ToType(typeof(ulong), provider); } /// diff --git a/lang/csharp/src/apache/test/AvroDecimalTest.cs b/lang/csharp/src/apache/test/AvroDecimalTest.cs index c6d0d3a807e..75b39add41f 100644 --- a/lang/csharp/src/apache/test/AvroDecimalTest.cs +++ b/lang/csharp/src/apache/test/AvroDecimalTest.cs @@ -16,6 +16,7 @@ * limitations under the License. */ +using System; using System.Globalization; using NUnit.Framework; @@ -91,5 +92,28 @@ public int TestAvroDecimalCompareTo(string left, string right) return leftAvroDecimal.CompareTo(rightAvroDecimal); } + + // AVRO-3569: the IConvertible conversions previously called + // Convert.ToXxx(this, provider), which recurses back into the same + // IConvertible method and overflows the stack. Verify they now return + // the converted value instead of crashing. + [Test] + public void TestAvroDecimalIConvertibleDoesNotRecurse() + { + var d = new AvroDecimal(42); + Assert.AreEqual((byte)42, Convert.ToByte(d)); + Assert.AreEqual((sbyte)42, Convert.ToSByte(d)); + Assert.AreEqual((short)42, Convert.ToInt16(d)); + Assert.AreEqual(42, Convert.ToInt32(d)); + Assert.AreEqual(42L, Convert.ToInt64(d)); + Assert.AreEqual((ushort)42, Convert.ToUInt16(d)); + Assert.AreEqual(42u, Convert.ToUInt32(d)); + Assert.AreEqual(42ul, Convert.ToUInt64(d)); + Assert.AreEqual(42d, Convert.ToDouble(d)); + Assert.AreEqual(42f, Convert.ToSingle(d)); + Assert.AreEqual(42m, Convert.ToDecimal(d)); + Assert.AreEqual(true, Convert.ToBoolean(d)); + Assert.AreEqual("42", Convert.ToString(d, CultureInfo.InvariantCulture)); + } } } diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestResolver.java b/lang/java/avro/src/test/java/org/apache/avro/TestResolver.java index 1d3919319af..546a724a956 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/TestResolver.java +++ b/lang/java/avro/src/test/java/org/apache/avro/TestResolver.java @@ -22,8 +22,13 @@ import org.apache.avro.data.TimeConversions; import org.apache.avro.generic.IndexedRecord; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumReader; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.DatumReader; import org.apache.avro.io.DecoderFactory; +import org.apache.avro.io.EncoderFactory; import org.apache.avro.io.FastReaderBuilder; import org.apache.avro.io.JsonDecoder; import org.hamcrest.MatcherAssert; @@ -103,6 +108,44 @@ void resolveEnum() throws IOException { Assertions.assertEquals("e3", result.get(0).toString()); } + // AVRO-3235: enums (and their enclosing records) that share the same simple + // name but live in different namespaces must still resolve, so that data + // versioned by namespace stays backward/forward compatible. + @Test + void resolveEnumAcrossNamespaces() throws IOException { + final Schema writeEnum = Schema.createEnum("Status", "", "test.v1", Arrays.asList("ON", "OFF")); + final Schema readEnum = Schema.createEnum("Status", "", "test.v2", Arrays.asList("ON", "OFF")); + + // The enum action must be an EnumAdjust, not a NAMES_DONT_MATCH error. + Resolver.Action enumAction = Resolver.resolve(writeEnum, readEnum); + MatcherAssert.assertThat(enumAction, Matchers.instanceOf(Resolver.EnumAdjust.class)); + + // Full record round-trip: writer record in v1, reader record in v2 with an + // extra defaulted field; the enum resolves and the default is inserted. + Schema writeRecord = Schema.createRecord("Simple", "", "test.v1", false, + Arrays.asList(new Schema.Field("name", Schema.create(Schema.Type.STRING), ""), + new Schema.Field("status", writeEnum, "", "ON"))); + Schema readRecord = Schema.createRecord("Simple", "", "test.v2", false, + Arrays.asList(new Schema.Field("name", Schema.create(Schema.Type.STRING), ""), + new Schema.Field("description", Schema.create(Schema.Type.STRING), "", ""), + new Schema.Field("status", readEnum, "", "ON"))); + + GenericData.Record rec = new GenericData.Record(writeRecord); + rec.put("name", "A"); + rec.put("status", new GenericData.EnumSymbol(writeEnum, "ON")); + java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); + org.apache.avro.io.Encoder enc = EncoderFactory.get().binaryEncoder(bos, null); + new GenericDatumWriter(writeRecord).write(rec, enc); + enc.flush(); + + GenericDatumReader datumReader = new GenericDatumReader<>(writeRecord, readRecord); + GenericRecord out = datumReader.read(null, + DecoderFactory.get().binaryDecoder(bos.toByteArray(), null)); + Assertions.assertEquals("A", out.get("name").toString()); + Assertions.assertEquals("", out.get("description").toString()); + Assertions.assertEquals("ON", out.get("status").toString()); + } + @Test void promoteIsValid() { Assertions.assertThrows(IllegalArgumentException.class,