Repository: ovn-org/libovsdb
Pull request #121 (2021-05-21 09:38:22)

ovsdb: Make it easier to access optional BaseType fields
Signed-off-by: Dave Tucker <dave@dtucker.co.uk>

coveralls: ## Pull Request Test Coverage Report for [Build 863408289](https://coveralls.io/builds/39885194)

* **18** of **60**   **(30.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-1.2%**) to **71.28%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [ovsdb/schema.go](https://coveralls.io/builds/39885194/source?filename=ovsdb%2Fschema.go#L182) | 18 | 60 | 30.0%
<!-- | **Total:** | **18** | **60** | **30.0%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/39885194/badge)](https://coveralls.io/builds/39885194) |
| :-- | --: |
| Change from base [Build 859723497](https://coveralls.io/builds/39847414): |  -1.2% |
| Covered Lines: | 1777 |
| Relevant Lines: | 2493 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/client/api.go b/client/api.go
index 4ffc462..fcfac08 100644
--- a/client/api.go
+++ b/client/api.go
@@ -61,7 +61,7 @@ type ConditionalAPI interface {
 	// Mutate returns the operations needed to perform the mutation specified
 	// By the model and the list of Mutation objects
 	// Depending on the Condition, it might return one or many operations
-	Mutate(Model, ...Mutation) ([]ovsdb.Operation, error)
+	Mutate(Model, []Mutation) ([]ovsdb.Operation, error)
 
 	// Update returns the operations needed to update any number of rows according
 	// to the data in the given model.
@@ -325,14 +325,10 @@ func (a api) Create(models ...Model) ([]ovsdb.Operation, error) {
 }
 
 // Mutate returns the operations needed to transform the one Model into another one
-func (a api) Mutate(model Model, mutationObjs ...Mutation) ([]ovsdb.Operation, error) {
+func (a api) Mutate(model Model, mutationObjs []Mutation) ([]ovsdb.Operation, error) {
 	var mutations []interface{}
 	var operations []ovsdb.Operation
 
-	if len(mutationObjs) < 1 {
-		return nil, fmt.Errorf("At least one Mutation must be provided")
-	}
-
 	tableName := a.cache.dbModel.FindTable(reflect.ValueOf(model).Type())
 	table := a.cache.orm.schema.Table(tableName)
 	if table == nil {
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index abc0ea9..0251f70 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -107,10 +107,12 @@ func deleteBridge(ovs *client.OvsdbClient, bridge *ormBridge) {
 		UUID: rootUUID,
 	}
 
-	mutateOp, err := ovs.Where(&ovsRow).Mutate(&ovsRow, client.Mutation{
-		Field:   &ovsRow.Bridges,
-		Mutator: "delete",
-		Value:   []string{bridge.UUID},
+	mutateOp, err := ovs.Where(&ovsRow).Mutate(&ovsRow, []client.Mutation{
+		{
+			Field:   &ovsRow.Bridges,
+			Mutator: "delete",
+			Value:   []string{bridge.UUID},
+		},
 	})
 	if err != nil {
 		log.Fatal(err)
@@ -143,10 +145,12 @@ func createBridge(ovs *client.OvsdbClient, iter int) {
 		log.Fatal(err)
 	}
 	ovsRow := ormOvs{}
-	mutateOp, err := ovs.Where(&ormOvs{UUID: rootUUID}).Mutate(&ovsRow, client.Mutation{
-		Field:   &ovsRow.Bridges,
-		Mutator: "insert",
-		Value:   []string{bridge.UUID},
+	mutateOp, err := ovs.Where(&ormOvs{UUID: rootUUID}).Mutate(&ovsRow, []client.Mutation{
+		{
+			Field:   &ovsRow.Bridges,
+			Mutator: "insert",
+			Value:   []string{bridge.UUID},
+		},
 	})
 	if err != nil {
 		log.Fatal(err)
diff --git a/example/play_with_ovs/play_with_ovs.go b/example/play_with_ovs/play_with_ovs.go
index 8933e80..63e5e03 100644
--- a/example/play_with_ovs/play_with_ovs.go
+++ b/example/play_with_ovs/play_with_ovs.go
@@ -72,11 +72,12 @@ func createBridge(ovs *client.OvsdbClient, bridgeName string) {
 	ovsRow := ormOvs{
 		UUID: rootUUID,
 	}
-	mutateOps, err := ovs.Where(&ovsRow).Mutate(&ovsRow, client.Mutation{
-		Field:   &ovsRow.Bridges,
-		Mutator: "insert",
-		Value:   []string{bridge.UUID},
-	})
+	mutateOps, err := ovs.Where(&ovsRow).Mutate(&ovsRow, []client.Mutation{
+		{
+			Field:   &ovsRow.Bridges,
+			Mutator: "insert",
+			Value:   []string{bridge.UUID},
+		}})
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/ovsdb/schema.go b/ovsdb/schema.go
index d748bb5..fd988b5 100644
--- a/ovsdb/schema.go
+++ b/ovsdb/schema.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math"
 	"os"
 	"strings"
 )
@@ -163,20 +164,120 @@ const (
 
 // BaseType is a base-type structure as per RFC7047
 type BaseType struct {
-	Type       string        `json:"type"`
-	Enum       []interface{} `json:"-"`
-	MinReal    *float64      `json:"minReal,omitempty"`
-	MaxReal    *float64      `json:"maxReal,omitempty"`
-	MinInteger *int          `json:"minInteger,omitempty"`
-	MaxInteger *int          `json:"maxInteger,omitempty"`
-	MinLength  *int          `json:"minLength,omitempty"`
-	MaxLength  *int          `json:"maxLength,omitempty"`
-	RefTable   *string       `json:"refTable,omitempty"`
-	RefType    *RefType      `json:"refType,omitempty"`
+	Type       string
+	Enum       []interface{}
+	minReal    *float64
+	maxReal    *float64
+	minInteger *int
+	maxInteger *int
+	minLength  *int
+	maxLength  *int
+	refTable   *string
+	refType    *RefType
 }
 
 func (b *BaseType) simpleAtomic() bool {
-	return isAtomicType(b.Type) && b.Enum == nil && b.MinReal == nil && b.MaxReal == nil && b.MinInteger == nil && b.MaxInteger == nil && b.MinLength == nil && b.MaxLength == nil && b.RefTable == nil && b.RefType == nil
+	return isAtomicType(b.Type) && b.Enum == nil && b.minReal == nil && b.maxReal == nil && b.minInteger == nil && b.maxInteger == nil && b.minLength == nil && b.maxLength == nil && b.refTable == nil && b.refType == nil
+}
+
+// MinReal returns the minimum real value
+// RFC7047 does not define a default, but we assume this to be
+// the smallest non zero value a float64 could hold
+func (b *BaseType) MinReal() (float64, error) {
+	if b.Type != TypeReal {
+		return 0, fmt.Errorf("%s is not a real", b.Type)
+	}
+	if b.minReal != nil {
+		return *b.minReal, nil
+	}
+	return math.SmallestNonzeroFloat64, nil
+}
+
+// MaxReal returns the maximum real value
+// RFC7047 does not define a default, but this would be the maximum
+// value held by a float64
+func (b *BaseType) MaxReal() (float64, error) {
+	if b.Type != TypeReal {
+		return 0, fmt.Errorf("%s is not a real", b.Type)
+	}
+	if b.maxReal != nil {
+		return *b.maxReal, nil
+	}
+	return math.MaxFloat64, nil
+}
+
+// MinInteger returns the minimum real value
+// RFC7047 species the minimum to be -2^63
+func (b *BaseType) MinInteger() (int, error) {
+	if b.Type != TypeInteger {
+		return 0, fmt.Errorf("%s is not an integer", b.Type)
+	}
+	if b.minInteger != nil {
+		return *b.minInteger, nil
+	}
+	return int(math.Pow(-2, 63)), nil
+}
+
+// MinInteger returns the minimum real value
+// RFC7047 species the minimum to be 2^63-1
+func (b *BaseType) MaxInteger() (int, error) {
+	if b.Type != TypeInteger {
+		return 0, fmt.Errorf("%s is not an integer", b.Type)
+	}
+	if b.maxInteger != nil {
+		return *b.maxInteger, nil
+	}
+	return int(math.Pow(2, 63)) - 1, nil
+}
+
+// MinLength returns the minimum string length
+// RFC7047 doesn't specify a default, but we assume
+// that it must be >= 0
+func (b *BaseType) MinLength() (int, error) {
+	if b.Type != TypeString {
+		return 0, fmt.Errorf("%s is not an string", b.Type)
+	}
+	if b.minLength != nil {
+		return *b.minLength, nil
+	}
+	return 0, nil
+}
+
+// MaxLength returns the maximum string length
+// RFC7047 doesn't specify a default, but we assume
+// that it must 2^63-1
+func (b *BaseType) MaxLength() (int, error) {
+	if b.Type != TypeString {
+		return 0, fmt.Errorf("%s is not an string", b.Type)
+	}
+	if b.maxLength != nil {
+		return *b.maxLength, nil
+	}
+	return int(math.Pow(2, 63)) - 1, nil
+}
+
+// RefTable returns the table to which a UUID type refers
+// It will return an empry string if not set
+func (b *BaseType) RefTable() (string, error) {
+	if b.Type != TypeUUID {
+		return "", fmt.Errorf("%s is not a uuid", b.Type)
+	}
+	if b.refTable != nil {
+		return *b.refTable, nil
+	}
+	return "", nil
+}
+
+// RefType returns the reference type for a UUID field
+// RFC7047 infers the RefType is strong if omitted
+func (b *BaseType) RefType() (RefType, error) {
+	if b.Type != TypeUUID {
+		return "", fmt.Errorf("%s is not a uuid", b.Type)
+	}
+	if b.refType != nil {
+		return *b.refType, nil
+	}
+	return Strong, nil
 }
 
 // UnmarshalJSON unmarshalls a json-formatted base type
@@ -224,18 +325,18 @@ func (b *BaseType) UnmarshalJSON(data []byte) error {
 		}
 	}
 	b.Type = bt.Type
-	b.MinReal = bt.MinReal
-	b.MaxReal = bt.MaxReal
-	b.MinInteger = bt.MinInteger
-	b.MaxInteger = bt.MaxInteger
-	b.MinLength = bt.MaxLength
-	b.MaxLength = bt.MaxLength
-	b.RefTable = bt.RefTable
-	b.RefType = bt.RefType
+	b.minReal = bt.MinReal
+	b.maxReal = bt.MaxReal
+	b.minInteger = bt.MinInteger
+	b.maxInteger = bt.MaxInteger
+	b.minLength = bt.MaxLength
+	b.maxLength = bt.MaxLength
+	b.refTable = bt.RefTable
+	b.refType = bt.RefType
 	return nil
 }
 
-// MarshalJSON marshalls a base type to JSON
+// MarshalJSON marshals a base type to JSON
 func (b BaseType) MarshalJSON() ([]byte, error) {
 	j := struct {
 		Type       string   `json:"type,omitempty"`
@@ -250,14 +351,14 @@ func (b BaseType) MarshalJSON() ([]byte, error) {
 		RefType    *RefType `json:"refType,omitempty"`
 	}{
 		Type:       b.Type,
-		MinReal:    b.MinReal,
-		MaxReal:    b.MaxReal,
-		MinInteger: b.MinInteger,
-		MaxInteger: b.MaxInteger,
-		MinLength:  b.MaxLength,
-		MaxLength:  b.MaxLength,
-		RefTable:   b.RefTable,
-		RefType:    b.RefType,
+		MinReal:    b.minReal,
+		MaxReal:    b.maxReal,
+		MinInteger: b.minInteger,
+		MaxInteger: b.maxInteger,
+		MinLength:  b.maxLength,
+		MaxLength:  b.maxLength,
+		RefTable:   b.refTable,
+		RefType:    b.refType,
 	}
 	if len(b.Enum) > 0 {
 		var err error
@@ -467,7 +568,13 @@ func (column *ColumnSchema) String() string {
 		typeStr = string(column.Type)
 	case TypeUUID:
 		if column.TypeObj != nil && column.TypeObj.Key != nil {
-			typeStr = fmt.Sprintf("uuid [%s (%s)]", *column.TypeObj.Key.RefTable, *column.TypeObj.Key.RefType)
+			// ignore err as we've already asserted this is a uuid
+			reftable, _ := column.TypeObj.Key.RefTable()
+			reftype := ""
+			if s, err := column.TypeObj.Key.RefType(); err != nil {
+				reftype = s
+			}
+			typeStr = fmt.Sprintf("uuid [%s (%s)]", reftable, reftype)
 		} else {
 			typeStr = "uuid"
 		}
@@ -479,7 +586,13 @@ func (column *ColumnSchema) String() string {
 	case TypeSet:
 		var keyStr string
 		if column.TypeObj.Key.Type == TypeUUID {
-			keyStr = fmt.Sprintf(" [%s (%s)]", *column.TypeObj.Key.RefTable, *column.TypeObj.Key.RefType)
+			// ignore err as we've already asserted this is a uuid
+			reftable, _ := column.TypeObj.Key.RefTable()
+			reftype := ""
+			if s, err := column.TypeObj.Key.RefType(); err != nil {
+				reftype = s
+			}
+			keyStr = fmt.Sprintf(" [%s (%s)]", reftable, reftype)
 		} else {
 			keyStr = string(column.TypeObj.Key.Type)
 		}


Test patch:
diff --git a/client/api_test.go b/client/api_test.go
index cf4b678..087733c 100644
--- a/client/api_test.go
+++ b/client/api_test.go
@@ -745,22 +745,12 @@ func TestAPIMutate(t *testing.T) {
 			},
 			err: false,
 		},
-		{
-			name: "No mutations should error",
-			condition: func(a API) ConditionalAPI {
-				return a.WhereCache(func(lsp *testLogicalSwitchPort) bool {
-					return lsp.Type == "someType"
-				})
-			},
-			mutations: []Mutation{},
-			err:       true,
-		},
 	}
 	for _, tt := range test {
 		t.Run(fmt.Sprintf("ApiMutate: %s", tt.name), func(t *testing.T) {
 			api := newAPI(cache)
 			cond := tt.condition(api)
-			ops, err := cond.Mutate(&testObj, tt.mutations...)
+			ops, err := cond.Mutate(&testObj, tt.mutations)
 			if tt.err {
 				assert.NotNil(t, err)
 			} else {
diff --git a/client/ovs_integration_test.go b/client/ovs_integration_test.go
index a086d4d..112574d 100644
--- a/client/ovs_integration_test.go
+++ b/client/ovs_integration_test.go
@@ -192,10 +192,12 @@ func TestInsertTransactIntegration(t *testing.T) {
 	// Inserting a Bridge row in Bridge table requires mutating the open_vswitch table.
 	ovsRow := ovsType{}
 	mutateOp, err := ovs.WhereCache(func(*ovsType) bool { return true }).
-		Mutate(&ovsRow, Mutation{
-			Field:   &ovsRow.Bridges,
-			Mutator: ovsdb.MutateOperationInsert,
-			Value:   []string{namedUUID},
+		Mutate(&ovsRow, []Mutation{
+			{
+				Field:   &ovsRow.Bridges,
+				Mutator: ovsdb.MutateOperationInsert,
+				Value:   []string{namedUUID},
+			},
 		})
 	assert.Nil(t, err)
 
@@ -238,10 +240,12 @@ func TestDeleteTransactIntegration(t *testing.T) {
 
 	ovsRow := ovsType{}
 	mutateOp, err := ovs.WhereCache(func(*ovsType) bool { return true }).
-		Mutate(&ovsRow, Mutation{
-			Field:   &ovsRow.Bridges,
-			Mutator: ovsdb.MutateOperationDelete,
-			Value:   []string{bridgeUUID},
+		Mutate(&ovsRow, []Mutation{
+			{
+				Field:   &ovsRow.Bridges,
+				Mutator: ovsdb.MutateOperationDelete,
+				Value:   []string{bridgeUUID},
+			},
 		})
 
 	assert.Nil(t, err)
@@ -540,10 +544,12 @@ func TestInsertDuplicateTransactIntegration(t *testing.T) {
 	// Inserting a Bridge row in Bridge table requires mutating the open_vswitch table.
 	ovsRow := ovsType{}
 	mutateOp, err := ovs.WhereCache(func(*ovsType) bool { return true }).
-		Mutate(&ovsRow, Mutation{
-			Field:   &ovsRow.Bridges,
-			Mutator: ovsdb.MutateOperationInsert,
-			Value:   []string{namedUUID},
+		Mutate(&ovsRow, []Mutation{
+			{
+				Field:   &ovsRow.Bridges,
+				Mutator: ovsdb.MutateOperationInsert,
+				Value:   []string{namedUUID},
+			},
 		})
 	assert.Nil(t, err)
 
diff --git a/ovsdb/schema_test.go b/ovsdb/schema_test.go
index 98dcedd..ed8919d 100644
--- a/ovsdb/schema_test.go
+++ b/ovsdb/schema_test.go
@@ -3,6 +3,7 @@ package ovsdb
 import (
 	"encoding/json"
 	"fmt"
+	"math"
 	"reflect"
 	"testing"
 
@@ -455,7 +456,7 @@ func TestBaseTypeMarshalUnmarshalJSON(t *testing.T) {
 		{
 			"uuid",
 			[]byte(`{"type": "uuid", "refTable": "Datapath", "refType": "strong"}`),
-			BaseType{Type: TypeUUID, RefTable: &datapath, RefType: &strong},
+			BaseType{Type: TypeUUID, refTable: &datapath, refType: &strong},
 			[]byte(`{"type": "uuid", "refTable": "Datapath", "refType": "strong"}`),
 			false,
 		},
@@ -469,7 +470,7 @@ func TestBaseTypeMarshalUnmarshalJSON(t *testing.T) {
 		{
 			"int with min and max",
 			[]byte(`{"type":"integer","minInteger":0,"maxInteger": 4294967295}`),
-			BaseType{Type: TypeInteger, MinInteger: &zero, MaxInteger: &max},
+			BaseType{Type: TypeInteger, minInteger: &zero, maxInteger: &max},
 			[]byte(`{"type":"integer","minInteger":0,"maxInteger": 4294967295}`),
 			false,
 		},
@@ -626,7 +627,7 @@ func TestColumnSchemaMarshalUnmarshalJSON(t *testing.T) {
 				Type: TypeMap,
 				TypeObj: &ColumnType{
 					Key:   &BaseType{Type: TypeString},
-					Value: &BaseType{Type: TypeUUID, RefTable: &datapath},
+					Value: &BaseType{Type: TypeUUID, refTable: &datapath},
 					min:   &zero,
 					max:   &unlimted,
 				},
@@ -639,7 +640,7 @@ func TestColumnSchemaMarshalUnmarshalJSON(t *testing.T) {
 			ColumnSchema{
 				Type: TypeSet,
 				TypeObj: &ColumnType{
-					Key: &BaseType{Type: TypeUUID, RefTable: &datapath},
+					Key: &BaseType{Type: TypeUUID, refTable: &datapath},
 					min: &zero,
 					max: &unlimted,
 				}},
@@ -673,10 +674,330 @@ func TestColumnSchemaMarshalUnmarshalJSON(t *testing.T) {
 	}
 }
 
-func TestBaseTypesimpleAtomic(t *testing.T) {
+func TestBaseTypeSimpleAtomic(t *testing.T) {
 	b := BaseType{Type: TypeString}
 	assert.True(t, b.simpleAtomic())
 	max := 1024
-	b1 := BaseType{Type: TypeInteger, MaxInteger: &max}
+	b1 := BaseType{Type: TypeInteger, maxInteger: &max}
 	assert.False(t, b1.simpleAtomic())
 }
+
+func TestBaseTypeMinReal(t *testing.T) {
+	value := float64(1024)
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    float64
+		wantErr bool
+	}{
+		{
+			"not a real",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeReal},
+			math.SmallestNonzeroFloat64,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeReal, minReal: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MinReal()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeMaxReal(t *testing.T) {
+	value := float64(1024)
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    float64
+		wantErr bool
+	}{
+		{
+			"not a real",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeReal},
+			math.MaxFloat64,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeReal, maxReal: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MaxReal()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeMinInteger(t *testing.T) {
+	value := 1024
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    int
+		wantErr bool
+	}{
+		{
+			"not an int",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeInteger},
+			int(math.Pow(-2, 63)),
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeInteger, minInteger: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MinInteger()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeMaxInteger(t *testing.T) {
+	value := 1024
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    int
+		wantErr bool
+	}{
+		{
+			"not an int",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeInteger},
+			int(math.Pow(2, 63)) - 1,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeInteger, maxInteger: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MaxInteger()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeMinLength(t *testing.T) {
+	value := 12
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    int
+		wantErr bool
+	}{
+		{
+			"not a string",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeString},
+			0,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeString, minLength: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MinLength()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeMaxLength(t *testing.T) {
+	value := 1024
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    int
+		wantErr bool
+	}{
+		{
+			"not a string",
+			&BaseType{Type: TypeUUID},
+			0,
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeString},
+			int(math.Pow(2, 63)) - 1,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeString, maxLength: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.MaxLength()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeRefTable(t *testing.T) {
+	value := "Bridge"
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    string
+		wantErr bool
+	}{
+		{
+			"not a uuid",
+			&BaseType{Type: TypeString},
+			"",
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeUUID},
+			"",
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeUUID, refTable: &value},
+			value,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.RefTable()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestBaseTypeRefType(t *testing.T) {
+	value := "weak"
+	tests := []struct {
+		name    string
+		bt      *BaseType
+		want    RefType
+		wantErr bool
+	}{
+		{
+			"not a uuid",
+			&BaseType{Type: TypeString},
+			"",
+			true,
+		},
+		{
+			"nil",
+			&BaseType{Type: TypeUUID},
+			Strong,
+			false,
+		},
+		{
+			"set",
+			&BaseType{Type: TypeUUID, refType: &value},
+			Weak,
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.bt.RefType()
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}