Skip to content

Commit 94fd061

Browse files
committed
mapper: Add compatility mode
Allow the user to set a Compatibility flag on the DatabaseModelRequest. When that flag is set, the verification phase will not fail if a column is missing on the schema or has a different type. Instead it will just skip the column. Same goes for missing tables, they will just be skipped. Signed-off-by: Adrian Moreno <[email protected]>
1 parent f4daab4 commit 94fd061

File tree

9 files changed

+434
-50
lines changed

9 files changed

+434
-50
lines changed

cache/cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ func (t *TableCache) ApplyModifications(tableName string, base model.Model, upda
797797
return err
798798
}
799799
for k, v := range update {
800-
if k == "_uuid" {
800+
if k == "_uuid" || !t.dbModel.HasColumn(tableName, k) {
801801
continue
802802
}
803803

cmd/stress/stress.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func main() {
250250

251251
var err error
252252
dbModelReq, err = model.NewDatabaseModelRequest("Open_vSwitch", map[string]model.Model{"Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}})
253+
dbModelReq.SetCompatibility(true)
253254
if err != nil {
254255
log.Fatal(err)
255256
}

mapper/info.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mapper
22

33
import (
44
"fmt"
5+
"log"
56
"reflect"
67

78
"github.com/ovn-org/libovsdb/ovsdb"
@@ -25,13 +26,13 @@ type Metadata struct {
2526
func (i *Info) FieldByColumn(column string) (interface{}, error) {
2627
fieldName, ok := i.Metadata.Fields[column]
2728
if !ok {
28-
return nil, fmt.Errorf("FieldByColumn: column %s not found in orm info", column)
29+
return nil, fmt.Errorf("FieldByColumn: column %s not found in mapper info", column)
2930
}
3031
return reflect.ValueOf(i.Obj).Elem().FieldByName(fieldName).Interface(), nil
3132
}
3233

33-
// FieldByColumn returns the field value that corresponds to a column
34-
func (i *Info) hasColumn(column string) bool {
34+
// HasColumn returns whether the column exists in the Metadata fields
35+
func (i *Info) HasColumn(column string) bool {
3536
_, ok := i.Metadata.Fields[column]
3637
return ok
3738
}
@@ -40,7 +41,7 @@ func (i *Info) hasColumn(column string) bool {
4041
func (i *Info) SetField(column string, value interface{}) error {
4142
fieldName, ok := i.Metadata.Fields[column]
4243
if !ok {
43-
return fmt.Errorf("SetField: column %s not found in orm info", column)
44+
return fmt.Errorf("SetField: column %s not found in mapper info", column)
4445
}
4546
fieldValue := reflect.ValueOf(i.Obj).Elem().FieldByName(fieldName)
4647

@@ -64,12 +65,12 @@ func (i *Info) ColumnByPtr(fieldPtr interface{}) (string, error) {
6465
if objType.Field(j).Offset == offset {
6566
column := objType.Field(j).Tag.Get("ovsdb")
6667
if _, ok := i.Metadata.Fields[column]; !ok {
67-
return "", fmt.Errorf("field does not have orm column information")
68+
return "", fmt.Errorf("field does not have mapper column information")
6869
}
6970
return column, nil
7071
}
7172
}
72-
return "", fmt.Errorf("field pointer does not correspond to orm struct")
73+
return "", fmt.Errorf("field pointer does not correspond to mapper struct")
7374
}
7475

7576
// getValidIndexes inspects the object and returns the a list of indexes (set of columns) for witch
@@ -85,7 +86,7 @@ func (i *Info) getValidIndexes() ([][]string, error) {
8586
OUTER:
8687
for _, idx := range possibleIndexes {
8788
for _, col := range idx {
88-
if !i.hasColumn(col) {
89+
if !i.HasColumn(col) {
8990
continue OUTER
9091
}
9192
columnSchema := i.Metadata.TableSchema.Column(col)
@@ -106,7 +107,7 @@ OUTER:
106107
}
107108

108109
// NewInfo creates a MapperInfo structure around an object based on a given table schema
109-
func NewInfo(tableName string, table *ovsdb.TableSchema, obj interface{}) (*Info, error) {
110+
func NewInfo(tableName string, table *ovsdb.TableSchema, obj interface{}, compat bool) (*Info, error) {
110111
objPtrVal := reflect.ValueOf(obj)
111112
if objPtrVal.Type().Kind() != reflect.Ptr {
112113
return nil, ovsdb.NewErrWrongType("NewMapperInfo", "pointer to a struct", obj)
@@ -127,25 +128,35 @@ func NewInfo(tableName string, table *ovsdb.TableSchema, obj interface{}) (*Info
127128
}
128129
column := table.Column(colName)
129130
if column == nil {
130-
return nil, &ErrMapper{
131+
err := &ErrMapper{
131132
objType: objType.String(),
132133
field: field.Name,
133134
fieldType: field.Type.String(),
134135
fieldTag: colName,
135136
reason: "Column does not exist in schema",
136137
}
138+
if compat {
139+
log.Printf("Warning: %s. Skipping", err.Error())
140+
continue
141+
}
142+
return nil, err
137143
}
138144

139145
// Perform schema-based type checking
140146
expType := ovsdb.NativeType(column)
141147
if expType != field.Type {
142-
return nil, &ErrMapper{
148+
err := &ErrMapper{
143149
objType: objType.String(),
144150
field: field.Name,
145151
fieldType: field.Type.String(),
146152
fieldTag: colName,
147153
reason: fmt.Sprintf("Wrong type, column expects %s", expType),
148154
}
155+
if compat {
156+
log.Printf("Warning: %s. Skipping", err.Error())
157+
continue
158+
}
159+
return nil, err
149160
}
150161
fields[colName] = field.Name
151162
}

mapper/info_test.go

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,77 @@ func TestNewMapperInfo(t *testing.T) {
3939
table []byte
4040
obj interface{}
4141
expectedCols []string
42+
compat bool
4243
err bool
4344
}
4445
tests := []test{
4546
{
46-
name: "no_orm",
47+
name: "Info from object without tags should return no columns",
4748
table: sampleTable,
4849
obj: &struct {
4950
foo string
5051
bar int
5152
}{},
52-
err: false,
53+
expectedCols: []string{},
54+
compat: false,
55+
err: false,
56+
},
57+
{
58+
name: "Valid model should contain columns",
59+
table: sampleTable,
60+
obj: &struct {
61+
Foo string `ovsdb:"aString"`
62+
Bar int `ovsdb:"aInteger"`
63+
}{},
64+
expectedCols: []string{"aString", "aInteger"},
65+
compat: true,
66+
err: false,
67+
},
68+
{
69+
name: "Extra column no compat should error",
70+
table: sampleTable,
71+
obj: &struct {
72+
Foo string `ovsdb:"aString"`
73+
Bar int `ovsdb:"aInteger"`
74+
Baz int `ovsdb:"aNonExistingCol"`
75+
}{},
76+
expectedCols: []string{"aString", "aInteger"},
77+
compat: false,
78+
err: true,
79+
},
80+
{
81+
name: "Extra column compat should not error",
82+
table: sampleTable,
83+
obj: &struct {
84+
Foo string `ovsdb:"aString"`
85+
Bar int `ovsdb:"aInteger"`
86+
Baz int `ovsdb:"aNonExistingCol"`
87+
}{},
88+
expectedCols: []string{"aString", "aInteger"},
89+
compat: true,
90+
err: false,
91+
},
92+
{
93+
name: "Different column typ no compat should error",
94+
table: sampleTable,
95+
obj: &struct {
96+
Foo string `ovsdb:"aString"`
97+
Bar string `ovsdb:"aInt"`
98+
}{},
99+
expectedCols: []string{"aString", "aInt"},
100+
compat: false,
101+
err: true,
102+
},
103+
{
104+
name: "Different column type compat should not error",
105+
table: sampleTable,
106+
obj: &struct {
107+
Foo string `ovsdb:"aString"`
108+
Bar string `ovsdb:"aInt"`
109+
}{},
110+
expectedCols: []string{"aString"},
111+
compat: true,
112+
err: false,
53113
},
54114
}
55115
for _, tt := range tests {
@@ -58,16 +118,16 @@ func TestNewMapperInfo(t *testing.T) {
58118
err := json.Unmarshal(tt.table, &table)
59119
assert.Nil(t, err)
60120

61-
info, err := NewInfo("Test", &table, tt.obj)
121+
info, err := NewInfo("Test", &table, tt.obj, tt.compat)
62122
if tt.err {
63123
assert.NotNil(t, err)
64124
} else {
65125
assert.Nil(t, err)
126+
for _, col := range tt.expectedCols {
127+
assert.Truef(t, info.HasColumn(col), "Expected column %s should be present in Mapper Info", col)
128+
}
129+
assert.Equal(t, "Test", info.Metadata.TableName)
66130
}
67-
for _, col := range tt.expectedCols {
68-
assert.Truef(t, info.hasColumn(col), "Expected column should be present in Mapper Info")
69-
}
70-
assert.Equal(t, "Test", info.Metadata.TableName)
71131

72132
})
73133
}
@@ -142,7 +202,7 @@ func TestMapperInfoSet(t *testing.T) {
142202
err := json.Unmarshal(tt.table, &table)
143203
assert.Nil(t, err)
144204

145-
info, err := NewInfo("Test", &table, tt.obj)
205+
info, err := NewInfo("Test", &table, tt.obj, false)
146206
assert.Nil(t, err)
147207

148208
err = info.SetField(tt.column, tt.field)
@@ -223,7 +283,7 @@ func TestMapperInfoColByPtr(t *testing.T) {
223283
err := json.Unmarshal(tt.table, &table)
224284
assert.Nil(t, err)
225285

226-
info, err := NewInfo("Test", &table, tt.obj)
286+
info, err := NewInfo("Test", &table, tt.obj, false)
227287
assert.Nil(t, err)
228288

229289
col, err := info.ColumnByPtr(tt.field)
@@ -355,7 +415,7 @@ func TestOrmGetIndex(t *testing.T) {
355415
}
356416
for _, tt := range tests {
357417
t.Run(fmt.Sprintf("GetValidIndexes_%s", tt.name), func(t *testing.T) {
358-
info, err := NewInfo("Test", &table, tt.obj)
418+
info, err := NewInfo("Test", &table, tt.obj, false)
359419
assert.Nil(t, err)
360420

361421
indexes, err := info.getValidIndexes()

mapper/mapper.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (m Mapper) GetRowData(row *ovsdb.Row, result *Info) error {
7272
// The result object must be given as pointer to an object with the right tags
7373
func (m Mapper) getData(ovsData ovsdb.Row, result *Info) error {
7474
for name, column := range result.Metadata.TableSchema.Columns {
75-
if !result.hasColumn(name) {
75+
if !result.HasColumn(name) {
7676
// If provided struct does not have a field to hold this value, skip it
7777
continue
7878
}
@@ -243,7 +243,7 @@ func (m Mapper) NewCondition(data *Info, field interface{}, function ovsdb.Condi
243243
// It takes care of field validation against the column type
244244
func (m Mapper) NewMutation(data *Info, column string, mutator ovsdb.Mutator, value interface{}) (*ovsdb.Mutation, error) {
245245
// Check the column exists in the object
246-
if !data.hasColumn(column) {
246+
if !data.HasColumn(column) {
247247
return nil, fmt.Errorf("mutation contains column %s that does not exist in object %v", column, data)
248248
}
249249
// Check that the mutation is valid
@@ -304,7 +304,7 @@ func (m Mapper) equalIndexes(one, other *Info, indexes ...string) (bool, error)
304304
if reflect.DeepEqual(ridx, lidx) {
305305
// All columns in an index must be simultaneously equal
306306
for _, col := range lidx {
307-
if !one.hasColumn(col) || !other.hasColumn(col) {
307+
if !one.HasColumn(col) || !other.HasColumn(col) {
308308
break
309309
}
310310
lfield, err := one.FieldByColumn(col)

mapper/mapper_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ func TestMapperGetData(t *testing.T) {
226226
test := ormTestType{
227227
NonTagged: "something",
228228
}
229-
testInfo, err := NewInfo("TestTable", schema.Table("TestTable"), &test)
229+
testInfo, err := NewInfo("TestTable", schema.Table("TestTable"), &test, false)
230230
assert.NoError(t, err)
231231

232232
err = mapper.GetRowData(&ovsRow, testInfo)
@@ -345,7 +345,7 @@ func TestMapperNewRow(t *testing.T) {
345345
for _, test := range tests {
346346
t.Run(fmt.Sprintf("NewRow: %s", test.name), func(t *testing.T) {
347347
mapper := NewMapper(&schema)
348-
info, err := NewInfo("TestTable", schema.Table("TestTable"), test.objInput)
348+
info, err := NewInfo("TestTable", schema.Table("TestTable"), test.objInput, false)
349349
assert.NoError(t, err)
350350
row, err := mapper.NewRow(info)
351351
if test.shoulderr {
@@ -438,7 +438,7 @@ func TestMapperNewRowFields(t *testing.T) {
438438
testObj.MyFloat = 0
439439

440440
test.prepare(&testObj)
441-
info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj)
441+
info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj, false)
442442
assert.NoError(t, err)
443443
row, err := mapper.NewRow(info, test.fields...)
444444
if test.err {
@@ -592,7 +592,7 @@ func TestMapperCondition(t *testing.T) {
592592
for _, tt := range tests {
593593
t.Run(fmt.Sprintf("newEqualityCondition_%s", tt.name), func(t *testing.T) {
594594
tt.prepare(&testObj)
595-
info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj)
595+
info, err := NewInfo("TestTable", schema.Table("TestTable"), &testObj, false)
596596
assert.NoError(t, err)
597597

598598
conds, err := mapper.NewEqualityCondition(info, tt.index...)
@@ -846,9 +846,9 @@ func TestMapperEqualIndexes(t *testing.T) {
846846
}
847847
for _, test := range tests {
848848
t.Run(fmt.Sprintf("Equal %s", test.name), func(t *testing.T) {
849-
info1, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj1)
849+
info1, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj1, false)
850850
assert.NoError(t, err)
851-
info2, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj2)
851+
info2, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj2, false)
852852
assert.NoError(t, err)
853853
eq, err := mapper.equalIndexes(info1, info2, test.indexes...)
854854
assert.Nil(t, err)
@@ -873,9 +873,9 @@ func TestMapperEqualIndexes(t *testing.T) {
873873
Int1: 42,
874874
Int2: 25,
875875
}
876-
info1, err := NewInfo("TestTable", schema.Table("TestTable"), &obj1)
876+
info1, err := NewInfo("TestTable", schema.Table("TestTable"), &obj1, false)
877877
assert.NoError(t, err)
878-
info2, err := NewInfo("TestTable", schema.Table("TestTable"), &obj2)
878+
info2, err := NewInfo("TestTable", schema.Table("TestTable"), &obj2, false)
879879
assert.NoError(t, err)
880880
eq, err := mapper.EqualFields(info1, info2, &obj1.Int1, &obj1.Int2)
881881
assert.Nil(t, err)
@@ -1031,7 +1031,7 @@ func TestMapperMutation(t *testing.T) {
10311031
}
10321032
for _, test := range tests {
10331033
t.Run(fmt.Sprintf("newMutation%s", test.name), func(t *testing.T) {
1034-
info, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj)
1034+
info, err := NewInfo("TestTable", schema.Table("TestTable"), &test.obj, false)
10351035
assert.NoError(t, err)
10361036

10371037
mutation, err := mapper.NewMutation(info, test.column, test.mutator, test.value)
@@ -1119,7 +1119,7 @@ func TestNewMonitorRequest(t *testing.T) {
11191119
require.NoError(t, err)
11201120
mapper := NewMapper(&schema)
11211121
testTable := &testType{}
1122-
info, err := NewInfo("TestTable", schema.Table("TestTable"), testTable)
1122+
info, err := NewInfo("TestTable", schema.Table("TestTable"), testTable, false)
11231123
assert.NoError(t, err)
11241124
mr, err := mapper.NewMonitorRequest(info, nil)
11251125
require.NoError(t, err)

0 commit comments

Comments
 (0)