]> git.proxmox.com Git - mirror_ovs.git/blob - python/ovs/db/schema.py
Global replace of Nicira Networks.
[mirror_ovs.git] / python / ovs / db / schema.py
1 # Copyright (c) 2009, 2010, 2011 Nicira, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import re
16 import sys
17
18 from ovs.db import error
19 import ovs.db.parser
20 from ovs.db import types
21
22
23 def _check_id(name, json):
24 if name.startswith('_'):
25 raise error.Error('names beginning with "_" are reserved', json)
26 elif not ovs.db.parser.is_identifier(name):
27 raise error.Error("name must be a valid id", json)
28
29
30 class DbSchema(object):
31 """Schema for an OVSDB database."""
32
33 def __init__(self, name, version, tables):
34 self.name = name
35 self.version = version
36 self.tables = tables
37
38 # "isRoot" was not part of the original schema definition. Before it
39 # was added, there was no support for garbage collection. So, for
40 # backward compatibility, if the root set is empty then assume that
41 # every table is in the root set.
42 if self.__root_set_size() == 0:
43 for table in self.tables.itervalues():
44 table.is_root = True
45
46 # Find the "ref_table"s referenced by "ref_table_name"s.
47 #
48 # Also force certain columns to be persistent, as explained in
49 # __check_ref_table(). This requires 'is_root' to be known, so this
50 # must follow the loop updating 'is_root' above.
51 for table in self.tables.itervalues():
52 for column in table.columns.itervalues():
53 self.__follow_ref_table(column, column.type.key, "key")
54 self.__follow_ref_table(column, column.type.value, "value")
55
56 def __root_set_size(self):
57 """Returns the number of tables in the schema's root set."""
58 n_root = 0
59 for table in self.tables.itervalues():
60 if table.is_root:
61 n_root += 1
62 return n_root
63
64 @staticmethod
65 def from_json(json):
66 parser = ovs.db.parser.Parser(json, "database schema")
67 name = parser.get("name", ['id'])
68 version = parser.get_optional("version", [str, unicode])
69 parser.get_optional("cksum", [str, unicode])
70 tablesJson = parser.get("tables", [dict])
71 parser.finish()
72
73 if (version is not None and
74 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
75 raise error.Error('schema version "%s" not in format x.y.z'
76 % version)
77
78 tables = {}
79 for tableName, tableJson in tablesJson.iteritems():
80 _check_id(tableName, json)
81 tables[tableName] = TableSchema.from_json(tableJson, tableName)
82
83 return DbSchema(name, version, tables)
84
85 def to_json(self):
86 # "isRoot" was not part of the original schema definition. Before it
87 # was added, there was no support for garbage collection. So, for
88 # backward compatibility, if every table is in the root set then do not
89 # output "isRoot" in table schemas.
90 default_is_root = self.__root_set_size() == len(self.tables)
91
92 tables = {}
93 for table in self.tables.itervalues():
94 tables[table.name] = table.to_json(default_is_root)
95 json = {"name": self.name, "tables": tables}
96 if self.version:
97 json["version"] = self.version
98 return json
99
100 def copy(self):
101 return DbSchema.from_json(self.to_json())
102
103 def __follow_ref_table(self, column, base, base_name):
104 if not base or base.type != types.UuidType or not base.ref_table_name:
105 return
106
107 base.ref_table = self.tables.get(base.ref_table_name)
108 if not base.ref_table:
109 raise error.Error("column %s %s refers to undefined table %s"
110 % (column.name, base_name, base.ref_table_name),
111 tag="syntax error")
112
113 if base.is_strong_ref() and not base.ref_table.is_root:
114 # We cannot allow a strong reference to a non-root table to be
115 # ephemeral: if it is the only reference to a row, then replaying
116 # the database log from disk will cause the referenced row to be
117 # deleted, even though it did exist in memory. If there are
118 # references to that row later in the log (to modify it, to delete
119 # it, or just to point to it), then this will yield a transaction
120 # error.
121 column.persistent = True
122
123
124 class IdlSchema(DbSchema):
125 def __init__(self, name, version, tables, idlPrefix, idlHeader):
126 DbSchema.__init__(self, name, version, tables)
127 self.idlPrefix = idlPrefix
128 self.idlHeader = idlHeader
129
130 @staticmethod
131 def from_json(json):
132 parser = ovs.db.parser.Parser(json, "IDL schema")
133 idlPrefix = parser.get("idlPrefix", [str, unicode])
134 idlHeader = parser.get("idlHeader", [str, unicode])
135
136 subjson = dict(json)
137 del subjson["idlPrefix"]
138 del subjson["idlHeader"]
139 schema = DbSchema.from_json(subjson)
140
141 return IdlSchema(schema.name, schema.version, schema.tables,
142 idlPrefix, idlHeader)
143
144
145 def column_set_from_json(json, columns):
146 if json is None:
147 return tuple(columns)
148 elif type(json) != list:
149 raise error.Error("array of distinct column names expected", json)
150 else:
151 for column_name in json:
152 if type(column_name) not in [str, unicode]:
153 raise error.Error("array of distinct column names expected",
154 json)
155 elif column_name not in columns:
156 raise error.Error("%s is not a valid column name"
157 % column_name, json)
158 if len(set(json)) != len(json):
159 # Duplicate.
160 raise error.Error("array of distinct column names expected", json)
161 return tuple([columns[column_name] for column_name in json])
162
163
164 class TableSchema(object):
165 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
166 is_root=True, indexes=[]):
167 self.name = name
168 self.columns = columns
169 self.mutable = mutable
170 self.max_rows = max_rows
171 self.is_root = is_root
172 self.indexes = indexes
173
174 @staticmethod
175 def from_json(json, name):
176 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
177 columns_json = parser.get("columns", [dict])
178 mutable = parser.get_optional("mutable", [bool], True)
179 max_rows = parser.get_optional("maxRows", [int])
180 is_root = parser.get_optional("isRoot", [bool], False)
181 indexes_json = parser.get_optional("indexes", [list], [])
182 parser.finish()
183
184 if max_rows == None:
185 max_rows = sys.maxint
186 elif max_rows <= 0:
187 raise error.Error("maxRows must be at least 1", json)
188
189 if not columns_json:
190 raise error.Error("table must have at least one column", json)
191
192 columns = {}
193 for column_name, column_json in columns_json.iteritems():
194 _check_id(column_name, json)
195 columns[column_name] = ColumnSchema.from_json(column_json,
196 column_name)
197
198 indexes = []
199 for index_json in indexes_json:
200 index = column_set_from_json(index_json, columns)
201 if not index:
202 raise error.Error("index must have at least one column", json)
203 elif len(index) == 1:
204 index[0].unique = True
205 for column in index:
206 if not column.persistent:
207 raise error.Error("ephemeral columns (such as %s) may "
208 "not be indexed" % column.name, json)
209 indexes.append(index)
210
211 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
212
213 def to_json(self, default_is_root=False):
214 """Returns this table schema serialized into JSON.
215
216 The "isRoot" member is included in the JSON only if its value would
217 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
218 false, because ordinarily a table would be not be part of the root set
219 if its "isRoot" member is omitted. However, garbage collection was not
220 orginally included in OVSDB, so in older schemas that do not include
221 any "isRoot" members, every table is implicitly part of the root set.
222 To serialize such a schema in a way that can be read by older OVSDB
223 tools, specify 'default_is_root' as True.
224 """
225 json = {}
226 if not self.mutable:
227 json["mutable"] = False
228 if default_is_root != self.is_root:
229 json["isRoot"] = self.is_root
230
231 json["columns"] = columns = {}
232 for column in self.columns.itervalues():
233 if not column.name.startswith("_"):
234 columns[column.name] = column.to_json()
235
236 if self.max_rows != sys.maxint:
237 json["maxRows"] = self.max_rows
238
239 if self.indexes:
240 json["indexes"] = []
241 for index in self.indexes:
242 json["indexes"].append([column.name for column in index])
243
244 return json
245
246
247 class ColumnSchema(object):
248 def __init__(self, name, mutable, persistent, type_):
249 self.name = name
250 self.mutable = mutable
251 self.persistent = persistent
252 self.type = type_
253 self.unique = False
254
255 @staticmethod
256 def from_json(json, name):
257 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
258 mutable = parser.get_optional("mutable", [bool], True)
259 ephemeral = parser.get_optional("ephemeral", [bool], False)
260 type_ = types.Type.from_json(parser.get("type", [dict, str, unicode]))
261 parser.finish()
262
263 return ColumnSchema(name, mutable, not ephemeral, type_)
264
265 def to_json(self):
266 json = {"type": self.type.to_json()}
267 if not self.mutable:
268 json["mutable"] = False
269 if not self.persistent:
270 json["ephemeral"] = True
271 return json