github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/avro_test.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package changefeedccl
    10  
    11  import (
    12  	"context"
    13  	"encoding/json"
    14  	"fmt"
    15  	"math"
    16  	"math/rand"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/apd"
    22  	"github.com/cockroachdb/cockroach/pkg/ccl/importccl"
    23  	"github.com/cockroachdb/cockroach/pkg/keys"
    24  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    28  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    29  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    30  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    31  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    32  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    33  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    34  	"github.com/cockroachdb/errors"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  func parseTableDesc(createTableStmt string) (*sqlbase.TableDescriptor, error) {
    39  	ctx := context.Background()
    40  	stmt, err := parser.ParseOne(createTableStmt)
    41  	if err != nil {
    42  		return nil, errors.Wrapf(err, `parsing %s`, createTableStmt)
    43  	}
    44  	createTable, ok := stmt.AST.(*tree.CreateTable)
    45  	if !ok {
    46  		return nil, errors.Errorf("expected *tree.CreateTable got %T", stmt)
    47  	}
    48  	st := cluster.MakeTestingClusterSettings()
    49  	const parentID = sqlbase.ID(keys.MaxReservedDescID + 1)
    50  	const tableID = sqlbase.ID(keys.MaxReservedDescID + 2)
    51  	mutDesc, err := importccl.MakeSimpleTableDescriptor(
    52  		ctx, st, createTable, parentID, tableID, importccl.NoFKs, hlc.UnixNano())
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	return mutDesc.TableDesc(), mutDesc.TableDesc().ValidateTable()
    57  }
    58  
    59  func parseValues(tableDesc *sqlbase.TableDescriptor, values string) ([]sqlbase.EncDatumRow, error) {
    60  	ctx := context.Background()
    61  	semaCtx := tree.MakeSemaContext()
    62  	evalCtx := &tree.EvalContext{}
    63  
    64  	valuesStmt, err := parser.ParseOne(values)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	selectStmt, ok := valuesStmt.AST.(*tree.Select)
    69  	if !ok {
    70  		return nil, errors.Errorf("expected *tree.Select got %T", valuesStmt)
    71  	}
    72  	valuesClause, ok := selectStmt.Select.(*tree.ValuesClause)
    73  	if !ok {
    74  		return nil, errors.Errorf("expected *tree.ValuesClause got %T", selectStmt.Select)
    75  	}
    76  
    77  	var rows []sqlbase.EncDatumRow
    78  	for _, rowTuple := range valuesClause.Rows {
    79  		var row sqlbase.EncDatumRow
    80  		for colIdx, expr := range rowTuple {
    81  			col := &tableDesc.Columns[colIdx]
    82  			typedExpr, err := sqlbase.SanitizeVarFreeExpr(
    83  				ctx, expr, col.Type, "avro", &semaCtx, false /* allowImpure */)
    84  			if err != nil {
    85  				return nil, err
    86  			}
    87  			datum, err := typedExpr.Eval(evalCtx)
    88  			if err != nil {
    89  				return nil, errors.Wrapf(err, "evaluating %s", typedExpr)
    90  			}
    91  			row = append(row, sqlbase.DatumToEncDatum(col.Type, datum))
    92  		}
    93  		rows = append(rows, row)
    94  	}
    95  	return rows, nil
    96  }
    97  
    98  func parseAvroSchema(j string) (*avroDataRecord, error) {
    99  	var s avroDataRecord
   100  	if err := json.Unmarshal([]byte(j), &s); err != nil {
   101  		return nil, err
   102  	}
   103  	// This avroDataRecord doesn't have any of the derived fields we need for
   104  	// serde. Instead of duplicating the logic, fake out a TableDescriptor, so
   105  	// we can reuse tableToAvroSchema and get them for free.
   106  	tableDesc := &sqlbase.TableDescriptor{
   107  		Name: AvroNameToSQLName(s.Name),
   108  	}
   109  	for _, f := range s.Fields {
   110  		// s.Fields[idx] has `Name` and `SchemaType` set but nothing else.
   111  		// They're needed for serialization/deserialization, so fake out a
   112  		// column descriptor so that we can reuse columnDescToAvroSchema to get
   113  		// all the various fields of avroSchemaField populated for free.
   114  		colDesc, err := avroFieldMetadataToColDesc(f.Metadata)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		tableDesc.Columns = append(tableDesc.Columns, *colDesc)
   119  	}
   120  	return tableToAvroSchema(tableDesc, avroSchemaNoSuffix)
   121  }
   122  
   123  func avroFieldMetadataToColDesc(metadata string) (*sqlbase.ColumnDescriptor, error) {
   124  	parsed, err := parser.ParseOne(`ALTER TABLE FOO ADD COLUMN ` + metadata)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	def := parsed.AST.(*tree.AlterTable).Cmds[0].(*tree.AlterTableAddColumn).ColumnDef
   129  	ctx := context.Background()
   130  	semaCtx := tree.MakeSemaContext()
   131  	col, _, _, err := sqlbase.MakeColumnDefDescs(ctx, def, &semaCtx, &tree.EvalContext{})
   132  	return col, err
   133  }
   134  
   135  // randTime generates a random time.Time whose .UnixNano result doesn't
   136  // overflow an int64.
   137  func randTime(rng *rand.Rand) time.Time {
   138  	return timeutil.Unix(0, rng.Int63())
   139  }
   140  
   141  func TestAvroSchema(t *testing.T) {
   142  	defer leaktest.AfterTest(t)()
   143  	rng, _ := randutil.NewPseudoRand()
   144  
   145  	type test struct {
   146  		name   string
   147  		schema string
   148  		values string
   149  	}
   150  	tests := []test{
   151  		{
   152  			name:   `NULLABLE`,
   153  			schema: `(a INT PRIMARY KEY, b INT NULL)`,
   154  			values: `(1, 2), (3, NULL)`,
   155  		},
   156  		{
   157  			name:   `TUPLE`,
   158  			schema: `(a INT PRIMARY KEY, b STRING)`,
   159  			values: `(1, 'a')`,
   160  		},
   161  		{
   162  			name:   `MULTI_WIDTHS`,
   163  			schema: `(a INT PRIMARY KEY, b DECIMAL (3,2), c DECIMAL (2, 1))`,
   164  			values: `(1, 1.23, 4.5)`,
   165  		},
   166  	}
   167  	// Generate a test for each column type with a random datum of that type.
   168  	for _, typ := range types.OidToType {
   169  		switch typ.Family() {
   170  		case types.AnyFamily, types.OidFamily, types.TupleFamily:
   171  			// These aren't expected to be needed for changefeeds.
   172  			continue
   173  		case types.IntervalFamily, types.ArrayFamily, types.BitFamily,
   174  			types.CollatedStringFamily:
   175  			// Implement these as customer demand dictates.
   176  			continue
   177  		}
   178  		datum := sqlbase.RandDatum(rng, typ, false /* nullOk */)
   179  		if datum == tree.DNull {
   180  			// DNull is returned by RandDatum for types.UNKNOWN or if the
   181  			// column type is unimplemented in RandDatum. In either case, the
   182  			// correct thing to do is skip this one.
   183  			continue
   184  		}
   185  		switch typ.Family() {
   186  		case types.TimestampFamily:
   187  			// Truncate to millisecond instead of microsecond because of a bug
   188  			// in the avro lib's deserialization code. The serialization seems
   189  			// to be fine and we only use deserialization for testing, so we
   190  			// should patch the bug but it's not currently affecting changefeed
   191  			// correctness.
   192  			// TODO(mjibson): goavro mishandles timestamps
   193  			// whose nanosecond representation overflows an
   194  			// int64, so restrict input to fit.
   195  			t := randTime(rng).Truncate(time.Millisecond)
   196  			datum = tree.MustMakeDTimestamp(t, time.Microsecond)
   197  		case types.TimestampTZFamily:
   198  			// See comments above for TimestampFamily.
   199  			t := randTime(rng).Truncate(time.Millisecond)
   200  			datum = tree.MustMakeDTimestampTZ(t, time.Microsecond)
   201  		case types.DecimalFamily:
   202  			// TODO(dan): Make RandDatum respect Precision and Width instead.
   203  			// TODO(dan): The precision is really meant to be in [1,10], but it
   204  			// sure looks like there's an off by one error in the avro library
   205  			// that makes this test flake if it picks precision of 1.
   206  			precision := rng.Int31n(10) + 2
   207  			scale := rng.Int31n(precision + 1)
   208  			typ = types.MakeDecimal(precision, scale)
   209  			coeff := rng.Int63n(int64(math.Pow10(int(precision))))
   210  			datum = &tree.DDecimal{Decimal: *apd.New(coeff, -scale)}
   211  		case types.DateFamily:
   212  			// TODO(mjibson): goavro mishandles dates whose
   213  			// nanosecond representation overflows an int64,
   214  			// so restrict input to fit.
   215  			var err error
   216  			datum, err = tree.NewDDateFromTime(randTime(rng))
   217  			if err != nil {
   218  				panic(err)
   219  			}
   220  		}
   221  		serializedDatum := tree.Serialize(datum)
   222  		// name can be "char" (with quotes), so needs to be escaped.
   223  		escapedName := fmt.Sprintf("%s_table", strings.Replace(typ.String(), "\"", "", -1))
   224  		// schema is used in a fmt.Sprintf to fill in the table name, so we have
   225  		// to escape any stray %s.
   226  		escapedDatum := strings.Replace(serializedDatum, `%`, `%%`, -1)
   227  		randTypeTest := test{
   228  			name:   escapedName,
   229  			schema: fmt.Sprintf(`(a INT PRIMARY KEY, b %s)`, typ.SQLString()),
   230  			values: fmt.Sprintf(`(1, %s)`, escapedDatum),
   231  		}
   232  		tests = append(tests, randTypeTest)
   233  	}
   234  
   235  	for _, test := range tests {
   236  		t.Run(test.name, func(t *testing.T) {
   237  			tableDesc, err := parseTableDesc(
   238  				fmt.Sprintf(`CREATE TABLE "%s" %s`, test.name, test.schema))
   239  			require.NoError(t, err)
   240  			origSchema, err := tableToAvroSchema(tableDesc, avroSchemaNoSuffix)
   241  			require.NoError(t, err)
   242  			jsonSchema := origSchema.codec.Schema()
   243  			roundtrippedSchema, err := parseAvroSchema(jsonSchema)
   244  			require.NoError(t, err)
   245  			// It would require some work, but we could also check that the
   246  			// roundtrippedSchema can be used to recreate the original `CREATE
   247  			// TABLE`.
   248  
   249  			rows, err := parseValues(tableDesc, `VALUES `+test.values)
   250  			require.NoError(t, err)
   251  
   252  			for _, row := range rows {
   253  				evalCtx := &tree.EvalContext{SessionData: &sessiondata.SessionData{}}
   254  				serialized, err := origSchema.textualFromRow(row)
   255  				require.NoError(t, err)
   256  				roundtripped, err := roundtrippedSchema.rowFromTextual(serialized)
   257  				require.NoError(t, err)
   258  				require.Equal(t, 0, row[1].Datum.Compare(evalCtx, roundtripped[1].Datum),
   259  					`%s != %s`, row[1].Datum, roundtripped[1].Datum)
   260  
   261  				serialized, err = origSchema.BinaryFromRow(nil, row)
   262  				require.NoError(t, err)
   263  				roundtripped, err = roundtrippedSchema.RowFromBinary(serialized)
   264  				require.NoError(t, err)
   265  				require.Equal(t, 0, row[1].Datum.Compare(evalCtx, roundtripped[1].Datum),
   266  					`%s != %s`, row[1].Datum, roundtripped[1].Datum)
   267  			}
   268  		})
   269  	}
   270  
   271  	t.Run("escaping", func(t *testing.T) {
   272  		tableDesc, err := parseTableDesc(`CREATE TABLE "☃" (🍦 INT PRIMARY KEY)`)
   273  		require.NoError(t, err)
   274  		tableSchema, err := tableToAvroSchema(tableDesc, avroSchemaNoSuffix)
   275  		require.NoError(t, err)
   276  		require.Equal(t,
   277  			`{"type":"record","name":"_u2603_","fields":[`+
   278  				`{"type":["null","long"],"name":"_u0001f366_","default":null,`+
   279  				`"__crdb__":"🍦 INT8 NOT NULL"}]}`,
   280  			tableSchema.codec.Schema())
   281  		indexSchema, err := indexToAvroSchema(tableDesc, &tableDesc.PrimaryIndex)
   282  		require.NoError(t, err)
   283  		require.Equal(t,
   284  			`{"type":"record","name":"_u2603_","fields":[`+
   285  				`{"type":["null","long"],"name":"_u0001f366_","default":null,`+
   286  				`"__crdb__":"🍦 INT8 NOT NULL"}]}`,
   287  			indexSchema.codec.Schema())
   288  	})
   289  
   290  	// This test shows what avro schema each sql column maps to, for easy
   291  	// reference.
   292  	t.Run("type_goldens", func(t *testing.T) {
   293  		goldens := map[string]string{
   294  			`BOOL`:         `["null","boolean"]`,
   295  			`BYTES`:        `["null","bytes"]`,
   296  			`DATE`:         `["null",{"type":"int","logicalType":"date"}]`,
   297  			`FLOAT8`:       `["null","double"]`,
   298  			`GEOGRAPHY`:    `["null","bytes"]`,
   299  			`GEOMETRY`:     `["null","bytes"]`,
   300  			`INET`:         `["null","string"]`,
   301  			`INT8`:         `["null","long"]`,
   302  			`JSONB`:        `["null","string"]`,
   303  			`STRING`:       `["null","string"]`,
   304  			`TIME`:         `["null",{"type":"long","logicalType":"time-micros"}]`,
   305  			`TIMETZ`:       `["null","string"]`,
   306  			`TIMESTAMP`:    `["null",{"type":"long","logicalType":"timestamp-micros"}]`,
   307  			`TIMESTAMPTZ`:  `["null",{"type":"long","logicalType":"timestamp-micros"}]`,
   308  			`UUID`:         `["null","string"]`,
   309  			`DECIMAL(3,2)`: `["null",{"type":"bytes","logicalType":"decimal","precision":3,"scale":2}]`,
   310  		}
   311  
   312  		for _, typ := range types.Scalar {
   313  			switch typ.Family() {
   314  			case types.IntervalFamily, types.OidFamily, types.BitFamily:
   315  				continue
   316  			case types.DecimalFamily:
   317  				typ = types.MakeDecimal(3, 2)
   318  			}
   319  
   320  			colType := typ.SQLString()
   321  			tableDesc, err := parseTableDesc(`CREATE TABLE foo (pk INT PRIMARY KEY, a ` + colType + `)`)
   322  			require.NoError(t, err)
   323  			field, err := columnDescToAvroSchema(&tableDesc.Columns[1])
   324  			require.NoError(t, err)
   325  			schema, err := json.Marshal(field.SchemaType)
   326  			require.NoError(t, err)
   327  			require.Equal(t, goldens[colType], string(schema), `SQL type %s`, colType)
   328  
   329  			// Delete from goldens for the following assertion that we don't have any
   330  			// unexpectedly unused goldens.
   331  			delete(goldens, colType)
   332  		}
   333  		if len(goldens) > 0 {
   334  			t.Fatalf("expected all goldens to be consumed: %v", goldens)
   335  		}
   336  	})
   337  
   338  	// This test shows what avro value some sql datums map to, for easy reference.
   339  	// The avro golden strings are in the textual format defined in the spec.
   340  	t.Run("value_goldens", func(t *testing.T) {
   341  		goldens := []struct {
   342  			sqlType string
   343  			sql     string
   344  			avro    string
   345  		}{
   346  			{sqlType: `INT`, sql: `NULL`, avro: `null`},
   347  			{sqlType: `INT`,
   348  				sql:  `1`,
   349  				avro: `{"long":1}`},
   350  
   351  			{sqlType: `BOOL`, sql: `NULL`, avro: `null`},
   352  			{sqlType: `BOOL`,
   353  				sql:  `true`,
   354  				avro: `{"boolean":true}`},
   355  
   356  			{sqlType: `FLOAT`, sql: `NULL`, avro: `null`},
   357  			{sqlType: `FLOAT`,
   358  				sql:  `1.2`,
   359  				avro: `{"double":1.2}`},
   360  
   361  			{sqlType: `GEOGRAPHY`, sql: `NULL`, avro: `null`},
   362  			{sqlType: `GEOGRAPHY`,
   363  				sql:  "'POINT(1.0 1.0)'",
   364  				avro: `{"bytes":"\u0001\u0001\u0000\u0000 \u00E6\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00F0?\u0000\u0000\u0000\u0000\u0000\u0000\u00F0?"}`},
   365  			{sqlType: `GEOMETRY`, sql: `NULL`, avro: `null`},
   366  			{sqlType: `GEOMETRY`,
   367  				sql:  "'POINT(1.0 1.0)'",
   368  				avro: `{"bytes":"\u0001\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00F0?\u0000\u0000\u0000\u0000\u0000\u0000\u00F0?"}`},
   369  
   370  			{sqlType: `STRING`, sql: `NULL`, avro: `null`},
   371  			{sqlType: `STRING`,
   372  				sql:  `'foo'`,
   373  				avro: `{"string":"foo"}`},
   374  
   375  			{sqlType: `BYTES`, sql: `NULL`, avro: `null`},
   376  			{sqlType: `BYTES`,
   377  				sql:  `'foo'`,
   378  				avro: `{"bytes":"foo"}`},
   379  
   380  			{sqlType: `DATE`, sql: `NULL`, avro: `null`},
   381  			{sqlType: `DATE`,
   382  				sql:  `'2019-01-02'`,
   383  				avro: `{"int.date":17898}`},
   384  
   385  			{sqlType: `TIME`, sql: `NULL`, avro: `null`},
   386  			{sqlType: `TIME`,
   387  				sql:  `'03:04:05'`,
   388  				avro: `{"long.time-micros":11045000000}`},
   389  
   390  			{sqlType: `TIMESTAMP`, sql: `NULL`, avro: `null`},
   391  			{sqlType: `TIMESTAMP`,
   392  				sql:  `'2019-01-02 03:04:05'`,
   393  				avro: `{"long.timestamp-micros":1546398245000000}`},
   394  
   395  			{sqlType: `TIMESTAMPTZ`, sql: `NULL`, avro: `null`},
   396  			{sqlType: `TIMESTAMPTZ`,
   397  				sql:  `'2019-01-02 03:04:05'`,
   398  				avro: `{"long.timestamp-micros":1546398245000000}`},
   399  
   400  			{sqlType: `DECIMAL(4,1)`, sql: `NULL`, avro: `null`},
   401  			{sqlType: `DECIMAL(4,1)`,
   402  				sql:  `1.2`,
   403  				avro: `{"bytes.decimal":"\f"}`},
   404  
   405  			{sqlType: `UUID`, sql: `NULL`, avro: `null`},
   406  			{sqlType: `UUID`,
   407  				sql:  `'27f4f4c9-e35a-45dd-9b79-5ff0f9b5fbb0'`,
   408  				avro: `{"string":"27f4f4c9-e35a-45dd-9b79-5ff0f9b5fbb0"}`},
   409  
   410  			{sqlType: `INET`, sql: `NULL`, avro: `null`},
   411  			{sqlType: `INET`,
   412  				sql:  `'190.0.0.0'`,
   413  				avro: `{"string":"190.0.0.0"}`},
   414  			{sqlType: `INET`,
   415  				sql:  `'190.0.0.0/24'`,
   416  				avro: `{"string":"190.0.0.0\/24"}`},
   417  			{sqlType: `INET`,
   418  				sql:  `'2001:4f8:3:ba:2e0:81ff:fe22:d1f1'`,
   419  				avro: `{"string":"2001:4f8:3:ba:2e0:81ff:fe22:d1f1"}`},
   420  			{sqlType: `INET`,
   421  				sql:  `'2001:4f8:3:ba:2e0:81ff:fe22:d1f1/120'`,
   422  				avro: `{"string":"2001:4f8:3:ba:2e0:81ff:fe22:d1f1\/120"}`},
   423  			{sqlType: `INET`,
   424  				sql:  `'::ffff:192.168.0.1/24'`,
   425  				avro: `{"string":"::ffff:192.168.0.1\/24"}`},
   426  
   427  			{sqlType: `JSONB`, sql: `NULL`, avro: `null`},
   428  			{sqlType: `JSONB`,
   429  				sql:  `'null'`,
   430  				avro: `{"string":"null"}`},
   431  			{sqlType: `JSONB`,
   432  				sql:  `'{"b": 1}'`,
   433  				avro: `{"string":"{\"b\": 1}"}`},
   434  		}
   435  
   436  		for _, test := range goldens {
   437  			tableDesc, err := parseTableDesc(
   438  				`CREATE TABLE foo (pk INT PRIMARY KEY, a ` + test.sqlType + `)`)
   439  			require.NoError(t, err)
   440  			rows, err := parseValues(tableDesc, `VALUES (1, `+test.sql+`)`)
   441  			require.NoError(t, err)
   442  
   443  			schema, err := tableToAvroSchema(tableDesc, avroSchemaNoSuffix)
   444  			require.NoError(t, err)
   445  			textual, err := schema.textualFromRow(rows[0])
   446  			require.NoError(t, err)
   447  			// Trim the outermost {}.
   448  			value := string(textual[1 : len(textual)-1])
   449  			// Strip out the pk field.
   450  			value = strings.Replace(value, `"pk":{"long":1}`, ``, -1)
   451  			// Trim the `,`, which could be on either side because of the avro library
   452  			// doesn't deterministically order the fields.
   453  			value = strings.Trim(value, `,`)
   454  			// Strip out the field name.
   455  			value = strings.Replace(value, `"a":`, ``, -1)
   456  			require.Equal(t, test.avro, value)
   457  		}
   458  	})
   459  }
   460  
   461  func (f *avroSchemaField) defaultValueNative() (interface{}, bool) {
   462  	schemaType := f.SchemaType
   463  	if union, ok := schemaType.([]avroSchemaType); ok {
   464  		// "Default values for union fields correspond to the first schema in
   465  		// the union."
   466  		schemaType = union[0]
   467  	}
   468  	switch schemaType {
   469  	case avroSchemaNull:
   470  		return nil, true
   471  	}
   472  	panic(errors.Errorf(`unimplemented %T: %v`, schemaType, schemaType))
   473  }
   474  
   475  // rowFromBinaryEvolved decodes `buf` using writerSchema but evolves/resolves it
   476  // to readerSchema using the rules from the avro spec:
   477  // https://avro.apache.org/docs/1.8.2/spec.html#Schema+Resolution
   478  //
   479  // It'd be nice if our avro library handled this for us, but neither of the
   480  // popular golang once seem to have it implemented.
   481  func rowFromBinaryEvolved(
   482  	buf []byte, writerSchema, readerSchema *avroDataRecord,
   483  ) (sqlbase.EncDatumRow, error) {
   484  	native, newBuf, err := writerSchema.codec.NativeFromBinary(buf)
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  	if len(newBuf) > 0 {
   489  		return nil, errors.New(`only one row was expected`)
   490  	}
   491  	nativeMap, ok := native.(map[string]interface{})
   492  	if !ok {
   493  		return nil, errors.Errorf(`unknown avro native type: %T`, native)
   494  	}
   495  	adjustNative(nativeMap, writerSchema, readerSchema)
   496  	return readerSchema.rowFromNative(nativeMap)
   497  }
   498  
   499  func adjustNative(native map[string]interface{}, writerSchema, readerSchema *avroDataRecord) {
   500  	for _, writerField := range writerSchema.Fields {
   501  		if _, inReader := readerSchema.fieldIdxByName[writerField.Name]; !inReader {
   502  			// "If the writer's record contains a field with a name not present
   503  			// in the reader's record, the writer's value for that field is
   504  			// ignored."
   505  			delete(native, writerField.Name)
   506  		}
   507  	}
   508  	for _, readerField := range readerSchema.Fields {
   509  		if _, inWriter := writerSchema.fieldIdxByName[readerField.Name]; !inWriter {
   510  			// "If the reader's record schema has a field that contains a
   511  			// default value, and writer's schema does not have a field with the
   512  			// same name, then the reader should use the default value from its
   513  			// field."
   514  			if readerFieldDefault, ok := readerField.defaultValueNative(); ok {
   515  				native[readerField.Name] = readerFieldDefault
   516  			}
   517  		}
   518  	}
   519  }
   520  
   521  func TestAvroMigration(t *testing.T) {
   522  	defer leaktest.AfterTest(t)()
   523  
   524  	type test struct {
   525  		name           string
   526  		writerSchema   string
   527  		writerValues   string
   528  		readerSchema   string
   529  		expectedValues string
   530  	}
   531  	tests := []test{
   532  		{
   533  			name:           `add_nullable`,
   534  			writerSchema:   `(a INT PRIMARY KEY)`,
   535  			writerValues:   `(1)`,
   536  			readerSchema:   `(a INT PRIMARY KEY, b INT)`,
   537  			expectedValues: `(1, NULL)`,
   538  		},
   539  	}
   540  	for _, test := range tests {
   541  		t.Run(test.name, func(t *testing.T) {
   542  			writerDesc, err := parseTableDesc(
   543  				fmt.Sprintf(`CREATE TABLE "%s" %s`, test.name, test.writerSchema))
   544  			require.NoError(t, err)
   545  			writerSchema, err := tableToAvroSchema(writerDesc, avroSchemaNoSuffix)
   546  			require.NoError(t, err)
   547  			readerDesc, err := parseTableDesc(
   548  				fmt.Sprintf(`CREATE TABLE "%s" %s`, test.name, test.readerSchema))
   549  			require.NoError(t, err)
   550  			readerSchema, err := tableToAvroSchema(readerDesc, avroSchemaNoSuffix)
   551  			require.NoError(t, err)
   552  
   553  			writerRows, err := parseValues(writerDesc, `VALUES `+test.writerValues)
   554  			require.NoError(t, err)
   555  			expectedRows, err := parseValues(readerDesc, `VALUES `+test.expectedValues)
   556  			require.NoError(t, err)
   557  
   558  			for i := range writerRows {
   559  				writerRow, expectedRow := writerRows[i], expectedRows[i]
   560  				encoded, err := writerSchema.BinaryFromRow(nil, writerRow)
   561  				require.NoError(t, err)
   562  				row, err := rowFromBinaryEvolved(encoded, writerSchema, readerSchema)
   563  				require.NoError(t, err)
   564  				require.Equal(t, expectedRow, row)
   565  			}
   566  		})
   567  	}
   568  }
   569  
   570  func TestDecimalRatRoundtrip(t *testing.T) {
   571  	defer leaktest.AfterTest(t)()
   572  
   573  	t.Run(`table`, func(t *testing.T) {
   574  		tests := []struct {
   575  			scale int32
   576  			dec   *apd.Decimal
   577  		}{
   578  			{0, apd.New(0, 0)},
   579  			{0, apd.New(1, 0)},
   580  			{0, apd.New(-1, 0)},
   581  			{0, apd.New(123, 0)},
   582  			{1, apd.New(0, -1)},
   583  			{1, apd.New(1, -1)},
   584  			{1, apd.New(123, -1)},
   585  			{5, apd.New(1, -5)},
   586  		}
   587  		for d, test := range tests {
   588  			rat, err := decimalToRat(*test.dec, test.scale)
   589  			require.NoError(t, err)
   590  			roundtrip := ratToDecimal(rat, test.scale)
   591  			if test.dec.CmpTotal(&roundtrip) != 0 {
   592  				t.Errorf(`%d: %s != %s`, d, test.dec, &roundtrip)
   593  			}
   594  		}
   595  	})
   596  	t.Run(`error`, func(t *testing.T) {
   597  		_, err := decimalToRat(*apd.New(1, -2), 1)
   598  		require.EqualError(t, err, "0.01 will not roundtrip at scale 1")
   599  		_, err = decimalToRat(*apd.New(1, -1), 2)
   600  		require.EqualError(t, err, "0.1 will not roundtrip at scale 2")
   601  		_, err = decimalToRat(apd.Decimal{Form: apd.Infinite}, 0)
   602  		require.EqualError(t, err, "cannot convert Infinite form decimal")
   603  	})
   604  	t.Run(`rand`, func(t *testing.T) {
   605  		rng, _ := randutil.NewPseudoRand()
   606  		precision := rng.Int31n(10) + 1
   607  		scale := rng.Int31n(precision + 1)
   608  		coeff := rng.Int63n(int64(math.Pow10(int(precision))))
   609  		dec := apd.New(coeff, -scale)
   610  		rat, err := decimalToRat(*dec, scale)
   611  		require.NoError(t, err)
   612  		roundtrip := ratToDecimal(rat, scale)
   613  		if dec.CmpTotal(&roundtrip) != 0 {
   614  			t.Errorf(`%s != %s`, dec, &roundtrip)
   615  		}
   616  	})
   617  }