Repository: amzn/ion-go
Pull request #93 (2020-07-07 22:13:27)

Fix issues with struct
1- Empty string is a valid value for field names.
Default value for strings in Go is empty string. Checking whether a fieldName is a set or not based on checking them against "", disallow us for having something like ```{"":empty}```.
One way to solve this issue is to change ```FieldName() string``` in Reader.go to ```FieldName() *string``` and set ```nil``` as ```reader.fieldName```'s default value in both ```textReader``` and ```binaryReader```. Then we will be able to change check if a fieldName is not set by ```r.fieldName != nil```. However, I do not think using nil as a magic value is a good idea in Go, and secondly changing return type of ```FieldName()``` is changing the API.
Alternatively in [this commit](https://github.com/amzn/ion-go/commit/d6c914ba3c51e8e144feddb6c4ab4104278701df), I introduced a constant representing a value for when field name is not set (for now I chose UNDEFINED, but open to other suggestions if we decided to go with this solution). The downside is we are omitting one certain word to be a field name and also it is not aligned with other implementations of Ion libraries. 



2- If a struct is marked as an ordered struct, there must be at least one symbol/value pair: [this commit](https://github.com/amzn/ion-go/commit/33d9fc1d70043d5394f6df86ea9d3d3128624c08)

fernomac: Hm, I don't love the idea of having a secret, non-empty-string value that can't get used as a field name. How would you feel about the approach [here](https://github.com/amzn/ion-go/compare/master...fernomac:empty-fieldnames), using `*string` internally, keeping the interface backwards compatible, and adding a `Reader.HasFieldName` for the (rare?) case where someone needs to explicitly check if an empty string from `Reader.FieldName` means no field name or the actual empty string?
Armanbqt: Discussed offline and it was decided to change ```FieldName() string``` in Reader interface to ```FieldName() *string```, and consequently ```filedName ``` attribute of ```reader``` struct.

Patch:
diff --git a/cmd/ion-go/eventwriter.go b/cmd/ion-go/eventwriter.go
index 40f9240..55717c5 100644
--- a/cmd/ion-go/eventwriter.go
+++ b/cmd/ion-go/eventwriter.go
@@ -13,7 +13,7 @@ type eventwriter struct {
 	enc *ion.Encoder
 
 	depth       int
-	fieldname   string
+	fieldname   *string
 	annotations []string
 	inStruct    map[int]bool
 }
@@ -28,7 +28,7 @@ func NewEventWriter(out io.Writer) ion.Writer {
 }
 
 func (e *eventwriter) FieldName(val string) error {
-	e.fieldname = val
+	e.fieldname = &val
 	return nil
 }
 
@@ -253,12 +253,12 @@ func clobify(val []byte) string {
 
 func (e *eventwriter) write(ev event) error {
 	name := e.fieldname
-	e.fieldname = ""
+	e.fieldname = nil
 	annos := e.annotations
 	e.annotations = nil
 
-	if name != "" {
-		ev.FieldName = &token{Text: name}
+	if name != nil {
+		ev.FieldName = &token{Text: *name}
 	}
 
 	if len(annos) > 0 {
diff --git a/cmd/ion-go/process.go b/cmd/ion-go/process.go
index 1a0be8a..2ccc821 100644
--- a/cmd/ion-go/process.go
+++ b/cmd/ion-go/process.go
@@ -176,8 +176,8 @@ func (p *processor) process(in ion.Reader) error {
 	for in.Next() {
 		p.idx++
 		name := in.FieldName()
-		if name != "" {
-			if err = p.out.FieldName(name); err != nil {
+		if name != nil {
+			if err = p.out.FieldName(*name); err != nil {
 				return p.error(write, err)
 			}
 		}
diff --git a/ion/binaryreader.go b/ion/binaryreader.go
index 9a64a7c..d13dcc1 100644
--- a/ion/binaryreader.go
+++ b/ion/binaryreader.go
@@ -250,7 +250,7 @@ func (r *binaryReader) readLocalSymbolTable() error {
 
 	for r.Next() {
 		var err error
-		switch r.FieldName() {
+		switch *r.FieldName() {
 		case "imports":
 			imps, err = r.readImports()
 		case "symbols":
@@ -319,7 +319,7 @@ func (r *binaryReader) readImport() (SharedSymbolTable, error) {
 
 	for r.Next() {
 		var err error
-		switch r.FieldName() {
+		switch *r.FieldName() {
 		case "name":
 			if r.Type() == StringType {
 				name, err = r.StringValue()
@@ -416,7 +416,8 @@ func (r *binaryReader) readFieldName() error {
 		return err
 	}
 
-	r.fieldName = r.resolve(id)
+	fn := r.resolve(id)
+	r.fieldName = &fn
 	return nil
 }
 
diff --git a/ion/binarywriter.go b/ion/binarywriter.go
index d3b6e06..2f40d88 100644
--- a/ion/binarywriter.go
+++ b/ion/binarywriter.go
@@ -449,11 +449,11 @@ func (w *binaryWriter) beginValue(api string) error {
 	}
 
 	if w.IsInStruct() {
-		if name == "" {
+		if name == nil {
 			return &UsageError{api, "field name not set"}
 		}
 
-		id, err := w.resolve(api, name)
+		id, err := w.resolve(api, *name)
 		if err != nil {
 			return err
 		}
diff --git a/ion/bitstream.go b/ion/bitstream.go
index a629b94..1885a9f 100644
--- a/ion/bitstream.go
+++ b/ion/bitstream.go
@@ -181,6 +181,10 @@ func (b *bitstream) Next() error {
 		if err != nil {
 			return err
 		}
+		if len == 0 {
+			// Ordered structs must have at least one symbol/value pair.
+			return &SyntaxError{"ordered structs cannot be empty", b.pos - 1}
+		}
 	}
 
 	if code == bitcodeNone {
diff --git a/ion/reader.go b/ion/reader.go
index f3c9255..8914c3f 100644
--- a/ion/reader.go
+++ b/ion/reader.go
@@ -85,7 +85,7 @@ type Reader interface {
 	// FieldName returns the field name associated with the current value. It returns
 	// the empty string if there is no current value or the current value has no field
 	// name.
-	FieldName() string
+	FieldName() *string
 
 	// Annotations returns the set of annotations associated with the current value.
 	// It returns nil if there is no current value or the current value has no annotations.
@@ -187,7 +187,7 @@ type reader struct {
 	eof bool
 	err error
 
-	fieldName   string
+	fieldName   *string
 	annotations []string
 	valueType   Type
 	value       interface{}
@@ -209,7 +209,7 @@ func (r *reader) IsNull() bool {
 }
 
 // FieldName returns the current value's field name.
-func (r *reader) FieldName() string {
+func (r *reader) FieldName() *string {
 	return r.fieldName
 }
 
@@ -384,7 +384,7 @@ func (r *reader) ByteValue() ([]byte, error) {
 
 // Clear clears the current value from the reader.
 func (r *reader) clear() {
-	r.fieldName = ""
+	r.fieldName = nil
 	r.annotations = nil
 	r.valueType = NoType
 	r.value = nil
diff --git a/ion/textreader.go b/ion/textreader.go
index d9cf407..e71147a 100644
--- a/ion/textreader.go
+++ b/ion/textreader.go
@@ -176,7 +176,7 @@ func (t *textReader) nextBeforeFieldName() (bool, error) {
 			return false, &UnexpectedTokenError{tok.String(), t.tok.Pos() - 1}
 		}
 
-		t.fieldName = val
+		t.fieldName = &val
 		t.state = trsBeforeTypeAnnotations
 
 		return false, nil
diff --git a/ion/textwriter.go b/ion/textwriter.go
index 17c23d8..47bebaa 100644
--- a/ion/textwriter.go
+++ b/ion/textwriter.go
@@ -353,13 +353,13 @@ func (w *textWriter) writeSeparator() error {
 
 // writeFieldName writes a field name inside a struct.
 func (w *textWriter) writeFieldName(api string) error {
-	if w.fieldName == "" {
+	if w.fieldName == nil {
 		return &UsageError{api, "field name not set"}
 	}
 	name := w.fieldName
-	w.fieldName = ""
+	w.fieldName = nil
 
-	if err := writeSymbol(name, w.out); err != nil {
+	if err := writeSymbol(*name, w.out); err != nil {
 		return err
 	}
 
diff --git a/ion/unmarshal.go b/ion/unmarshal.go
index e5e6109..c1e01cc 100644
--- a/ion/unmarshal.go
+++ b/ion/unmarshal.go
@@ -138,7 +138,7 @@ func (d *Decoder) decodeMap() (map[string]interface{}, error) {
 		if err != nil {
 			return nil, err
 		}
-		result[name] = value
+		result[*name] = value
 	}
 
 	if err := d.r.StepOut(); err != nil {
@@ -482,7 +482,7 @@ func (d *Decoder) decodeStructToStruct(v reflect.Value) error {
 
 	for d.r.Next() {
 		name := d.r.FieldName()
-		field := findField(fields, name)
+		field := findField(fields, *name)
 		if field != nil {
 			subv, err := findSubvalue(v, field)
 			if err != nil {
@@ -555,7 +555,7 @@ func (d *Decoder) decodeStructToMap(v reflect.Value) error {
 		var kv reflect.Value
 		switch t.Key().Kind() {
 		case reflect.String:
-			kv = reflect.ValueOf(name)
+			kv = reflect.ValueOf(*name)
 		default:
 			panic("wat?")
 		}
diff --git a/ion/writer.go b/ion/writer.go
index 83d6133..e4e6639 100644
--- a/ion/writer.go
+++ b/ion/writer.go
@@ -121,7 +121,7 @@ type writer struct {
 	ctx ctxstack
 	err error
 
-	fieldName   string
+	fieldName   *string
 	annotations []string
 }
 
@@ -136,7 +136,7 @@ func (w *writer) FieldName(val string) error {
 		return w.err
 	}
 
-	w.fieldName = val
+	w.fieldName = &val
 	return nil
 }
 
@@ -163,6 +163,6 @@ func (w *writer) IsInStruct() bool {
 
 // Clear clears field name and annotations after writing a value.
 func (w *writer) clear() {
-	w.fieldName = ""
+	w.fieldName = nil
 	w.annotations = nil
 }


Test patch:
diff --git a/ion/binaryreader_test.go b/ion/binaryreader_test.go
index 4f0901b..e46f452 100644
--- a/ion/binaryreader_test.go
+++ b/ion/binaryreader_test.go
@@ -123,8 +123,8 @@ func TestReadBinaryLST(t *testing.T) {
 		t.Fatal("symboltable is nil")
 	}
 
-	if lst.MaxID() != 111 {
-		t.Errorf("expected maxid=111, got %v", lst.MaxID())
+	if lst.MaxID() != 112 {
+		t.Errorf("expected maxid=112, got %v", lst.MaxID())
 	}
 
 	if _, ok := lst.FindByID(109); ok {
@@ -139,6 +139,14 @@ func TestReadBinaryLST(t *testing.T) {
 		t.Errorf("expected $111=bar, got %v", sym)
 	}
 
+	sym, ok = lst.FindByID(112)
+	if !ok {
+		t.Fatal("no symbol defined for $112")
+	}
+	if sym != "" {
+		t.Errorf("expected $112='', got %v", sym)
+	}
+
 	id, ok := lst.FindByName("foo")
 	if !ok {
 		t.Fatal("no id defined for foo")
@@ -147,8 +155,8 @@ func TestReadBinaryLST(t *testing.T) {
 		t.Errorf("expected foo=$110, got $%v", id)
 	}
 
-	if _, ok := lst.FindByID(112); ok {
-		t.Error("found a symbol for $112")
+	if _, ok := lst.FindByID(113); ok {
+		t.Error("found a symbol for $113")
 	}
 
 	if _, ok := lst.FindByName("bogus"); ok {
@@ -162,19 +170,25 @@ func TestReadBinaryStructs(t *testing.T) {
 		0xD0,                   // {}
 		0xEA, 0x81, 0xEE, 0xD7, // foo::{
 		0x84, 0xE3, 0x81, 0xEF, 0xD0, // name:bar::{},
-		0x88, 0x20, // max_id:0
-		// }
+		0x88, 0x20, // max_id:0},
+		0xD3, 0xF0, 0x21, 0x0F, // {"":15},
 	})
 
 	_null(t, r, StructType)
 	_struct(t, r, func(t *testing.T, r Reader) {
 		_eof(t, r)
 	})
-	_structAF(t, r, "", []string{"foo"}, func(t *testing.T, r Reader) {
-		_structAF(t, r, "name", []string{"bar"}, func(t *testing.T, r Reader) {
+	_structAF(t, r, nil, []string{"foo"}, func(t *testing.T, r Reader) {
+		name := "name"
+		_structAF(t, r, &name, []string{"bar"}, func(t *testing.T, r Reader) {
 			_eof(t, r)
 		})
-		_intAF(t, r, "max_id", nil, 0)
+		maxID := "max_id"
+		_intAF(t, r, &maxID, nil, 0)
+	})
+	emptyString := ""
+	_structAF(t, r, &emptyString, nil, func(t *testing.T, r Reader) {
+		_intAF(t, r, &emptyString, nil, 15)
 	})
 	_eof(t, r)
 }
@@ -288,7 +302,7 @@ func TestReadBinarySymbols(t *testing.T) {
 	_symbol(t, r, "$ion")
 	_symbol(t, r, "$10")
 	_symbol(t, r, "foo")
-	_symbolAF(t, r, "", []string{"foo"}, "bar")
+	_symbolAF(t, r, nil, []string{"foo"}, "bar")
 	_symbol(t, r, fmt.Sprintf("$%v", uint64(math.MaxUint64)))
 	_eof(t, r)
 }
@@ -423,8 +437,8 @@ func TestReadBinaryNulls(t *testing.T) {
 	})
 
 	_null(t, r, NullType)
-	_nullAF(t, r, NullType, "", []string{"$ion"})
-	_nullAF(t, r, NullType, "", []string{"foo", "bar"})
+	_nullAF(t, r, NullType, nil, []string{"$ion"})
+	_nullAF(t, r, NullType, nil, []string{"foo", "bar"})
 	_eof(t, r)
 }
 
@@ -437,16 +451,17 @@ func TestReadEmptyBinary(t *testing.T) {
 func readBinary(ion []byte) Reader {
 	prefix := []byte{
 		0xE0, 0x01, 0x00, 0xEA, // $ion_1_0
-		0xEE, 0x9F, 0x81, 0x83, 0xDE, 0x9B, // $ion_symbol_table::{
+		0xEE, 0xA0, 0x81, 0x83, 0xDE, 0x9C, // $ion_symbol_table::{
 		0x86, 0xBE, 0x8E, // imports:[
 		0xDD,                                // {
 		0x84, 0x85, 'b', 'o', 'g', 'u', 's', // name: "bogus"
 		0x85, 0x21, 0x2A, // version: 42
 		0x88, 0x21, 0x64, // max_id: 100
 		// }]
-		0x87, 0xB8, // symbols: [
+		0x87, 0xB9, // symbols: [
 		0x83, 'f', 'o', 'o', // "foo"
 		0x83, 'b', 'a', 'r', // "bar"
+		0x80, // ""
 		// ]
 		// }
 	}
diff --git a/ion/integration_test.go b/ion/integration_test.go
index 74f9a9e..fabbdc9 100644
--- a/ion/integration_test.go
+++ b/ion/integration_test.go
@@ -37,12 +37,14 @@ type ionItem struct {
 	ionType     Type
 	annotations []string
 	value       []interface{}
+	fieldName   string
 }
 
 func (i *ionItem) equal(o ionItem) bool {
 	return reflect.DeepEqual(i.value, o.value) &&
 		reflect.DeepEqual(i.annotations, o.annotations) &&
-		reflect.DeepEqual(i.ionType, o.ionType)
+		reflect.DeepEqual(i.ionType, o.ionType) &&
+		reflect.DeepEqual(i.fieldName, o.fieldName)
 }
 
 var readGoodFilesSkipList = []string{
@@ -63,7 +65,6 @@ var binaryRoundTripSkipList = []string{
 	"leapDayRollover.ion",
 	"lists.ion",
 	"localSymbolTableImportZeroMaxId.ion",
-	"structWhitespace.ion",
 	"symbolEmpty.ion",
 	"T6-large.10n",
 	"T6-small.10n",
@@ -98,7 +99,6 @@ var textRoundTripSkipList = []string{
 	"lists.ion",
 	"localSymbolTableImportZeroMaxId.ion",
 	"notVersionMarkers.ion",
-	"structWhitespace.ion",
 	"subfieldVarUInt.ion",
 	"subfieldVarUInt15bit.ion",
 	"subfieldVarUInt16bit.ion",
@@ -168,7 +168,6 @@ var malformedIonsSkipList = []string{
 	"nopPadWithAnnotations.10n",
 	"nullDotCommentInt.ion",
 	"sexpOperatorAnnotation.ion",
-	"structOrderedEmpty.10n",
 	"surrogate_1.ion",
 	"surrogate_10.ion",
 	"surrogate_2.ion",
@@ -528,8 +527,8 @@ func isInSkipList(skipList []string, fn string) bool {
 func writeToWriterFromReader(t *testing.T, reader Reader, writer Writer) {
 	for reader.Next() {
 		name := reader.FieldName()
-		if name != "" {
-			err := writer.FieldName(name)
+		if name != nil {
+			err := writer.FieldName(*name)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -716,6 +715,11 @@ func readCurrentValue(t *testing.T, reader Reader) ionItem {
 		ionItem.annotations = an
 	}
 
+	fn := reader.FieldName()
+	if fn != nil {
+		ionItem.fieldName = *fn
+	}
+
 	currentType := reader.Type()
 	if reader.IsNull() {
 		ionItem.value = append(ionItem.value, textNulls[currentType])
diff --git a/ion/textreader_test.go b/ion/textreader_test.go
index f3c5816..24fe3d0 100644
--- a/ion/textreader_test.go
+++ b/ion/textreader_test.go
@@ -44,7 +44,7 @@ func TestReadSexps(t *testing.T) {
 	test("(foo bar baz :: boop)", func(t *testing.T, r Reader) {
 		_symbol(t, r, "foo")
 		_symbol(t, r, "bar")
-		_symbolAF(t, r, "", []string{"baz"}, "boop")
+		_symbolAF(t, r, nil, []string{"baz"}, "boop")
 	})
 }
 
@@ -62,13 +62,20 @@ func TestStructs(t *testing.T) {
 	})
 
 	test("{foo : bar :: baz}", func(t *testing.T, r Reader) {
-		_symbolAF(t, r, "foo", []string{"bar"}, "baz")
+		foo := "foo"
+		_symbolAF(t, r, &foo, []string{"bar"}, "baz")
 	})
 
 	test("{foo: a, bar: b, baz: c}", func(t *testing.T, r Reader) {
-		_symbolAF(t, r, "foo", nil, "a")
-		_symbolAF(t, r, "bar", nil, "b")
-		_symbolAF(t, r, "baz", nil, "c")
+		foo, bar, baz := "foo", "bar", "baz"
+		_symbolAF(t, r, &foo, nil, "a")
+		_symbolAF(t, r, &bar, nil, "b")
+		_symbolAF(t, r, &baz, nil, "c")
+	})
+
+	test("{\"\": a}", func(t *testing.T, r Reader) {
+		emptyString := ""
+		_symbolAF(t, r, &emptyString, nil, "a")
 	})
 }
 
@@ -88,7 +95,7 @@ func TestNullStructs(t *testing.T) {
 	r := NewReaderStr("null.struct 'null'::{foo:bar}")
 
 	_null(t, r, StructType)
-	_nextAF(t, r, StructType, "", []string{"null"})
+	_nextAF(t, r, StructType, nil, []string{"null"})
 	_eof(t, r)
 }
 
@@ -113,7 +120,7 @@ func TestLists(t *testing.T) {
 	test("[foo, bar, baz::boop]", func(t *testing.T, r Reader) {
 		_symbol(t, r, "foo")
 		_symbol(t, r, "bar")
-		_symbolAF(t, r, "", []string{"baz"}, "boop")
+		_symbolAF(t, r, nil, []string{"baz"}, "boop")
 		_eof(t, r)
 	})
 }
@@ -189,7 +196,7 @@ func TestTimestamps(t *testing.T) {
 	testA := func(str string, etas []string, eval time.Time) {
 		t.Run(str, func(t *testing.T) {
 			r := NewReaderStr(str)
-			_nextAF(t, r, TimestampType, "", etas)
+			_nextAF(t, r, TimestampType, nil, etas)
 
 			val, err := r.TimeValue()
 			if err != nil {
@@ -229,7 +236,7 @@ func TestDecimals(t *testing.T) {
 			ee := MustParseDecimal(eval)
 
 			r := NewReaderStr(str)
-			_nextAF(t, r, DecimalType, "", etas)
+			_nextAF(t, r, DecimalType, nil, etas)
 
 			val, err := r.DecimalValue()
 			if err != nil {
@@ -261,7 +268,7 @@ func TestFloats(t *testing.T) {
 	testA := func(str string, etas []string, eval float64) {
 		t.Run(str, func(t *testing.T) {
 			r := NewReaderStr(str)
-			_floatAF(t, r, "", etas, eval)
+			_floatAF(t, r, nil, etas, eval)
 			_eof(t, r)
 		})
 	}
@@ -354,9 +361,9 @@ func TestInts(t *testing.T) {
 func TestStrings(t *testing.T) {
 	r := NewReaderStr(`foo::"bar" "baz" 'a'::'b'::'''beep''' '''boop''' null.string`)
 
-	_stringAF(t, r, "", []string{"foo"}, "bar")
+	_stringAF(t, r, nil, []string{"foo"}, "bar")
 	_string(t, r, "baz")
-	_stringAF(t, r, "", []string{"a", "b"}, "beepboop")
+	_stringAF(t, r, nil, []string{"a", "b"}, "beepboop")
 	_null(t, r, StringType)
 
 	_eof(t, r)
@@ -365,9 +372,9 @@ func TestStrings(t *testing.T) {
 func TestSymbols(t *testing.T) {
 	r := NewReaderStr("'null'::foo bar a::b::'baz' null.symbol")
 
-	_symbolAF(t, r, "", []string{"null"}, "foo")
+	_symbolAF(t, r, nil, []string{"null"}, "foo")
 	_symbol(t, r, "bar")
-	_symbolAF(t, r, "", []string{"a", "b"}, "baz")
+	_symbolAF(t, r, nil, []string{"a", "b"}, "baz")
 	_null(t, r, SymbolType)
 
 	_eof(t, r)
@@ -458,30 +465,30 @@ func TestInStruct(t *testing.T) {
 type containerhandler func(t *testing.T, r Reader)
 
 func _sexp(t *testing.T, r Reader, f containerhandler) {
-	_sexpAF(t, r, "", nil, f)
+	_sexpAF(t, r, nil, nil, f)
 }
 
-func _sexpAF(t *testing.T, r Reader, efn string, etas []string, f containerhandler) {
+func _sexpAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
 	_containerAF(t, r, SexpType, efn, etas, f)
 }
 
 func _struct(t *testing.T, r Reader, f containerhandler) {
-	_structAF(t, r, "", nil, f)
+	_structAF(t, r, nil, nil, f)
 }
 
-func _structAF(t *testing.T, r Reader, efn string, etas []string, f containerhandler) {
+func _structAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
 	_containerAF(t, r, StructType, efn, etas, f)
 }
 
 func _list(t *testing.T, r Reader, f containerhandler) {
-	_listAF(t, r, "", nil, f)
+	_listAF(t, r, nil, nil, f)
 }
 
-func _listAF(t *testing.T, r Reader, efn string, etas []string, f containerhandler) {
+func _listAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
 	_containerAF(t, r, ListType, efn, etas, f)
 }
 
-func _containerAF(t *testing.T, r Reader, et Type, efn string, etas []string, f containerhandler) {
+func _containerAF(t *testing.T, r Reader, et Type, efn *string, etas []string, f containerhandler) {
 	_nextAF(t, r, et, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.%v", et, et)
@@ -499,10 +506,10 @@ func _containerAF(t *testing.T, r Reader, et Type, efn string, etas []string, f
 }
 
 func _int(t *testing.T, r Reader, eval int) {
-	_intAF(t, r, "", nil, eval)
+	_intAF(t, r, nil, nil, eval)
 }
 
-func _intAF(t *testing.T, r Reader, efn string, etas []string, eval int) {
+func _intAF(t *testing.T, r Reader, efn *string, etas []string, eval int) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -526,10 +533,10 @@ func _intAF(t *testing.T, r Reader, efn string, etas []string, eval int) {
 }
 
 func _int64(t *testing.T, r Reader, eval int64) {
-	_int64AF(t, r, "", nil, eval)
+	_int64AF(t, r, nil, nil, eval)
 }
 
-func _int64AF(t *testing.T, r Reader, efn string, etas []string, eval int64) {
+func _int64AF(t *testing.T, r Reader, efn *string, etas []string, eval int64) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -553,10 +560,10 @@ func _int64AF(t *testing.T, r Reader, efn string, etas []string, eval int64) {
 }
 
 func _uint(t *testing.T, r Reader, eval uint64) {
-	_uintAF(t, r, "", nil, eval)
+	_uintAF(t, r, nil, nil, eval)
 }
 
-func _uintAF(t *testing.T, r Reader, efn string, etas []string, eval uint64) {
+func _uintAF(t *testing.T, r Reader, efn *string, etas []string, eval uint64) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -580,10 +587,10 @@ func _uintAF(t *testing.T, r Reader, efn string, etas []string, eval uint64) {
 }
 
 func _bigInt(t *testing.T, r Reader, eval *big.Int) {
-	_bigIntAF(t, r, "", nil, eval)
+	_bigIntAF(t, r, nil, nil, eval)
 }
 
-func _bigIntAF(t *testing.T, r Reader, efn string, etas []string, eval *big.Int) {
+func _bigIntAF(t *testing.T, r Reader, efn *string, etas []string, eval *big.Int) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -607,10 +614,10 @@ func _bigIntAF(t *testing.T, r Reader, efn string, etas []string, eval *big.Int)
 }
 
 func _float(t *testing.T, r Reader, eval float64) {
-	_floatAF(t, r, "", nil, eval)
+	_floatAF(t, r, nil, nil, eval)
 }
 
-func _floatAF(t *testing.T, r Reader, efn string, etas []string, eval float64) {
+func _floatAF(t *testing.T, r Reader, efn *string, etas []string, eval float64) {
 	_nextAF(t, r, FloatType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.float", eval)
@@ -631,10 +638,10 @@ func _floatAF(t *testing.T, r Reader, efn string, etas []string, eval float64) {
 }
 
 func _decimal(t *testing.T, r Reader, eval *Decimal) {
-	_decimalAF(t, r, "", nil, eval)
+	_decimalAF(t, r, nil, nil, eval)
 }
 
-func _decimalAF(t *testing.T, r Reader, efn string, etas []string, eval *Decimal) {
+func _decimalAF(t *testing.T, r Reader, efn *string, etas []string, eval *Decimal) {
 	_nextAF(t, r, DecimalType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.decimal", eval)
@@ -651,10 +658,10 @@ func _decimalAF(t *testing.T, r Reader, efn string, etas []string, eval *Decimal
 }
 
 func _timestamp(t *testing.T, r Reader, eval time.Time) {
-	_timestampAF(t, r, "", nil, eval)
+	_timestampAF(t, r, nil, nil, eval)
 }
 
-func _timestampAF(t *testing.T, r Reader, efn string, etas []string, eval time.Time) {
+func _timestampAF(t *testing.T, r Reader, efn *string, etas []string, eval time.Time) {
 	_nextAF(t, r, TimestampType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.timestamp", eval)
@@ -671,10 +678,10 @@ func _timestampAF(t *testing.T, r Reader, efn string, etas []string, eval time.T
 }
 
 func _string(t *testing.T, r Reader, eval string) {
-	_stringAF(t, r, "", nil, eval)
+	_stringAF(t, r, nil, nil, eval)
 }
 
-func _stringAF(t *testing.T, r Reader, efn string, etas []string, eval string) {
+func _stringAF(t *testing.T, r Reader, efn *string, etas []string, eval string) {
 	_nextAF(t, r, StringType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.string", eval)
@@ -690,10 +697,10 @@ func _stringAF(t *testing.T, r Reader, efn string, etas []string, eval string) {
 }
 
 func _symbol(t *testing.T, r Reader, eval string) {
-	_symbolAF(t, r, "", nil, eval)
+	_symbolAF(t, r, nil, nil, eval)
 }
 
-func _symbolAF(t *testing.T, r Reader, efn string, etas []string, eval string) {
+func _symbolAF(t *testing.T, r Reader, efn *string, etas []string, eval string) {
 	_nextAF(t, r, SymbolType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.symbol", eval)
@@ -709,10 +716,10 @@ func _symbolAF(t *testing.T, r Reader, efn string, etas []string, eval string) {
 }
 
 func _bool(t *testing.T, r Reader, eval bool) {
-	_boolAF(t, r, "", nil, eval)
+	_boolAF(t, r, nil, nil, eval)
 }
 
-func _boolAF(t *testing.T, r Reader, efn string, etas []string, eval bool) {
+func _boolAF(t *testing.T, r Reader, efn *string, etas []string, eval bool) {
 	_nextAF(t, r, BoolType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.bool", eval)
@@ -728,10 +735,10 @@ func _boolAF(t *testing.T, r Reader, efn string, etas []string, eval bool) {
 }
 
 func _clob(t *testing.T, r Reader, eval []byte) {
-	_clobAF(t, r, "", nil, eval)
+	_clobAF(t, r, nil, nil, eval)
 }
 
-func _clobAF(t *testing.T, r Reader, efn string, etas []string, eval []byte) {
+func _clobAF(t *testing.T, r Reader, efn *string, etas []string, eval []byte) {
 	_nextAF(t, r, ClobType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.clob", eval)
@@ -747,10 +754,10 @@ func _clobAF(t *testing.T, r Reader, efn string, etas []string, eval []byte) {
 }
 
 func _blob(t *testing.T, r Reader, eval []byte) {
-	_blobAF(t, r, "", nil, eval)
+	_blobAF(t, r, nil, nil, eval)
 }
 
-func _blobAF(t *testing.T, r Reader, efn string, etas []string, eval []byte) {
+func _blobAF(t *testing.T, r Reader, efn *string, etas []string, eval []byte) {
 	_nextAF(t, r, BlobType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.blob", eval)
@@ -766,10 +773,10 @@ func _blobAF(t *testing.T, r Reader, efn string, etas []string, eval []byte) {
 }
 
 func _null(t *testing.T, r Reader, et Type) {
-	_nullAF(t, r, et, "", nil)
+	_nullAF(t, r, et, nil, nil)
 }
 
-func _nullAF(t *testing.T, r Reader, et Type, efn string, etas []string) {
+func _nullAF(t *testing.T, r Reader, et Type, efn *string, etas []string) {
 	_nextAF(t, r, et, efn, etas)
 	if !r.IsNull() {
 		t.Error("isnull returned false")
@@ -777,10 +784,10 @@ func _nullAF(t *testing.T, r Reader, et Type, efn string, etas []string) {
 }
 
 func _next(t *testing.T, r Reader, et Type) {
-	_nextAF(t, r, et, "", nil)
+	_nextAF(t, r, et, nil, nil)
 }
 
-func _nextAF(t *testing.T, r Reader, et Type, efn string, etas []string) {
+func _nextAF(t *testing.T, r Reader, et Type, efn *string, etas []string) {
 	if !r.Next() {
 		t.Fatal(r.Err())
 	}
@@ -788,8 +795,9 @@ func _nextAF(t *testing.T, r Reader, et Type, efn string, etas []string) {
 		t.Fatalf("expected %v, got %v", et, r.Type())
 	}
 
-	if efn != r.FieldName() {
-		t.Errorf("expected fieldname=%v, got %v", efn, r.FieldName())
+	fn := r.FieldName()
+	if efn != nil && fn != nil && *efn != *fn {
+		t.Errorf("expected fieldname=%v, got %v", *efn, *fn)
 	}
 	if !_strequals(etas, r.Annotations()) {
 		t.Errorf("expected type annotations=%v, got %v", etas, r.Annotations())