File: client.go

package info (click to toggle)
golang-github-ovn-org-libovsdb 0.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 1,440 kB
  • sloc: makefile: 52; sh: 14
file content (171 lines) | stat: -rw-r--r-- 5,128 bytes parent folder | download | duplicates (2)
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
package model

import (
	"fmt"
	"reflect"

	"github.com/ovn-org/libovsdb/mapper"
	"github.com/ovn-org/libovsdb/ovsdb"
)

// ColumnKey addresses a column and optionally a key within a column
type ColumnKey struct {
	Column string
	Key    interface{}
}

// ClientIndex defines a client index by a set of columns
type ClientIndex struct {
	Columns []ColumnKey
}

// ClientDBModel contains the client information needed to build a DatabaseModel
type ClientDBModel struct {
	name    string
	types   map[string]reflect.Type
	indexes map[string][]ClientIndex
}

// NewModel returns a new instance of a model from a specific string
func (db ClientDBModel) newModel(table string) (Model, error) {
	mtype, ok := db.types[table]
	if !ok {
		return nil, fmt.Errorf("table %s not found in database model", string(table))
	}
	model := reflect.New(mtype.Elem())
	return model.Interface().(Model), nil
}

// Name returns the database name
func (db ClientDBModel) Name() string {
	return db.name
}

// Indexes returns the client indexes for a model
func (db ClientDBModel) Indexes(table string) []ClientIndex {
	if len(db.indexes) == 0 {
		return nil
	}
	if _, ok := db.indexes[table]; ok {
		return copyIndexes(db.indexes)[table]
	}
	return nil
}

// SetIndexes sets the client indexes. Client indexes are optional, similar to
// schema indexes and are only tracked in the specific client instances that are
// provided with this client model. A client index may point to multiple models
// as uniqueness is not enforced. They are defined per table and multiple
// indexes can be defined for a table. Each index consists of a set of columns.
// If the column is a map, specific keys of that map can be addressed for the
// index.
func (db *ClientDBModel) SetIndexes(indexes map[string][]ClientIndex) {
	db.indexes = copyIndexes(indexes)
}

// Validate validates the DatabaseModel against the input schema
// Returns all the errors detected
func (db ClientDBModel) validate(schema ovsdb.DatabaseSchema) []error {
	var errors []error
	if db.name != schema.Name {
		errors = append(errors, fmt.Errorf("database model name (%s) does not match schema (%s)",
			db.name, schema.Name))
	}

	infos := make(map[string]*mapper.Info, len(db.types))
	for tableName := range db.types {
		tableSchema := schema.Table(tableName)
		if tableSchema == nil {
			errors = append(errors, fmt.Errorf("database model contains a model for table %s that does not exist in schema", tableName))
			continue
		}
		model, err := db.newModel(tableName)
		if err != nil {
			errors = append(errors, err)
			continue
		}
		info, err := mapper.NewInfo(tableName, tableSchema, model)
		if err != nil {
			errors = append(errors, err)
			continue
		}
		infos[tableName] = info
	}

	for tableName, indexSets := range db.indexes {
		info, ok := infos[tableName]
		if !ok {
			errors = append(errors, fmt.Errorf("database model contains a client index for table %s that does not exist in schema", tableName))
			continue
		}
		for _, indexSet := range indexSets {
			for _, indexColumn := range indexSet.Columns {
				f, err := info.FieldByColumn(indexColumn.Column)
				if err != nil {
					errors = append(
						errors,
						fmt.Errorf("database model contains a client index for column %s that does not exist in table %s",
							indexColumn.Column,
							tableName))
					continue
				}
				if indexColumn.Key != nil && reflect.ValueOf(f).Kind() != reflect.Map {
					errors = append(
						errors,
						fmt.Errorf("database model contains a client index for key %s in column %s of table %s that is not a map",
							indexColumn.Key,
							indexColumn.Column,
							tableName))
					continue
				}
			}
		}
	}
	return errors
}

// NewClientDBModel constructs a ClientDBModel based on a database name and dictionary of models indexed by table name
func NewClientDBModel(name string, models map[string]Model) (ClientDBModel, error) {
	types := make(map[string]reflect.Type, len(models))
	for table, model := range models {
		modelType := reflect.TypeOf(model)
		if modelType.Kind() != reflect.Ptr || modelType.Elem().Kind() != reflect.Struct {
			return ClientDBModel{}, fmt.Errorf("model is expected to be a pointer to struct")
		}
		hasUUID := false
		for i := 0; i < modelType.Elem().NumField(); i++ {
			if field := modelType.Elem().Field(i); field.Tag.Get("ovsdb") == "_uuid" &&
				field.Type.Kind() == reflect.String {
				hasUUID = true
				break
			}
		}
		if !hasUUID {
			return ClientDBModel{}, fmt.Errorf("model is expected to have a string field called uuid")
		}

		types[table] = modelType
	}
	return ClientDBModel{
		types: types,
		name:  name,
	}, nil
}

func copyIndexes(src map[string][]ClientIndex) map[string][]ClientIndex {
	if len(src) == 0 {
		return nil
	}
	dst := make(map[string][]ClientIndex, len(src))
	for table, indexSets := range src {
		dst[table] = make([]ClientIndex, 0, len(indexSets))
		for _, indexSet := range indexSets {
			indexSetCopy := ClientIndex{
				Columns: make([]ColumnKey, len(indexSet.Columns)),
			}
			copy(indexSetCopy.Columns, indexSet.Columns)
			dst[table] = append(dst[table], indexSetCopy)
		}
	}
	return dst
}