| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package iceberg |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "iter" |
| "maps" |
| "slices" |
| "strings" |
| "sync" |
| "sync/atomic" |
| "unicode" |
| ) |
| |
| // Schema is an Iceberg table schema, represented as a struct with |
| // multiple fields. The fields are only exported via accessor methods |
| // rather than exposing the slice directly in order to ensure a schema |
| // as immutable. |
| type Schema struct { |
| ID int `json:"schema-id"` |
| IdentifierFieldIDs []int `json:"identifier-field-ids"` |
| |
| fields []NestedField |
| |
| // the following maps are lazily populated as needed. |
| // rather than have lock contention with a mutex, we can use |
| // atomic pointers to Store/Load the values. |
| idToName atomic.Pointer[map[int]string] |
| idToField atomic.Pointer[map[int]NestedField] |
| nameToID atomic.Pointer[map[string]int] |
| nameToIDLower atomic.Pointer[map[string]int] |
| idToAccessor atomic.Pointer[map[int]accessor] |
| |
| lazyIDToParent func() (map[int]int, error) |
| lazyNameMapping func() NameMapping |
| } |
| |
| // NewSchemaFromJsonFields constructs a new schema with the provided ID and fields in json form |
| func NewSchemaFromJsonFields(id int, jsonFieldsStr string) (*Schema, error) { |
| var fields []NestedField |
| err := json.Unmarshal([]byte(jsonFieldsStr), &fields) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse schema JSON: %w", err) |
| } |
| |
| return NewSchema(id, fields...), nil |
| } |
| |
| // NewSchema constructs a new schema with the provided ID |
| // and list of fields. |
| func NewSchema(id int, fields ...NestedField) *Schema { |
| return NewSchemaWithIdentifiers(id, []int{}, fields...) |
| } |
| |
| // NewSchemaWithIdentifiers constructs a new schema with the provided ID |
| // and fields, along with a slice of field IDs to be listed as identifier |
| // fields. |
| func NewSchemaWithIdentifiers(id int, identifierIDs []int, fields ...NestedField) *Schema { |
| s := &Schema{ID: id, fields: fields, IdentifierFieldIDs: identifierIDs} |
| s.init() |
| |
| return s |
| } |
| |
| func (s *Schema) init() { |
| s.lazyIDToParent = sync.OnceValues(func() (map[int]int, error) { |
| return IndexParents(s) |
| }) |
| s.lazyNameMapping = sync.OnceValue(func() NameMapping { |
| return createMappingFromSchema(s) |
| }) |
| } |
| |
| func (s *Schema) String() string { |
| var b strings.Builder |
| b.WriteString("table {") |
| for _, f := range s.fields { |
| b.WriteString("\n\t") |
| b.WriteString(f.String()) |
| } |
| b.WriteString("\n}") |
| |
| return b.String() |
| } |
| |
| func (s *Schema) lazyNameToID() (map[string]int, error) { |
| index := s.nameToID.Load() |
| if index != nil { |
| return *index, nil |
| } |
| |
| idx, err := IndexByName(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.nameToID.Store(&idx) |
| |
| return idx, nil |
| } |
| |
| // FlatFields returns an iterator over the flattened fields in the schema |
| // The fields are returned in arbitrary order. |
| func (s *Schema) FlatFields() (iter.Seq[NestedField], error) { |
| fields, err := s.lazyIDToField() |
| if err != nil { |
| return nil, err |
| } |
| |
| return maps.Values(fields), nil |
| } |
| |
| func (s *Schema) lazyIDToField() (map[int]NestedField, error) { |
| index := s.idToField.Load() |
| if index != nil { |
| return *index, nil |
| } |
| |
| idx, err := IndexByID(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.idToField.Store(&idx) |
| |
| return idx, nil |
| } |
| |
| func (s *Schema) lazyIDToName() (map[int]string, error) { |
| index := s.idToName.Load() |
| if index != nil { |
| return *index, nil |
| } |
| |
| idx, err := IndexNameByID(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.idToName.Store(&idx) |
| |
| return idx, nil |
| } |
| |
| func (s *Schema) lazyNameToIDLower() (map[string]int, error) { |
| index := s.nameToIDLower.Load() |
| if index != nil { |
| return *index, nil |
| } |
| |
| idx, err := s.lazyNameToID() |
| if err != nil { |
| return nil, err |
| } |
| |
| out := make(map[string]int) |
| for k, v := range idx { |
| out[strings.ToLower(k)] = v |
| } |
| |
| s.nameToIDLower.Store(&out) |
| |
| return out, nil |
| } |
| |
| func (s *Schema) lazyIdToAccessor() (map[int]accessor, error) { |
| index := s.idToAccessor.Load() |
| if index != nil { |
| return *index, nil |
| } |
| |
| idx, err := buildAccessors(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| s.idToAccessor.Store(&idx) |
| |
| return idx, nil |
| } |
| |
| func (s *Schema) NameMapping() NameMapping { return s.lazyNameMapping() } |
| |
| func (s *Schema) Type() string { return "struct" } |
| |
| // AsStruct returns a Struct with the same fields as the schema which can |
| // then be used as a Type. |
| func (s *Schema) AsStruct() StructType { return StructType{FieldList: s.fields} } |
| func (s *Schema) NumFields() int { return len(s.fields) } |
| func (s *Schema) Field(i int) NestedField { return s.fields[i] } |
| func (s *Schema) Fields() []NestedField { return slices.Clone(s.fields) } |
| func (s *Schema) FieldIDs() []int { |
| idx, _ := s.lazyNameToID() |
| |
| return slices.Collect(maps.Values(idx)) |
| } |
| |
| func (s *Schema) UnmarshalJSON(b []byte) error { |
| type Alias Schema |
| aux := struct { |
| Fields []NestedField `json:"fields"` |
| *Alias |
| }{Alias: (*Alias)(s)} |
| |
| if err := json.Unmarshal(b, &aux); err != nil { |
| return err |
| } |
| |
| s.init() |
| |
| s.fields = aux.Fields |
| if s.IdentifierFieldIDs == nil { |
| s.IdentifierFieldIDs = []int{} |
| } |
| |
| return nil |
| } |
| |
| func (s *Schema) MarshalJSON() ([]byte, error) { |
| if s.IdentifierFieldIDs == nil { |
| s.IdentifierFieldIDs = []int{} |
| } |
| |
| type Alias Schema |
| |
| return json.Marshal(struct { |
| Type string `json:"type"` |
| Fields []NestedField `json:"fields"` |
| *Alias |
| }{Type: "struct", Fields: s.fields, Alias: (*Alias)(s)}) |
| } |
| |
| // FindColumnName returns the name of the column identified by the |
| // passed in field id. The second return value reports whether or |
| // not the field id was found in the schema. |
| func (s *Schema) FindColumnName(fieldID int) (string, bool) { |
| idx, _ := s.lazyIDToName() |
| col, ok := idx[fieldID] |
| |
| return col, ok |
| } |
| |
| // FindFieldByName returns the field identified by the name given, |
| // the second return value will be false if no field by this name |
| // is found. |
| // |
| // Note: This search is done in a case sensitive manner. To perform |
| // a case insensitive search, use [*Schema.FindFieldByNameCaseInsensitive]. |
| func (s *Schema) FindFieldByName(name string) (NestedField, bool) { |
| idx, _ := s.lazyNameToID() |
| |
| id, ok := idx[name] |
| if !ok { |
| return NestedField{}, false |
| } |
| |
| return s.FindFieldByID(id) |
| } |
| |
| // FindFieldByNameCaseInsensitive is like [*Schema.FindFieldByName], |
| // but performs a case insensitive search. |
| func (s *Schema) FindFieldByNameCaseInsensitive(name string) (NestedField, bool) { |
| idx, _ := s.lazyNameToIDLower() |
| |
| id, ok := idx[strings.ToLower(name)] |
| if !ok { |
| return NestedField{}, false |
| } |
| |
| return s.FindFieldByID(id) |
| } |
| |
| // FindFieldByID is like [*Schema.FindColumnName], but returns the whole |
| // field rather than just the field name. |
| func (s *Schema) FindFieldByID(id int) (NestedField, bool) { |
| idx, _ := s.lazyIDToField() |
| f, ok := idx[id] |
| |
| return f, ok |
| } |
| |
| // FindTypeByID is like [*Schema.FindFieldByID], but returns only the data |
| // type of the field. |
| func (s *Schema) FindTypeByID(id int) (Type, bool) { |
| f, ok := s.FindFieldByID(id) |
| if !ok { |
| return nil, false |
| } |
| |
| return f.Type, true |
| } |
| |
| // FindTypeByName is a convenience function for calling [*Schema.FindFieldByName], |
| // and then returning just the type. |
| func (s *Schema) FindTypeByName(name string) (Type, bool) { |
| f, ok := s.FindFieldByName(name) |
| if !ok { |
| return nil, false |
| } |
| |
| return f.Type, true |
| } |
| |
| // FindTypeByNameCaseInsensitive is like [*Schema.FindTypeByName] but |
| // performs a case insensitive search. |
| func (s *Schema) FindTypeByNameCaseInsensitive(name string) (Type, bool) { |
| f, ok := s.FindFieldByNameCaseInsensitive(name) |
| if !ok { |
| return nil, false |
| } |
| |
| return f.Type, true |
| } |
| |
| func (s *Schema) accessorForField(id int) (accessor, bool) { |
| idx, err := s.lazyIdToAccessor() |
| if err != nil { |
| return accessor{}, false |
| } |
| |
| acc, ok := idx[id] |
| |
| return acc, ok |
| } |
| |
| // Equals compares the fields and identifierIDs, but does not compare |
| // the schema ID itself. |
| func (s *Schema) Equals(other *Schema) bool { |
| if other == nil { |
| return false |
| } |
| |
| if s == other { |
| return true |
| } |
| |
| if len(s.fields) != len(other.fields) { |
| return false |
| } |
| |
| if !slices.Equal(s.IdentifierFieldIDs, other.IdentifierFieldIDs) { |
| return false |
| } |
| |
| return slices.EqualFunc(s.fields, other.fields, func(a, b NestedField) bool { |
| return a.Equals(b) |
| }) |
| } |
| |
| // HighestFieldID returns the value of the numerically highest field ID |
| // in this schema. |
| func (s *Schema) HighestFieldID() int { |
| id, _ := Visit(s, findLastFieldID{}) |
| |
| return id |
| } |
| |
| type Void = struct{} |
| |
| var void = Void{} |
| |
| // Select creates a new schema with just the fields identified by name |
| // passed in the order they are provided. If caseSensitive is false, |
| // then fields will be identified by case insensitive search. |
| // |
| // An error is returned if a requested name cannot be found. |
| func (s *Schema) Select(caseSensitive bool, names ...string) (*Schema, error) { |
| ids := make(map[int]Void) |
| if caseSensitive { |
| nameMap, _ := s.lazyNameToID() |
| for _, n := range names { |
| id, ok := nameMap[n] |
| if !ok { |
| return nil, fmt.Errorf("%w: could not find column %s", ErrInvalidSchema, n) |
| } |
| ids[id] = void |
| } |
| } else { |
| nameMap, _ := s.lazyNameToIDLower() |
| for _, n := range names { |
| id, ok := nameMap[strings.ToLower(n)] |
| if !ok { |
| return nil, fmt.Errorf("%w: could not find column %s", ErrInvalidSchema, n) |
| } |
| ids[id] = void |
| } |
| } |
| |
| return PruneColumns(s, ids, true) |
| } |
| |
| func (s *Schema) FieldHasOptionalParent(id int) bool { |
| idToParent, _ := s.lazyIDToParent() |
| idToField, _ := s.lazyIDToField() |
| |
| f, ok := idToField[id] |
| if !ok { |
| return false |
| } |
| |
| for { |
| parent, ok := idToParent[f.ID] |
| if !ok { |
| return false |
| } |
| |
| if f = idToField[parent]; !f.Required { |
| return true |
| } |
| } |
| } |
| |
| // SchemaVisitor is an interface that can be implemented to allow for |
| // easy traversal and processing of a schema. |
| // |
| // A SchemaVisitor can also optionally implement the Before/After Field, |
| // ListElement, MapKey, or MapValue interfaces to allow them to get called |
| // at the appropriate points within schema traversal. |
| type SchemaVisitor[T any] interface { |
| Schema(schema *Schema, structResult T) T |
| Struct(st StructType, fieldResults []T) T |
| Field(field NestedField, fieldResult T) T |
| List(list ListType, elemResult T) T |
| Map(mapType MapType, keyResult, valueResult T) T |
| Primitive(p PrimitiveType) T |
| } |
| |
| type BeforeFieldVisitor interface { |
| BeforeField(field NestedField) |
| } |
| |
| type AfterFieldVisitor interface { |
| AfterField(field NestedField) |
| } |
| |
| type BeforeListElementVisitor interface { |
| BeforeListElement(elem NestedField) |
| } |
| |
| type AfterListElementVisitor interface { |
| AfterListElement(elem NestedField) |
| } |
| |
| type BeforeMapKeyVisitor interface { |
| BeforeMapKey(key NestedField) |
| } |
| |
| type AfterMapKeyVisitor interface { |
| AfterMapKey(key NestedField) |
| } |
| |
| type BeforeMapValueVisitor interface { |
| BeforeMapValue(value NestedField) |
| } |
| |
| type AfterMapValueVisitor interface { |
| AfterMapValue(value NestedField) |
| } |
| |
| type SchemaVisitorPerPrimitiveType[T any] interface { |
| SchemaVisitor[T] |
| |
| VisitFixed(FixedType) T |
| VisitDecimal(DecimalType) T |
| VisitBoolean() T |
| VisitInt32() T |
| VisitInt64() T |
| VisitFloat32() T |
| VisitFloat64() T |
| VisitDate() T |
| VisitTime() T |
| VisitTimestamp() T |
| VisitTimestampNs() T |
| VisitTimestampTz() T |
| VisitTimestampNsTz() T |
| VisitString() T |
| VisitBinary() T |
| VisitUUID() T |
| } |
| |
| // Visit accepts a visitor and performs a post-order traversal of the given schema. |
| func Visit[T any](sc *Schema, visitor SchemaVisitor[T]) (res T, err error) { |
| if sc == nil { |
| err = fmt.Errorf("%w: cannot visit nil schema", ErrInvalidArgument) |
| |
| return res, err |
| } |
| |
| defer func() { |
| if r := recover(); r != nil { |
| switch e := r.(type) { |
| case string: |
| err = fmt.Errorf("error encountered during schema visitor: %s", e) |
| case error: |
| err = fmt.Errorf("error encountered during schema visitor: %w", e) |
| } |
| } |
| }() |
| |
| return visitor.Schema(sc, visitStruct(sc.AsStruct(), visitor)), nil |
| } |
| |
| func visitStruct[T any](obj StructType, visitor SchemaVisitor[T]) T { |
| results := make([]T, len(obj.FieldList)) |
| |
| bf, _ := visitor.(BeforeFieldVisitor) |
| af, _ := visitor.(AfterFieldVisitor) |
| |
| for i, f := range obj.FieldList { |
| if bf != nil { |
| bf.BeforeField(f) |
| } |
| |
| res := visitField(f, visitor) |
| |
| if af != nil { |
| af.AfterField(f) |
| } |
| |
| results[i] = visitor.Field(f, res) |
| } |
| |
| return visitor.Struct(obj, results) |
| } |
| |
| func visitList[T any](obj ListType, visitor SchemaVisitor[T]) T { |
| elemField := obj.ElementField() |
| |
| if bl, ok := visitor.(BeforeListElementVisitor); ok { |
| bl.BeforeListElement(elemField) |
| } else if bf, ok := visitor.(BeforeFieldVisitor); ok { |
| bf.BeforeField(elemField) |
| } |
| |
| res := visitField(elemField, visitor) |
| |
| if al, ok := visitor.(AfterListElementVisitor); ok { |
| al.AfterListElement(elemField) |
| } else if af, ok := visitor.(AfterFieldVisitor); ok { |
| af.AfterField(elemField) |
| } |
| |
| return visitor.List(obj, res) |
| } |
| |
| func visitMap[T any](obj MapType, visitor SchemaVisitor[T]) T { |
| keyField, valueField := obj.KeyField(), obj.ValueField() |
| |
| if bmk, ok := visitor.(BeforeMapKeyVisitor); ok { |
| bmk.BeforeMapKey(keyField) |
| } else if bf, ok := visitor.(BeforeFieldVisitor); ok { |
| bf.BeforeField(keyField) |
| } |
| |
| keyRes := visitField(keyField, visitor) |
| |
| if amk, ok := visitor.(AfterMapKeyVisitor); ok { |
| amk.AfterMapKey(keyField) |
| } else if af, ok := visitor.(AfterFieldVisitor); ok { |
| af.AfterField(keyField) |
| } |
| |
| if bmk, ok := visitor.(BeforeMapValueVisitor); ok { |
| bmk.BeforeMapValue(valueField) |
| } else if bf, ok := visitor.(BeforeFieldVisitor); ok { |
| bf.BeforeField(valueField) |
| } |
| |
| valueRes := visitField(valueField, visitor) |
| |
| if amk, ok := visitor.(AfterMapValueVisitor); ok { |
| amk.AfterMapValue(valueField) |
| } else if af, ok := visitor.(AfterFieldVisitor); ok { |
| af.AfterField(valueField) |
| } |
| |
| return visitor.Map(obj, keyRes, valueRes) |
| } |
| |
| func visitField[T any](f NestedField, visitor SchemaVisitor[T]) T { |
| switch typ := f.Type.(type) { |
| case *StructType: |
| return visitStruct(*typ, visitor) |
| case *ListType: |
| return visitList(*typ, visitor) |
| case *MapType: |
| return visitMap(*typ, visitor) |
| default: // primitive |
| if perPrimitive, ok := visitor.(SchemaVisitorPerPrimitiveType[T]); ok { |
| switch t := typ.(type) { |
| case BooleanType: |
| return perPrimitive.VisitBoolean() |
| case Int32Type: |
| return perPrimitive.VisitInt32() |
| case Int64Type: |
| return perPrimitive.VisitInt64() |
| case Float32Type: |
| return perPrimitive.VisitFloat32() |
| case Float64Type: |
| return perPrimitive.VisitFloat64() |
| case DateType: |
| return perPrimitive.VisitDate() |
| case TimeType: |
| return perPrimitive.VisitTime() |
| case TimestampType: |
| return perPrimitive.VisitTimestamp() |
| case TimestampNsType: |
| return perPrimitive.VisitTimestampNs() |
| case TimestampTzType: |
| return perPrimitive.VisitTimestampTz() |
| case TimestampTzNsType: |
| return perPrimitive.VisitTimestampNsTz() |
| case StringType: |
| return perPrimitive.VisitString() |
| case BinaryType: |
| return perPrimitive.VisitBinary() |
| case UUIDType: |
| return perPrimitive.VisitUUID() |
| case DecimalType: |
| return perPrimitive.VisitDecimal(t) |
| case FixedType: |
| return perPrimitive.VisitFixed(t) |
| } |
| } |
| |
| return visitor.Primitive(typ.(PrimitiveType)) |
| } |
| } |
| |
| type PreOrderSchemaVisitor[T any] interface { |
| Schema(*Schema, func() T) T |
| Struct(StructType, []func() T) T |
| Field(NestedField, func() T) T |
| List(ListType, func() T) T |
| Map(MapType, func() T, func() T) T |
| Primitive(PrimitiveType) T |
| } |
| |
| func PreOrderVisit[T any](sc *Schema, visitor PreOrderSchemaVisitor[T]) (res T, err error) { |
| if sc == nil { |
| err = fmt.Errorf("%w: cannot visit nil schema", ErrInvalidArgument) |
| |
| return res, err |
| } |
| |
| defer func() { |
| if r := recover(); r != nil { |
| switch e := r.(type) { |
| case string: |
| err = fmt.Errorf("error encountered during schema visitor: %s", e) |
| case error: |
| err = fmt.Errorf("error encountered during schema visitor: %w", e) |
| } |
| } |
| }() |
| |
| return visitor.Schema(sc, func() T { |
| return visitStructPreOrder(sc.AsStruct(), visitor) |
| }), nil |
| } |
| |
| func visitStructPreOrder[T any](obj StructType, visitor PreOrderSchemaVisitor[T]) T { |
| results := make([]func() T, len(obj.FieldList)) |
| |
| for i, f := range obj.FieldList { |
| results[i] = func() T { |
| return visitFieldPreOrder(f, visitor) |
| } |
| } |
| |
| return visitor.Struct(obj, results) |
| } |
| |
| func visitListPreOrder[T any](obj ListType, visitor PreOrderSchemaVisitor[T]) T { |
| return visitor.List(obj, func() T { |
| return visitFieldPreOrder(obj.ElementField(), visitor) |
| }) |
| } |
| |
| func visitMapPreOrder[T any](obj MapType, visitor PreOrderSchemaVisitor[T]) T { |
| return visitor.Map(obj, func() T { |
| return visitFieldPreOrder(obj.KeyField(), visitor) |
| }, func() T { |
| return visitFieldPreOrder(obj.ValueField(), visitor) |
| }) |
| } |
| |
| func visitFieldPreOrder[T any](f NestedField, visitor PreOrderSchemaVisitor[T]) T { |
| var fn func() T |
| switch typ := f.Type.(type) { |
| case *StructType: |
| fn = func() T { return visitStructPreOrder(*typ, visitor) } |
| case *ListType: |
| fn = func() T { return visitListPreOrder(*typ, visitor) } |
| case *MapType: |
| fn = func() T { return visitMapPreOrder(*typ, visitor) } |
| default: |
| fn = func() T { return visitor.Primitive(typ.(PrimitiveType)) } |
| } |
| |
| return visitor.Field(f, fn) |
| } |
| |
| // IndexByID performs a post-order traversal of the given schema and |
| // returns a mapping from field ID to field. |
| func IndexByID(schema *Schema) (map[int]NestedField, error) { |
| return Visit(schema, &indexByID{index: make(map[int]NestedField)}) |
| } |
| |
| type indexByID struct { |
| index map[int]NestedField |
| } |
| |
| func (i *indexByID) Schema(*Schema, map[int]NestedField) map[int]NestedField { |
| return i.index |
| } |
| |
| func (i *indexByID) Struct(StructType, []map[int]NestedField) map[int]NestedField { |
| return i.index |
| } |
| |
| func (i *indexByID) Field(field NestedField, _ map[int]NestedField) map[int]NestedField { |
| i.index[field.ID] = field |
| |
| return i.index |
| } |
| |
| func (i *indexByID) List(list ListType, _ map[int]NestedField) map[int]NestedField { |
| i.index[list.ElementID] = list.ElementField() |
| |
| return i.index |
| } |
| |
| func (i *indexByID) Map(mapType MapType, _, _ map[int]NestedField) map[int]NestedField { |
| i.index[mapType.KeyID] = mapType.KeyField() |
| i.index[mapType.ValueID] = mapType.ValueField() |
| |
| return i.index |
| } |
| |
| func (i *indexByID) Primitive(PrimitiveType) map[int]NestedField { |
| return i.index |
| } |
| |
| // IndexByName performs a post-order traversal of the schema and returns |
| // a mapping from field name to field ID. |
| func IndexByName(schema *Schema) (map[string]int, error) { |
| if schema == nil { |
| return nil, fmt.Errorf("%w: cannot index nil schema", ErrInvalidArgument) |
| } |
| |
| if len(schema.fields) > 0 { |
| indexer := &indexByName{ |
| index: make(map[string]int), |
| shortNameId: make(map[string]int), |
| fieldNames: make([]string, 0), |
| shortFieldNames: make([]string, 0), |
| } |
| if _, err := Visit(schema, indexer); err != nil { |
| return nil, err |
| } |
| |
| return indexer.ByName(), nil |
| } |
| |
| return map[string]int{}, nil |
| } |
| |
| // IndexNameByID performs a post-order traversal of the schema and returns |
| // a mapping from field ID to field name. |
| func IndexNameByID(schema *Schema) (map[int]string, error) { |
| indexer := &indexByName{ |
| index: make(map[string]int), |
| shortNameId: make(map[string]int), |
| fieldNames: make([]string, 0), |
| shortFieldNames: make([]string, 0), |
| } |
| if _, err := Visit(schema, indexer); err != nil { |
| return nil, err |
| } |
| |
| return indexer.ByID(), nil |
| } |
| |
| type indexByName struct { |
| index map[string]int |
| shortNameId map[string]int |
| combinedIndex map[string]int |
| fieldNames []string |
| shortFieldNames []string |
| } |
| |
| func (i *indexByName) ByID() map[int]string { |
| idToName := make(map[int]string) |
| for k, v := range i.index { |
| idToName[v] = k |
| } |
| |
| return idToName |
| } |
| |
| func (i *indexByName) ByName() map[string]int { |
| i.combinedIndex = maps.Clone(i.shortNameId) |
| maps.Copy(i.combinedIndex, i.index) |
| |
| return i.combinedIndex |
| } |
| |
| func (i *indexByName) Primitive(PrimitiveType) map[string]int { return i.index } |
| func (i *indexByName) addField(name string, fieldID int) { |
| fullName := name |
| if len(i.fieldNames) > 0 { |
| fullName = strings.Join(i.fieldNames, ".") + "." + name |
| } |
| |
| if _, ok := i.index[fullName]; ok { |
| panic(fmt.Errorf("%w: multiple fields for name %s: %d and %d", |
| ErrInvalidSchema, fullName, i.index[fullName], fieldID)) |
| } |
| |
| i.index[fullName] = fieldID |
| if len(i.shortFieldNames) > 0 { |
| shortName := strings.Join(i.shortFieldNames, ".") + "." + name |
| i.shortNameId[shortName] = fieldID |
| } |
| } |
| |
| func (i *indexByName) Schema(*Schema, map[string]int) map[string]int { |
| return i.index |
| } |
| |
| func (i *indexByName) Struct(StructType, []map[string]int) map[string]int { |
| return i.index |
| } |
| |
| func (i *indexByName) Field(field NestedField, _ map[string]int) map[string]int { |
| i.addField(field.Name, field.ID) |
| |
| return i.index |
| } |
| |
| func (i *indexByName) List(list ListType, _ map[string]int) map[string]int { |
| i.addField(list.ElementField().Name, list.ElementID) |
| |
| return i.index |
| } |
| |
| func (i *indexByName) Map(mapType MapType, _, _ map[string]int) map[string]int { |
| i.addField(mapType.KeyField().Name, mapType.KeyID) |
| i.addField(mapType.ValueField().Name, mapType.ValueID) |
| |
| return i.index |
| } |
| |
| func (i *indexByName) BeforeListElement(elem NestedField) { |
| if _, ok := elem.Type.(*StructType); !ok { |
| i.shortFieldNames = append(i.shortFieldNames, elem.Name) |
| } |
| i.fieldNames = append(i.fieldNames, elem.Name) |
| } |
| |
| func (i *indexByName) AfterListElement(elem NestedField) { |
| if _, ok := elem.Type.(*StructType); !ok { |
| i.shortFieldNames = i.shortFieldNames[:len(i.shortFieldNames)-1] |
| } |
| i.fieldNames = i.fieldNames[:len(i.fieldNames)-1] |
| } |
| |
| func (i *indexByName) BeforeField(field NestedField) { |
| i.fieldNames = append(i.fieldNames, field.Name) |
| i.shortFieldNames = append(i.shortFieldNames, field.Name) |
| } |
| |
| func (i *indexByName) AfterField(field NestedField) { |
| i.fieldNames = i.fieldNames[:len(i.fieldNames)-1] |
| i.shortFieldNames = i.shortFieldNames[:len(i.shortFieldNames)-1] |
| } |
| |
| // PruneColumns visits a schema pruning any columns which do not exist in the |
| // provided selected set. Parent fields of a selected child will be retained. |
| func PruneColumns(schema *Schema, selected map[int]Void, selectFullTypes bool) (*Schema, error) { |
| result, err := Visit(schema, &pruneColVisitor{ |
| selected: selected, |
| fullTypes: selectFullTypes, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| n, ok := result.(NestedType) |
| if !ok { |
| n = &StructType{} |
| } |
| |
| newIdentifierIDs := make([]int, 0, len(schema.IdentifierFieldIDs)) |
| for _, id := range schema.IdentifierFieldIDs { |
| if _, ok := selected[id]; ok { |
| newIdentifierIDs = append(newIdentifierIDs, id) |
| } |
| } |
| |
| return &Schema{ |
| fields: n.Fields(), |
| ID: schema.ID, |
| IdentifierFieldIDs: newIdentifierIDs, |
| }, nil |
| } |
| |
| type pruneColVisitor struct { |
| selected map[int]Void |
| fullTypes bool |
| } |
| |
| func (p *pruneColVisitor) Schema(_ *Schema, structResult Type) Type { |
| return structResult |
| } |
| |
| func (p *pruneColVisitor) Struct(st StructType, fieldResults []Type) Type { |
| selected, fields := []NestedField{}, st.FieldList |
| sameType := true |
| |
| for i, t := range fieldResults { |
| field := fields[i] |
| if field.Type == t { |
| selected = append(selected, field) |
| } else if t != nil { |
| sameType = false |
| // type has changed, create a new field with the projected type |
| selected = append(selected, NestedField{ |
| ID: field.ID, |
| Name: field.Name, |
| Type: t, |
| Doc: field.Doc, |
| Required: field.Required, |
| }) |
| } |
| } |
| |
| if len(selected) > 0 { |
| if len(selected) == len(fields) && sameType { |
| // nothing changed, return the original |
| return &st |
| } else { |
| return &StructType{FieldList: selected} |
| } |
| } |
| |
| return nil |
| } |
| |
| func (p *pruneColVisitor) Field(field NestedField, fieldResult Type) Type { |
| _, ok := p.selected[field.ID] |
| if !ok { |
| if fieldResult != nil { |
| return fieldResult |
| } |
| |
| return nil |
| } |
| |
| if p.fullTypes { |
| return field.Type |
| } |
| |
| if _, ok := field.Type.(*StructType); ok { |
| return p.projectSelectedStruct(fieldResult) |
| } |
| |
| typ, ok := field.Type.(PrimitiveType) |
| if !ok { |
| panic(fmt.Errorf("%w: cannot explicitly project List or Map types, %d:%s of type %s was selected", |
| ErrInvalidSchema, field.ID, field.Name, field.Type)) |
| } |
| |
| return typ |
| } |
| |
| func (p *pruneColVisitor) List(list ListType, elemResult Type) Type { |
| _, ok := p.selected[list.ElementID] |
| if !ok { |
| if elemResult != nil { |
| return p.projectList(&list, elemResult) |
| } |
| |
| return nil |
| } |
| |
| if p.fullTypes { |
| return &list |
| } |
| |
| _, ok = list.Element.(*StructType) |
| if list.Element != nil && ok { |
| projected := p.projectSelectedStruct(elemResult) |
| |
| return p.projectList(&list, projected) |
| } |
| |
| if _, ok = list.Element.(PrimitiveType); !ok { |
| panic(fmt.Errorf("%w: cannot explicitly project List or Map types, %d of type %s was selected", |
| ErrInvalidSchema, list.ElementID, list.Element)) |
| } |
| |
| return &list |
| } |
| |
| func (p *pruneColVisitor) Map(mapType MapType, keyResult, valueResult Type) Type { |
| _, ok := p.selected[mapType.ValueID] |
| if !ok { |
| if valueResult != nil { |
| return p.projectMap(&mapType, valueResult) |
| } |
| |
| if _, ok = p.selected[mapType.KeyID]; ok { |
| return &mapType |
| } |
| |
| return nil |
| } |
| |
| if p.fullTypes { |
| return &mapType |
| } |
| |
| _, ok = mapType.ValueType.(*StructType) |
| if mapType.ValueType != nil && ok { |
| projected := p.projectSelectedStruct(valueResult) |
| |
| return p.projectMap(&mapType, projected) |
| } |
| |
| if _, ok = mapType.ValueType.(PrimitiveType); !ok { |
| panic(fmt.Errorf("%w: cannot explicitly project List or Map types, Map value %d of type %s was selected", |
| ErrInvalidSchema, mapType.ValueID, mapType.ValueType)) |
| } |
| |
| return &mapType |
| } |
| |
| func (p *pruneColVisitor) Primitive(_ PrimitiveType) Type { return nil } |
| |
| func (*pruneColVisitor) projectSelectedStruct(projected Type) *StructType { |
| if projected == nil { |
| return &StructType{} |
| } |
| |
| if ty, ok := projected.(*StructType); ok { |
| return ty |
| } |
| |
| panic("expected a struct") |
| } |
| |
| func (*pruneColVisitor) projectList(listType *ListType, elementResult Type) *ListType { |
| if listType.Element.Equals(elementResult) { |
| return listType |
| } |
| |
| return &ListType{ |
| ElementID: listType.ElementID, Element: elementResult, |
| ElementRequired: listType.ElementRequired, |
| } |
| } |
| |
| func (*pruneColVisitor) projectMap(mapType *MapType, valueResult Type) *MapType { |
| if mapType.ValueType.Equals(valueResult) { |
| return mapType |
| } |
| |
| return &MapType{ |
| KeyID: mapType.KeyID, |
| ValueID: mapType.ValueID, |
| KeyType: mapType.KeyType, |
| ValueType: valueResult, |
| ValueRequired: mapType.ValueRequired, |
| } |
| } |
| |
| type findLastFieldID struct{} |
| |
| func (findLastFieldID) Schema(_ *Schema, result int) int { |
| return result |
| } |
| |
| func (findLastFieldID) Struct(_ StructType, fieldResults []int) int { |
| return max(fieldResults...) |
| } |
| |
| func (findLastFieldID) Field(field NestedField, fieldResult int) int { |
| return max(field.ID, fieldResult) |
| } |
| |
| func (findLastFieldID) List(field ListType, elemResult int) int { |
| return max(field.ElementID, elemResult) |
| } |
| |
| func (findLastFieldID) Map(field MapType, keyResult, valueResult int) int { |
| return max(field.KeyID, field.ValueID, keyResult, valueResult) |
| } |
| |
| func (findLastFieldID) Primitive(PrimitiveType) int { return 0 } |
| |
| // IndexParents generates an index of field IDs to their parent field |
| // IDs. Root fields are not indexed |
| func IndexParents(schema *Schema) (map[int]int, error) { |
| indexer := &indexParents{ |
| idToParent: make(map[int]int), |
| idStack: make([]int, 0), |
| } |
| |
| return Visit(schema, indexer) |
| } |
| |
| type indexParents struct { |
| idToParent map[int]int |
| idStack []int |
| } |
| |
| func (i *indexParents) BeforeField(field NestedField) { |
| i.idStack = append(i.idStack, field.ID) |
| } |
| |
| func (i *indexParents) AfterField(field NestedField) { |
| i.idStack = i.idStack[:len(i.idStack)-1] |
| } |
| |
| func (i *indexParents) Schema(schema *Schema, _ map[int]int) map[int]int { |
| return i.idToParent |
| } |
| |
| func (i *indexParents) Struct(st StructType, _ []map[int]int) map[int]int { |
| var parent int |
| stackLen := len(i.idStack) |
| if stackLen > 0 { |
| parent = i.idStack[stackLen-1] |
| for _, f := range st.FieldList { |
| i.idToParent[f.ID] = parent |
| } |
| } |
| |
| return i.idToParent |
| } |
| |
| func (i *indexParents) Field(NestedField, map[int]int) map[int]int { |
| return i.idToParent |
| } |
| |
| func (i *indexParents) List(list ListType, _ map[int]int) map[int]int { |
| i.idToParent[list.ElementID] = i.idStack[len(i.idStack)-1] |
| |
| return i.idToParent |
| } |
| |
| func (i *indexParents) Map(mapType MapType, _, _ map[int]int) map[int]int { |
| parent := i.idStack[len(i.idStack)-1] |
| i.idToParent[mapType.KeyID] = parent |
| i.idToParent[mapType.ValueID] = parent |
| |
| return i.idToParent |
| } |
| |
| func (i *indexParents) Primitive(PrimitiveType) map[int]int { |
| return i.idToParent |
| } |
| |
| type buildPosAccessors struct{} |
| |
| func (buildPosAccessors) Schema(_ *Schema, structResult map[int]accessor) map[int]accessor { |
| return structResult |
| } |
| |
| func (buildPosAccessors) Struct(st StructType, fieldResults []map[int]accessor) map[int]accessor { |
| result := map[int]accessor{} |
| for pos, f := range st.FieldList { |
| if innerMap := fieldResults[pos]; len(innerMap) != 0 { |
| for inner, acc := range innerMap { |
| acc := acc |
| result[inner] = accessor{pos: pos, inner: &acc} |
| } |
| } else { |
| result[f.ID] = accessor{pos: pos} |
| } |
| } |
| |
| return result |
| } |
| |
| func (buildPosAccessors) Field(_ NestedField, fieldResult map[int]accessor) map[int]accessor { |
| return fieldResult |
| } |
| |
| func (buildPosAccessors) List(ListType, map[int]accessor) map[int]accessor { |
| return map[int]accessor{} |
| } |
| |
| func (buildPosAccessors) Map(_ MapType, _, _ map[int]accessor) map[int]accessor { |
| return map[int]accessor{} |
| } |
| |
| func (buildPosAccessors) Primitive(PrimitiveType) map[int]accessor { |
| return map[int]accessor{} |
| } |
| |
| func buildAccessors(schema *Schema) (map[int]accessor, error) { |
| return Visit(schema, buildPosAccessors{}) |
| } |
| |
| type setFreshIDs struct { |
| oldIdToNew map[int]int |
| nextIDFunc func() int |
| } |
| |
| func (s *setFreshIDs) getAndInc(currentID int) int { |
| next := s.nextIDFunc() |
| s.oldIdToNew[currentID] = next |
| |
| return next |
| } |
| |
| func (s *setFreshIDs) Schema(_ *Schema, structResult func() Type) Type { |
| return structResult() |
| } |
| |
| func (s *setFreshIDs) Struct(st StructType, fieldResults []func() Type) Type { |
| newFields := make([]NestedField, len(st.FieldList)) |
| for idx, f := range st.FieldList { |
| newFields[idx] = NestedField{ |
| ID: s.getAndInc(f.ID), |
| Name: f.Name, |
| Type: fieldResults[idx](), |
| Doc: f.Doc, |
| Required: f.Required, |
| } |
| } |
| |
| return &StructType{FieldList: newFields} |
| } |
| |
| func (s *setFreshIDs) Field(_ NestedField, fieldResult func() Type) Type { |
| return fieldResult() |
| } |
| |
| func (s *setFreshIDs) List(list ListType, elemResult func() Type) Type { |
| elemID := s.getAndInc(list.ElementID) |
| |
| return &ListType{ |
| ElementID: elemID, |
| Element: elemResult(), |
| ElementRequired: list.ElementRequired, |
| } |
| } |
| |
| func (s *setFreshIDs) Map(mapType MapType, keyResult, valueResult func() Type) Type { |
| keyID := s.getAndInc(mapType.KeyID) |
| valueID := s.getAndInc(mapType.ValueID) |
| |
| return &MapType{ |
| KeyID: keyID, |
| ValueID: valueID, |
| KeyType: keyResult(), |
| ValueType: valueResult(), |
| ValueRequired: mapType.ValueRequired, |
| } |
| } |
| |
| func (s *setFreshIDs) Primitive(p PrimitiveType) Type { |
| return p |
| } |
| |
| // AssignFreshSchemaIDs creates a new schema with fresh field IDs for all of the |
| // fields in it. The nextID function is used to iteratively generate the ids, if |
| // it is nil then a simple incrementing counter is used starting at 1. |
| func AssignFreshSchemaIDs(sc *Schema, nextID func() int) (*Schema, error) { |
| if nextID == nil { |
| var id int = 0 |
| nextID = func() int { |
| id++ |
| |
| return id |
| } |
| } |
| visitor := &setFreshIDs{oldIdToNew: make(map[int]int), nextIDFunc: nextID} |
| outType, err := PreOrderVisit(sc, visitor) |
| if err != nil { |
| return nil, err |
| } |
| |
| fields := outType.(*StructType).FieldList |
| var newIdentifierIDs []int |
| if len(sc.IdentifierFieldIDs) != 0 { |
| newIdentifierIDs = make([]int, len(sc.IdentifierFieldIDs)) |
| for i, id := range sc.IdentifierFieldIDs { |
| newIdentifierIDs[i] = visitor.oldIdToNew[id] |
| } |
| } |
| |
| return NewSchemaWithIdentifiers(0, newIdentifierIDs, fields...), nil |
| } |
| |
| type SchemaWithPartnerVisitor[T, P any] interface { |
| Schema(sc *Schema, schemaPartner P, structResult T) T |
| Struct(st StructType, structPartner P, fieldResults []T) T |
| Field(field NestedField, fieldPartner P, fieldResult T) T |
| List(l ListType, listPartner P, elemResult T) T |
| Map(m MapType, mapPartner P, keyResult, valResult T) T |
| Primitive(p PrimitiveType, primitivePartner P) T |
| } |
| |
| type PartnerAccessor[P any] interface { |
| SchemaPartner(P) P |
| FieldPartner(partnerStruct P, fieldID int, fieldName string) P |
| ListElementPartner(P) P |
| MapKeyPartner(P) P |
| MapValuePartner(P) P |
| } |
| |
| func VisitSchemaWithPartner[T, P any](sc *Schema, partner P, visitor SchemaWithPartnerVisitor[T, P], accessor PartnerAccessor[P]) (res T, err error) { |
| if sc == nil { |
| err = fmt.Errorf("%w: cannot visit nil schema", ErrInvalidArgument) |
| |
| return res, err |
| } |
| |
| if visitor == nil || accessor == nil { |
| err = fmt.Errorf("%w: cannot visit with nil visitor or accessor", ErrInvalidArgument) |
| |
| return res, err |
| } |
| |
| defer func() { |
| if r := recover(); r != nil { |
| switch e := r.(type) { |
| case string: |
| err = fmt.Errorf("error encountered during schema visitor: %s", e) |
| case error: |
| err = fmt.Errorf("error encountered during schema visitor: %w", e) |
| } |
| } |
| }() |
| |
| structPartner := accessor.SchemaPartner(partner) |
| |
| return visitor.Schema(sc, partner, visitStructWithPartner(sc.AsStruct(), structPartner, visitor, accessor)), nil |
| } |
| |
| func visitStructWithPartner[T, P any](st StructType, partner P, visitor SchemaWithPartnerVisitor[T, P], accessor PartnerAccessor[P]) T { |
| type ( |
| beforeField interface { |
| BeforeField(NestedField, P) |
| } |
| afterField interface { |
| AfterField(NestedField, P) |
| } |
| ) |
| |
| bf, _ := visitor.(beforeField) |
| af, _ := visitor.(afterField) |
| |
| fieldResults := make([]T, len(st.FieldList)) |
| |
| for i, f := range st.FieldList { |
| fieldPartner := accessor.FieldPartner(partner, f.ID, f.Name) |
| if bf != nil { |
| bf.BeforeField(f, fieldPartner) |
| } |
| fieldResult := visitTypeWithPartner(f.Type, fieldPartner, visitor, accessor) |
| fieldResults[i] = visitor.Field(f, fieldPartner, fieldResult) |
| if af != nil { |
| af.AfterField(f, fieldPartner) |
| } |
| } |
| |
| return visitor.Struct(st, partner, fieldResults) |
| } |
| |
| func visitListWithPartner[T, P any](listType ListType, partner P, visitor SchemaWithPartnerVisitor[T, P], accessor PartnerAccessor[P]) T { |
| type ( |
| beforeListElem interface { |
| BeforeListElement(NestedField, P) |
| } |
| afterListElem interface { |
| AfterListElement(NestedField, P) |
| } |
| ) |
| |
| elemPartner := accessor.ListElementPartner(partner) |
| if ble, ok := visitor.(beforeListElem); ok { |
| ble.BeforeListElement(listType.ElementField(), elemPartner) |
| } |
| elemResult := visitTypeWithPartner(listType.Element, elemPartner, visitor, accessor) |
| if ale, ok := visitor.(afterListElem); ok { |
| ale.AfterListElement(listType.ElementField(), elemPartner) |
| } |
| |
| return visitor.List(listType, partner, elemResult) |
| } |
| |
| func visitMapWithPartner[T, P any](m MapType, partner P, visitor SchemaWithPartnerVisitor[T, P], accessor PartnerAccessor[P]) T { |
| type ( |
| beforeMapKey interface { |
| BeforeMapKey(NestedField, P) |
| } |
| afterMapKey interface { |
| AfterMapKey(NestedField, P) |
| } |
| |
| beforeMapValue interface { |
| BeforeMapValue(NestedField, P) |
| } |
| afterMapValue interface { |
| AfterMapValue(NestedField, P) |
| } |
| ) |
| |
| keyPartner := accessor.MapKeyPartner(partner) |
| if bmk, ok := visitor.(beforeMapKey); ok { |
| bmk.BeforeMapKey(m.KeyField(), keyPartner) |
| } |
| keyResult := visitTypeWithPartner(m.KeyType, keyPartner, visitor, accessor) |
| if amk, ok := visitor.(afterMapKey); ok { |
| amk.AfterMapKey(m.KeyField(), keyPartner) |
| } |
| |
| valPartner := accessor.MapValuePartner(partner) |
| if bmv, ok := visitor.(beforeMapValue); ok { |
| bmv.BeforeMapValue(m.ValueField(), valPartner) |
| } |
| valResult := visitTypeWithPartner(m.ValueType, valPartner, visitor, accessor) |
| if amv, ok := visitor.(afterMapValue); ok { |
| amv.AfterMapValue(m.ValueField(), valPartner) |
| } |
| |
| return visitor.Map(m, partner, keyResult, valResult) |
| } |
| |
| func visitTypeWithPartner[T, P any](t Type, fieldPartner P, visitor SchemaWithPartnerVisitor[T, P], accessor PartnerAccessor[P]) T { |
| switch t := t.(type) { |
| case *ListType: |
| return visitListWithPartner(*t, fieldPartner, visitor, accessor) |
| case *StructType: |
| return visitStructWithPartner(*t, fieldPartner, visitor, accessor) |
| case *MapType: |
| return visitMapWithPartner(*t, fieldPartner, visitor, accessor) |
| default: |
| return visitor.Primitive(t.(PrimitiveType), fieldPartner) |
| } |
| } |
| |
| func makeCompatibleName(n string) string { |
| if !validAvroName(n) { |
| return sanitizeName(n) |
| } |
| |
| return n |
| } |
| |
| func validAvroName(n string) bool { |
| if len(n) == 0 { |
| panic("cannot validate empty name") |
| } |
| |
| if !unicode.IsLetter(rune(n[0])) && n[0] != '_' { |
| return false |
| } |
| |
| for _, r := range n[1:] { |
| if !unicode.In(r, unicode.Number, unicode.Letter) && r != '_' { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| func sanitize(r rune) string { |
| if unicode.IsDigit(r) { |
| return "_" + string(r) |
| } |
| |
| return fmt.Sprintf("_x%X", r) |
| } |
| |
| func sanitizeName(n string) string { |
| var b strings.Builder |
| b.Grow(len(n)) |
| |
| first := n[0] |
| if !(unicode.IsLetter(rune(first)) || first == '_') { |
| b.WriteString(sanitize(rune(first))) |
| } else { |
| b.WriteByte(first) |
| } |
| |
| for _, r := range n[1:] { |
| if !unicode.In(r, unicode.Number, unicode.Letter) && r != '_' { |
| b.WriteString(sanitize(r)) |
| } else { |
| b.WriteRune(r) |
| } |
| } |
| |
| return b.String() |
| } |
| |
| func SanitizeColumnNames(sc *Schema) (*Schema, error) { |
| result, err := Visit(sc, sanitizeColumnNameVisitor{}) |
| if err != nil { |
| return nil, err |
| } |
| |
| return NewSchemaWithIdentifiers(sc.ID, sc.IdentifierFieldIDs, |
| result.Type.(*StructType).FieldList...), nil |
| } |
| |
| type sanitizeColumnNameVisitor struct{} |
| |
| func (sanitizeColumnNameVisitor) Schema(_ *Schema, structResult NestedField) NestedField { |
| return structResult |
| } |
| |
| func (sanitizeColumnNameVisitor) Field(field NestedField, fieldResult NestedField) NestedField { |
| field.Type = fieldResult.Type |
| field.Name = makeCompatibleName(field.Name) |
| |
| return field |
| } |
| |
| func (sanitizeColumnNameVisitor) Struct(_ StructType, fieldResults []NestedField) NestedField { |
| return NestedField{Type: &StructType{FieldList: fieldResults}} |
| } |
| |
| func (sanitizeColumnNameVisitor) List(list ListType, elemResult NestedField) NestedField { |
| list.Element = elemResult.Type |
| |
| return NestedField{Type: &list} |
| } |
| |
| func (sanitizeColumnNameVisitor) Map(mapType MapType, keyResult, valueResult NestedField) NestedField { |
| mapType.KeyType = keyResult.Type |
| mapType.ValueType = valueResult.Type |
| |
| return NestedField{Type: &mapType} |
| } |
| |
| func (sanitizeColumnNameVisitor) Primitive(p PrimitiveType) NestedField { |
| return NestedField{Type: p} |
| } |