Implement database schema versioning.
[sliver-openvswitch.git] / python / ovs / db / schema.py
1 # Copyright (c) 2009, 2010 Nicira Networks
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 class DbSchema(object):
23     """Schema for an OVSDB database."""
24
25     def __init__(self, name, version, tables):
26         self.name = name
27         self.version = version
28         self.tables = tables
29
30         # Validate that all ref_tables refer to the names of tables
31         # that exist.
32         for table in self.tables.itervalues():
33             for column in table.columns.itervalues():
34                 self.__check_ref_table(column, column.type.key, "key")
35                 self.__check_ref_table(column, column.type.value, "value")
36
37     @staticmethod
38     def from_json(json):
39         parser = ovs.db.parser.Parser(json, "database schema")
40         name = parser.get("name", ['id'])
41         version = parser.get_optional("version", [unicode])
42         parser.get_optional("cksum", [unicode])
43         tablesJson = parser.get("tables", [dict])
44         parser.finish()
45
46         if (version is not None and
47             not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
48             raise error.Error("schema version \"%s\" not in format x.y.z"
49                               % version)
50
51         tables = {}
52         for tableName, tableJson in tablesJson.iteritems():
53             if tableName.startswith('_'):
54                 raise error.Error("names beginning with \"_\" are reserved",
55                                   json)
56             elif not ovs.db.parser.is_identifier(tableName):
57                 raise error.Error("name must be a valid id", json)
58             tables[tableName] = TableSchema.from_json(tableJson, tableName)
59
60         return DbSchema(name, version, tables)
61
62     def to_json(self):
63         tables = {}
64         for table in self.tables.itervalues():
65             tables[table.name] = table.to_json()
66         json = {"name": self.name, "tables": tables}
67         if self.version:
68             json["version"] = self.version
69         return json
70
71     def __check_ref_table(self, column, base, base_name):
72         if (base and base.type == types.UuidType and base.ref_table and
73             base.ref_table not in self.tables):
74             raise error.Error("column %s %s refers to undefined table %s"
75                               % (column.name, base_name, base.ref_table),
76                               tag="syntax error")
77
78 class IdlSchema(DbSchema):
79     def __init__(self, name, version, tables, idlPrefix, idlHeader):
80         DbSchema.__init__(self, name, version, tables)
81         self.idlPrefix = idlPrefix
82         self.idlHeader = idlHeader
83
84     @staticmethod
85     def from_json(json):
86         parser = ovs.db.parser.Parser(json, "IDL schema")
87         idlPrefix = parser.get("idlPrefix", [unicode])
88         idlHeader = parser.get("idlHeader", [unicode])
89
90         subjson = dict(json)
91         del subjson["idlPrefix"]
92         del subjson["idlHeader"]
93         schema = DbSchema.from_json(subjson)
94
95         return IdlSchema(schema.name, schema.version, schema.tables,
96                          idlPrefix, idlHeader)
97
98 class TableSchema(object):
99     def __init__(self, name, columns, mutable=True, max_rows=sys.maxint):
100         self.name = name
101         self.columns = columns
102         self.mutable = mutable
103         self.max_rows = max_rows        
104
105     @staticmethod
106     def from_json(json, name):
107         parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
108         columnsJson = parser.get("columns", [dict])
109         mutable = parser.get_optional("mutable", [bool], True)
110         max_rows = parser.get_optional("maxRows", [int])
111         parser.finish()
112
113         if max_rows == None:
114             max_rows = sys.maxint
115         elif max_rows <= 0:
116             raise error.Error("maxRows must be at least 1", json)
117
118         if not columnsJson:
119             raise error.Error("table must have at least one column", json)
120
121         columns = {}
122         for columnName, columnJson in columnsJson.iteritems():
123             if columnName.startswith('_'):
124                 raise error.Error("names beginning with \"_\" are reserved",
125                                   json)
126             elif not ovs.db.parser.is_identifier(columnName):
127                 raise error.Error("name must be a valid id", json)
128             columns[columnName] = ColumnSchema.from_json(columnJson,
129                                                          columnName)
130
131         return TableSchema(name, columns, mutable, max_rows)
132
133     def to_json(self):
134         json = {}
135         if not self.mutable:
136             json["mutable"] = False
137
138         json["columns"] = columns = {}
139         for column in self.columns.itervalues():
140             if not column.name.startswith("_"):
141                 columns[column.name] = column.to_json()
142
143         if self.max_rows != sys.maxint:
144             json["maxRows"] = self.max_rows
145
146         return json
147
148 class ColumnSchema(object):
149     def __init__(self, name, mutable, persistent, type):
150         self.name = name
151         self.mutable = mutable
152         self.persistent = persistent
153         self.type = type
154
155     @staticmethod
156     def from_json(json, name):
157         parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
158         mutable = parser.get_optional("mutable", [bool], True)
159         ephemeral = parser.get_optional("ephemeral", [bool], False)
160         type = types.Type.from_json(parser.get("type", [dict, unicode]))
161         parser.finish()
162
163         return ColumnSchema(name, mutable, not ephemeral, type)
164
165     def to_json(self):
166         json = {"type": self.type.to_json()}
167         if not self.mutable:
168             json["mutable"] = False
169         if not self.persistent:
170             json["ephemeral"] = True
171         return json
172