forked from ovn-org/libovsdb
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathschema.go
379 lines (339 loc) · 11.8 KB
/
schema.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
package libovsdb
import (
"encoding/json"
"fmt"
"io"
"strings"
)
// DatabaseSchema is a database schema according to RFC7047
type DatabaseSchema struct {
Name string `json:"name"`
Version string `json:"version"`
Tables map[string]TableSchema `json:"tables"`
}
// GetColumn returns a Column Schema for a given table and column name
func (schema DatabaseSchema) GetColumn(tableName, columnName string) (*ColumnSchema, error) {
table, ok := schema.Tables[tableName]
if !ok {
return nil, fmt.Errorf("Table not found in schema %s", tableName)
}
if columnName == "_uuid" {
return &ColumnSchema{
Type: TypeUUID,
}, nil
}
column, ok := table.Columns[columnName]
if !ok {
return nil, fmt.Errorf("Column not found in schema %s", columnName)
}
return column, nil
}
// Print will print the contents of the DatabaseSchema
func (schema DatabaseSchema) Print(w io.Writer) {
fmt.Fprintf(w, "%s, (%s)\n", schema.Name, schema.Version)
for table, tableSchema := range schema.Tables {
fmt.Fprintf(w, "\t %s\n", table)
for column, columnSchema := range tableSchema.Columns {
fmt.Fprintf(w, "\t\t %s => %s\n", column, columnSchema)
}
}
}
// Basic validation for operations against Database Schema
func (schema DatabaseSchema) validateOperations(operations ...Operation) bool {
for _, op := range operations {
table, ok := schema.Tables[op.Table]
if ok {
for column := range op.Row {
if _, ok := table.Columns[column]; !ok {
if column != "_uuid" && column != "_version" {
return false
}
}
}
for _, row := range op.Rows {
for column := range row {
if _, ok := table.Columns[column]; !ok {
if column != "_uuid" && column != "_version" {
return false
}
}
}
}
for _, column := range op.Columns {
if _, ok := table.Columns[column]; !ok {
if column != "_uuid" && column != "_version" {
return false
}
}
}
} else {
return false
}
}
return true
}
// TableSchema is a table schema according to RFC7047
type TableSchema struct {
Columns map[string]*ColumnSchema `json:"columns"`
Indexes [][]string `json:"indexes,omitempty"`
}
/*RFC7047 defines some atomic-types (e.g: integer, string, etc). However, the Column's type
can also hold other more complex types such as set, enum and map. The way to determine the type
depends on internal, not directly marshallable fields. Therefore, in order to simplify the usage
of this library, we define an ExtendedType that includes all possible column types (including
atomic fields).
*/
//ExtendedType includes atomic types as defined in the RFC plus Enum, Map and Set
type ExtendedType = string
// RefType is used to define the possible RefTypes
type RefType = string
const (
//Unlimited is used to express unlimited "Max"
Unlimited int = -1
//Strong RefType
Strong RefType = "strong"
//Weak RefType
Weak RefType = "weak"
//ExtendedType associated with Atomic Types
//TypeInteger is equivalent to 'int'
TypeInteger ExtendedType = "integer"
//TypeReal is equivalent to 'float64'
TypeReal ExtendedType = "real"
//TypeBoolean is equivalent to 'bool'
TypeBoolean ExtendedType = "boolean"
//TypeString is equivalent to 'string'
TypeString ExtendedType = "string"
//TypeUUID is equivalent to 'libovsdb.UUID'
TypeUUID ExtendedType = "uuid"
//Extended Types used to summarize the interal type of the field.
//TypeEnum is an enumerator of type defined by Key.Type
TypeEnum ExtendedType = "enum"
//TypeMap is a map whose type depend on Key.Type and Value.Type
TypeMap ExtendedType = "map"
//TypeSet is a set whose type depend on Key.Type
TypeSet ExtendedType = "set"
)
// ColumnSchema is a column schema according to RFC7047
type ColumnSchema struct {
// According to RFC7047, "type" field can be, either an <atomic-type>
// Or a ColumnTypeObject defined below. To try to simplify the usage, the
// json message will be parsed manually and Type will indicate the "extended"
// type. Depending on its value, more information may be available in TypeObj.
// E.g: If Type == TypeEnum, TypeObj.Key.Enum contains the possible values
Type ExtendedType
TypeObj *ColumnType
Ephemeral bool
Mutable bool
}
// ColumnType is a type object as per RFC7047
type ColumnType struct {
Key *BaseType
Value *BaseType
Min int
// Unlimited is expressed by the const value Unlimited (-1)
Max int
}
// BaseType is a base-type structure as per RFC7047
type BaseType struct {
Type string `json:"type"`
// Enum will be parsed manually and set to a slice
// of possible values. They must be type-asserted to the
// corret type depending on the Type field
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"`
}
// String returns a string representation of the (native) column type
func (column *ColumnSchema) String() string {
var flags []string
var flagStr string
var typeStr string
if column.Ephemeral {
flags = append(flags, "E")
}
if column.Mutable {
flags = append(flags, "M")
}
if len(flags) > 0 {
flagStr = fmt.Sprintf("[%s]", strings.Join(flags, ","))
}
switch column.Type {
case TypeInteger, TypeReal, TypeBoolean, TypeString:
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)
} else {
typeStr = "uuid"
}
case TypeEnum:
typeStr = fmt.Sprintf("enum (type: %s): %v", column.TypeObj.Key.Type, column.TypeObj.Key.Enum)
case TypeMap:
typeStr = fmt.Sprintf("[%s]%s", column.TypeObj.Key.Type, column.TypeObj.Value.Type)
case TypeSet:
var keyStr string
if column.TypeObj.Key.Type == TypeUUID {
keyStr = fmt.Sprintf(" [%s (%s)]", column.TypeObj.Key.RefTable, column.TypeObj.Key.RefType)
} else {
keyStr = string(column.TypeObj.Key.Type)
}
typeStr = fmt.Sprintf("[]%s (min: %d, max: %d)", keyStr, column.TypeObj.Min, column.TypeObj.Max)
default:
panic(fmt.Sprintf("Unsupported type %s", column.Type))
}
return fmt.Sprintf(strings.Join([]string{typeStr, flagStr}, " "))
}
// UnmarshalJSON unmarshalls a json-formatted column
func (column *ColumnSchema) UnmarshalJSON(data []byte) error {
// ColumnJSON represents the known json values for a Column
type ColumnJSON struct {
TypeRawMsg json.RawMessage `json:"type"`
Ephemeral bool `json:"ephemeral,omitempty"`
Mutable bool `json:"mutable,omitempty"`
}
var colJSON ColumnJSON
// Unmarshall known keys
if err := json.Unmarshal(data, &colJSON); err != nil {
return fmt.Errorf("Cannot parse column object %s", err)
}
column.Ephemeral = colJSON.Ephemeral
column.Mutable = colJSON.Mutable
// 'type' can be a string or an object, let's figure it out
var typeString string
if err := json.Unmarshal(colJSON.TypeRawMsg, &typeString); err == nil {
if !isAtomicType(typeString) {
return fmt.Errorf("Schema contains unknown atomic type %s", typeString)
}
// This was an easy one. Use the string as our 'extended' type
column.Type = typeString
return nil
}
// 'type' can be an object defined as:
// "key": <base-type> required
// "value": <base-type> optional
// "min": <integer> optional (default: 1)
// "max": <integer> or "unlimited" optional (default: 1)
column.TypeObj = &ColumnType{
Key: &BaseType{},
Value: nil,
Max: 1,
Min: 1,
}
// ColumnTypeJSON is used to dynamically decode the ColumnType
type ColumnTypeJSON struct {
KeyRawMsg *json.RawMessage `json:"key,omitempty"`
ValueRawMsg *json.RawMessage `json:"value,omitempty"`
Min int `json:"min,omitempty"`
MaxRawMsg *json.RawMessage `json:"max,omitempty"`
}
colTypeJSON := ColumnTypeJSON{
Min: 1,
}
if err := json.Unmarshal(colJSON.TypeRawMsg, &colTypeJSON); err != nil {
return fmt.Errorf("Cannot parse type object: %s", err)
}
// Now we have to unmarshall some fields manually because they can store
// values of different types. Also, in order to really know what native
// type can store a value of this column, the RFC defines some logic based
// on the values of 'type'. So, in addition to manually unmarshalling, let's
// figure out what is the real native type and store it in column.Type for
// ease of use.
// 'max' can be an integer or the string "unlimmited". To simplify, use -1
// as unlimited
if colTypeJSON.MaxRawMsg != nil {
var maxString string
if err := json.Unmarshal(*colTypeJSON.MaxRawMsg, &maxString); err == nil {
if maxString == "unlimited" {
column.TypeObj.Max = Unlimited
} else {
return fmt.Errorf("Unknown max value %s", maxString)
}
} else if err := json.Unmarshal(*colTypeJSON.MaxRawMsg, &column.TypeObj.Max); err != nil {
return fmt.Errorf("Cannot parse max field: %s", err)
}
}
column.TypeObj.Min = colTypeJSON.Min
// 'key' and 'value' can, themselves, be a string or a BaseType.
// key='<atomic_type>' is equivalent to 'key': {'type': '<atomic_type>'}
// To simplify things a bit, we'll translate the former to the latter
if err := json.Unmarshal(*colTypeJSON.KeyRawMsg, &column.TypeObj.Key.Type); err != nil {
if err := json.Unmarshal(*colTypeJSON.KeyRawMsg, column.TypeObj.Key); err != nil {
return fmt.Errorf("Cannot parse key object: %s", err)
}
if err := column.TypeObj.Key.parseEnum(*colTypeJSON.KeyRawMsg); err != nil {
return err
}
}
if !isAtomicType(column.TypeObj.Key.Type) {
return fmt.Errorf("Schema contains unknown atomic type %s", column.TypeObj.Key.Type)
}
// 'value' is optional. If it exists, we know the real native type is a map
if colTypeJSON.ValueRawMsg != nil {
column.TypeObj.Value = &BaseType{}
if err := json.Unmarshal(*colTypeJSON.ValueRawMsg, &column.TypeObj.Value.Type); err != nil {
if err := json.Unmarshal(*colTypeJSON.ValueRawMsg, &column.TypeObj.Value); err != nil {
return fmt.Errorf("Cannot parse value object: %s", err)
}
if err := column.TypeObj.Value.parseEnum(*colTypeJSON.ValueRawMsg); err != nil {
return err
}
}
if !isAtomicType(column.TypeObj.Value.Type) {
return fmt.Errorf("Schema contains unknown atomic type %s", column.TypeObj.Key.Type)
}
}
// Technially, we have finished unmarshalling. But let's finish infering the native
if column.TypeObj.Value != nil {
column.Type = TypeMap
} else if column.TypeObj.Min != 1 || column.TypeObj.Max != 1 {
column.Type = TypeSet
} else if len(column.TypeObj.Key.Enum) > 0 {
column.Type = TypeEnum
} else {
column.Type = column.TypeObj.Key.Type
}
return nil
}
// parseEnum decodes the enum field and populates the BaseType.Enum field
func (bt *BaseType) parseEnum(rawData json.RawMessage) error {
// EnumJSON is used to dynamically decode the Enum values
type EnumJSON struct {
Enum interface{} `json:"enum,omitempty"`
}
var enumJSON EnumJSON
if err := json.Unmarshal(rawData, &enumJSON); err != nil {
return fmt.Errorf("Cannot parse enum object: %s (%s)", string(rawData), err)
}
// enum is optional
if enumJSON.Enum == nil {
return nil
}
// 'enum' is a list or a single element representing a list of exactly one element
switch enumJSON.Enum.(type) {
case []interface{}:
// it's an OvsSet
oSet := enumJSON.Enum.([]interface{})
innerSet := oSet[1].([]interface{})
bt.Enum = make([]interface{}, len(innerSet))
for k, val := range innerSet {
bt.Enum[k] = val
}
default:
bt.Enum = []interface{}{enumJSON.Enum}
}
return nil
}
func isAtomicType(atype string) bool {
switch atype {
case TypeInteger, TypeReal, TypeBoolean, TypeString, TypeUUID:
return true
default:
return false
}
}