python: Drop unicode type.
[cascardo/ovs.git] / python / ovs / db / types.py
1 # Copyright (c) 2009, 2010, 2011, 2012, 2013 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 sys
16 import uuid
17
18 import six
19
20 from ovs.db import error
21 import ovs.db.parser
22 import ovs.db.data
23 import ovs.ovsuuid
24
25
26 class AtomicType(object):
27     def __init__(self, name, default, python_types):
28         self.name = name
29         self.default = default
30         self.python_types = python_types
31
32     @staticmethod
33     def from_string(s):
34         if s != "void":
35             for atomic_type in ATOMIC_TYPES:
36                 if s == atomic_type.name:
37                     return atomic_type
38         raise error.Error('"%s" is not an atomic-type' % s, s)
39
40     @staticmethod
41     def from_json(json):
42         if not isinstance(json, six.string_types):
43             raise error.Error("atomic-type expected", json)
44         else:
45             return AtomicType.from_string(json)
46
47     def __str__(self):
48         return self.name
49
50     def to_string(self):
51         return self.name
52
53     def to_json(self):
54         return self.name
55
56     def default_atom(self):
57         return ovs.db.data.Atom(self, self.default)
58
59 REAL_PYTHON_TYPES = list(six.integer_types)
60 REAL_PYTHON_TYPES.extend([float])
61 REAL_PYTHON_TYPES = tuple(REAL_PYTHON_TYPES)
62
63 VoidType = AtomicType("void", None, ())
64 IntegerType = AtomicType("integer", 0, six.integer_types)
65 RealType = AtomicType("real", 0.0, REAL_PYTHON_TYPES)
66 BooleanType = AtomicType("boolean", False, (bool,))
67 StringType = AtomicType("string", "", six.string_types)
68 UuidType = AtomicType("uuid", ovs.ovsuuid.zero(), (uuid.UUID,))
69
70 ATOMIC_TYPES = [VoidType, IntegerType, RealType, BooleanType, StringType,
71                 UuidType]
72
73
74 def escapeCString(src):
75     dst = ""
76     for c in src:
77         if c in "\\\"":
78             dst += "\\" + c
79         elif ord(c) < 32:
80             if c == '\n':
81                 dst += '\\n'
82             elif c == '\r':
83                 dst += '\\r'
84             elif c == '\a':
85                 dst += '\\a'
86             elif c == '\b':
87                 dst += '\\b'
88             elif c == '\f':
89                 dst += '\\f'
90             elif c == '\t':
91                 dst += '\\t'
92             elif c == '\v':
93                 dst += '\\v'
94             else:
95                 dst += '\\%03o' % ord(c)
96         else:
97             dst += c
98     return dst
99
100
101 def commafy(x):
102     """Returns integer x formatted in decimal with thousands set off by
103     commas."""
104     return _commafy("%d" % x)
105
106
107 def _commafy(s):
108     if s.startswith('-'):
109         return '-' + _commafy(s[1:])
110     elif len(s) <= 3:
111         return s
112     else:
113         return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
114
115
116 def returnUnchanged(x):
117     return x
118
119
120 class BaseType(object):
121     def __init__(self, type_, enum=None, min=None, max=None,
122                  min_length=0, max_length=sys.maxint, ref_table_name=None):
123         assert isinstance(type_, AtomicType)
124         self.type = type_
125         self.enum = enum
126         self.min = min
127         self.max = max
128         self.min_length = min_length
129         self.max_length = max_length
130         self.ref_table_name = ref_table_name
131         if ref_table_name:
132             self.ref_type = 'strong'
133         else:
134             self.ref_type = None
135         self.ref_table = None
136
137     def default(self):
138         return ovs.db.data.Atom.default(self.type)
139
140     def __eq__(self, other):
141         if not isinstance(other, BaseType):
142             return NotImplemented
143         return (self.type == other.type and self.enum == other.enum and
144                 self.min == other.min and self.max == other.max and
145                 self.min_length == other.min_length and
146                 self.max_length == other.max_length and
147                 self.ref_table_name == other.ref_table_name)
148
149     def __ne__(self, other):
150         if not isinstance(other, BaseType):
151             return NotImplemented
152         else:
153             return not (self == other)
154
155     @staticmethod
156     def __parse_uint(parser, name, default):
157         value = parser.get_optional(name, six.integer_types)
158         if value is None:
159             value = default
160         else:
161             max_value = 2 ** 32 - 1
162             if not (0 <= value <= max_value):
163                 raise error.Error("%s out of valid range 0 to %d"
164                                   % (name, max_value), value)
165         return value
166
167     @staticmethod
168     def from_json(json):
169         if isinstance(json, six.string_types):
170             return BaseType(AtomicType.from_json(json))
171
172         parser = ovs.db.parser.Parser(json, "ovsdb type")
173         atomic_type = AtomicType.from_json(parser.get("type",
174                                                       six.string_types))
175
176         base = BaseType(atomic_type)
177
178         enum = parser.get_optional("enum", [])
179         if enum is not None:
180             base.enum = ovs.db.data.Datum.from_json(
181                     BaseType.get_enum_type(base.type), enum)
182         elif base.type == IntegerType:
183             base.min = parser.get_optional("minInteger", six.integer_types)
184             base.max = parser.get_optional("maxInteger", six.integer_types)
185             if (base.min is not None and base.max is not None
186                     and base.min > base.max):
187                 raise error.Error("minInteger exceeds maxInteger", json)
188         elif base.type == RealType:
189             base.min = parser.get_optional("minReal", REAL_PYTHON_TYPES)
190             base.max = parser.get_optional("maxReal", REAL_PYTHON_TYPES)
191             if (base.min is not None and base.max is not None
192                     and base.min > base.max):
193                 raise error.Error("minReal exceeds maxReal", json)
194         elif base.type == StringType:
195             base.min_length = BaseType.__parse_uint(parser, "minLength", 0)
196             base.max_length = BaseType.__parse_uint(parser, "maxLength",
197                                                     sys.maxint)
198             if base.min_length > base.max_length:
199                 raise error.Error("minLength exceeds maxLength", json)
200         elif base.type == UuidType:
201             base.ref_table_name = parser.get_optional("refTable", ['id'])
202             if base.ref_table_name:
203                 base.ref_type = parser.get_optional("refType",
204                                                     six.string_types,
205                                                    "strong")
206                 if base.ref_type not in ['strong', 'weak']:
207                     raise error.Error('refType must be "strong" or "weak" '
208                                       '(not "%s")' % base.ref_type)
209         parser.finish()
210
211         return base
212
213     def to_json(self):
214         if not self.has_constraints():
215             return self.type.to_json()
216
217         json = {'type': self.type.to_json()}
218
219         if self.enum:
220             json['enum'] = self.enum.to_json()
221
222         if self.type == IntegerType:
223             if self.min is not None:
224                 json['minInteger'] = self.min
225             if self.max is not None:
226                 json['maxInteger'] = self.max
227         elif self.type == RealType:
228             if self.min is not None:
229                 json['minReal'] = self.min
230             if self.max is not None:
231                 json['maxReal'] = self.max
232         elif self.type == StringType:
233             if self.min_length != 0:
234                 json['minLength'] = self.min_length
235             if self.max_length != sys.maxint:
236                 json['maxLength'] = self.max_length
237         elif self.type == UuidType:
238             if self.ref_table_name:
239                 json['refTable'] = self.ref_table_name
240                 if self.ref_type != 'strong':
241                     json['refType'] = self.ref_type
242         return json
243
244     def copy(self):
245         base = BaseType(self.type, self.enum.copy(), self.min, self.max,
246                         self.min_length, self.max_length, self.ref_table_name)
247         base.ref_table = self.ref_table
248         return base
249
250     def is_valid(self):
251         if self.type in (VoidType, BooleanType, UuidType):
252             return True
253         elif self.type in (IntegerType, RealType):
254             return self.min is None or self.max is None or self.min <= self.max
255         elif self.type == StringType:
256             return self.min_length <= self.max_length
257         else:
258             return False
259
260     def has_constraints(self):
261         return (self.enum is not None or self.min is not None or
262                 self.max is not None or
263                 self.min_length != 0 or self.max_length != sys.maxint or
264                 self.ref_table_name is not None)
265
266     def without_constraints(self):
267         return BaseType(self.type)
268
269     @staticmethod
270     def get_enum_type(atomic_type):
271         """Returns the type of the 'enum' member for a BaseType whose
272         'type' is 'atomic_type'."""
273         return Type(BaseType(atomic_type), None, 1, sys.maxint)
274
275     def is_ref(self):
276         return self.type == UuidType and self.ref_table_name is not None
277
278     def is_strong_ref(self):
279         return self.is_ref() and self.ref_type == 'strong'
280
281     def is_weak_ref(self):
282         return self.is_ref() and self.ref_type == 'weak'
283
284     def toEnglish(self, escapeLiteral=returnUnchanged):
285         if self.type == UuidType and self.ref_table_name:
286             s = escapeLiteral(self.ref_table_name)
287             if self.ref_type == 'weak':
288                 s = "weak reference to " + s
289             return s
290         else:
291             return self.type.to_string()
292
293     def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
294                              escapeNumber=returnUnchanged):
295         if self.enum:
296             literals = [value.toEnglish(escapeLiteral)
297                         for value in self.enum.values]
298             if len(literals) == 1:
299                 english = 'must be %s' % (literals[0])
300             elif len(literals) == 2:
301                 english = 'either %s or %s' % (literals[0], literals[1])
302             else:
303                 english = 'one of %s, %s, or %s' % (literals[0],
304                                                     ', '.join(literals[1:-1]),
305                                                     literals[-1])
306         elif self.min is not None and self.max is not None:
307             if self.type == IntegerType:
308                 english = 'in range %s to %s' % (
309                     escapeNumber(commafy(self.min)),
310                     escapeNumber(commafy(self.max)))
311             else:
312                 english = 'in range %s to %s' % (
313                     escapeNumber("%g" % self.min),
314                     escapeNumber("%g" % self.max))
315         elif self.min is not None:
316             if self.type == IntegerType:
317                 english = 'at least %s' % escapeNumber(commafy(self.min))
318             else:
319                 english = 'at least %s' % escapeNumber("%g" % self.min)
320         elif self.max is not None:
321             if self.type == IntegerType:
322                 english = 'at most %s' % escapeNumber(commafy(self.max))
323             else:
324                 english = 'at most %s' % escapeNumber("%g" % self.max)
325         elif self.min_length != 0 and self.max_length != sys.maxint:
326             if self.min_length == self.max_length:
327                 english = ('exactly %s characters long'
328                            % commafy(self.min_length))
329             else:
330                 english = ('between %s and %s characters long'
331                         % (commafy(self.min_length),
332                            commafy(self.max_length)))
333         elif self.min_length != 0:
334             return 'at least %s characters long' % commafy(self.min_length)
335         elif self.max_length != sys.maxint:
336             english = 'at most %s characters long' % commafy(self.max_length)
337         else:
338             english = ''
339
340         return english
341
342     def toCType(self, prefix):
343         if self.ref_table_name:
344             return "struct %s%s *" % (prefix, self.ref_table_name.lower())
345         else:
346             return {IntegerType: 'int64_t ',
347                     RealType: 'double ',
348                     UuidType: 'struct uuid ',
349                     BooleanType: 'bool ',
350                     StringType: 'char *'}[self.type]
351
352     def toAtomicType(self):
353         return "OVSDB_TYPE_%s" % self.type.to_string().upper()
354
355     def copyCValue(self, dst, src):
356         args = {'dst': dst, 'src': src}
357         if self.ref_table_name:
358             return ("%(dst)s = %(src)s->header_.uuid;") % args
359         elif self.type == StringType:
360             return "%(dst)s = xstrdup(%(src)s);" % args
361         else:
362             return "%(dst)s = %(src)s;" % args
363
364     def assign_c_value_casting_away_const(self, dst, src):
365         args = {'dst': dst, 'src': src}
366         if self.ref_table_name:
367             return ("%(dst)s = %(src)s->header_.uuid;") % args
368         elif self.type == StringType:
369             return "%(dst)s = CONST_CAST(char *, %(src)s);" % args
370         else:
371             return "%(dst)s = %(src)s;" % args
372
373     def initCDefault(self, var, is_optional):
374         if self.ref_table_name:
375             return "%s = NULL;" % var
376         elif self.type == StringType and not is_optional:
377             return '%s = "";' % var
378         else:
379             pattern = {IntegerType: '%s = 0;',
380                        RealType: '%s = 0.0;',
381                        UuidType: 'uuid_zero(&%s);',
382                        BooleanType: '%s = false;',
383                        StringType: '%s = NULL;'}[self.type]
384             return pattern % var
385
386     def cInitBaseType(self, indent, var):
387         stmts = []
388         stmts.append('ovsdb_base_type_init(&%s, %s);' % (
389                 var, self.toAtomicType()))
390         if self.enum:
391             stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
392                          % (var, var))
393             stmts += self.enum.cInitDatum("%s.enum_" % var)
394         if self.type == IntegerType:
395             if self.min is not None:
396                 stmts.append('%s.u.integer.min = INT64_C(%d);'
397                         % (var, self.min))
398             if self.max is not None:
399                 stmts.append('%s.u.integer.max = INT64_C(%d);'
400                         % (var, self.max))
401         elif self.type == RealType:
402             if self.min is not None:
403                 stmts.append('%s.u.real.min = %d;' % (var, self.min))
404             if self.max is not None:
405                 stmts.append('%s.u.real.max = %d;' % (var, self.max))
406         elif self.type == StringType:
407             if self.min_length is not None:
408                 stmts.append('%s.u.string.minLen = %d;'
409                         % (var, self.min_length))
410             if self.max_length != sys.maxint:
411                 stmts.append('%s.u.string.maxLen = %d;'
412                         % (var, self.max_length))
413         elif self.type == UuidType:
414             if self.ref_table_name is not None:
415                 stmts.append('%s.u.uuid.refTableName = "%s";'
416                         % (var, escapeCString(self.ref_table_name)))
417                 stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
418                         % (var, self.ref_type.upper()))
419         return '\n'.join([indent + stmt for stmt in stmts])
420
421
422 class Type(object):
423     DEFAULT_MIN = 1
424     DEFAULT_MAX = 1
425
426     def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
427         self.key = key
428         self.value = value
429         self.n_min = n_min
430         self.n_max = n_max
431
432     def copy(self):
433         if self.value is None:
434             value = None
435         else:
436             value = self.value.copy()
437         return Type(self.key.copy(), value, self.n_min, self.n_max)
438
439     def __eq__(self, other):
440         if not isinstance(other, Type):
441             return NotImplemented
442         return (self.key == other.key and self.value == other.value and
443                 self.n_min == other.n_min and self.n_max == other.n_max)
444
445     def __ne__(self, other):
446         if not isinstance(other, Type):
447             return NotImplemented
448         else:
449             return not (self == other)
450
451     def is_valid(self):
452         return (self.key.type != VoidType and self.key.is_valid() and
453                 (self.value is None or
454                  (self.value.type != VoidType and self.value.is_valid())) and
455                 self.n_min <= 1 <= self.n_max)
456
457     def is_scalar(self):
458         return self.n_min == 1 and self.n_max == 1 and not self.value
459
460     def is_optional(self):
461         return self.n_min == 0 and self.n_max == 1
462
463     def is_composite(self):
464         return self.n_max > 1
465
466     def is_set(self):
467         return self.value is None and (self.n_min != 1 or self.n_max != 1)
468
469     def is_map(self):
470         return self.value is not None
471
472     def is_smap(self):
473         return (self.is_map()
474                 and self.key.type == StringType
475                 and self.value.type == StringType)
476
477     def is_optional_pointer(self):
478         return (self.is_optional() and not self.value
479                 and (self.key.type == StringType or self.key.ref_table_name))
480
481     @staticmethod
482     def __n_from_json(json, default):
483         if json is None:
484             return default
485         elif type(json) == int and 0 <= json <= sys.maxint:
486             return json
487         else:
488             raise error.Error("bad min or max value", json)
489
490     @staticmethod
491     def from_json(json):
492         if isinstance(json, six.string_types):
493             return Type(BaseType.from_json(json))
494
495         parser = ovs.db.parser.Parser(json, "ovsdb type")
496         _types = list(six.string_types)
497         _types.extend([dict])
498         key_json = parser.get("key", _types)
499         value_json = parser.get_optional("value", _types)
500         min_json = parser.get_optional("min", [int])
501         _types = list(six.string_types)
502         _types.extend([int])
503         max_json = parser.get_optional("max", _types)
504         parser.finish()
505
506         key = BaseType.from_json(key_json)
507         if value_json:
508             value = BaseType.from_json(value_json)
509         else:
510             value = None
511
512         n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
513
514         if max_json == 'unlimited':
515             n_max = sys.maxint
516         else:
517             n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
518
519         type_ = Type(key, value, n_min, n_max)
520         if not type_.is_valid():
521             raise error.Error("ovsdb type fails constraint checks", json)
522         return type_
523
524     def to_json(self):
525         if self.is_scalar() and not self.key.has_constraints():
526             return self.key.to_json()
527
528         json = {"key": self.key.to_json()}
529         if self.value is not None:
530             json["value"] = self.value.to_json()
531         if self.n_min != Type.DEFAULT_MIN:
532             json["min"] = self.n_min
533         if self.n_max == sys.maxint:
534             json["max"] = "unlimited"
535         elif self.n_max != Type.DEFAULT_MAX:
536             json["max"] = self.n_max
537         return json
538
539     def toEnglish(self, escapeLiteral=returnUnchanged):
540         keyName = self.key.toEnglish(escapeLiteral)
541         if self.value:
542             valueName = self.value.toEnglish(escapeLiteral)
543
544         if self.is_scalar():
545             return keyName
546         elif self.is_optional():
547             if self.value:
548                 return "optional %s-%s pair" % (keyName, valueName)
549             else:
550                 return "optional %s" % keyName
551         else:
552             if self.n_max == sys.maxint:
553                 if self.n_min:
554                     quantity = "%s or more " % commafy(self.n_min)
555                 else:
556                     quantity = ""
557             elif self.n_min:
558                 quantity = "%s to %s " % (commafy(self.n_min),
559                                           commafy(self.n_max))
560             else:
561                 quantity = "up to %s " % commafy(self.n_max)
562
563             if self.value:
564                 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
565             else:
566                 if keyName.endswith('s'):
567                     plural = keyName + "es"
568                 else:
569                     plural = keyName + "s"
570                 return "set of %s%s" % (quantity, plural)
571
572     def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
573                              escapeNumber=returnUnchanged):
574         constraints = []
575         keyConstraints = self.key.constraintsToEnglish(escapeLiteral,
576                                                        escapeNumber)
577         if keyConstraints:
578             if self.value:
579                 constraints.append('key %s' % keyConstraints)
580             else:
581                 constraints.append(keyConstraints)
582
583         if self.value:
584             valueConstraints = self.value.constraintsToEnglish(escapeLiteral,
585                                                                escapeNumber)
586             if valueConstraints:
587                 constraints.append('value %s' % valueConstraints)
588
589         return ', '.join(constraints)
590
591     def cDeclComment(self):
592         if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
593             return "\t/* Always nonnull. */"
594         else:
595             return ""
596
597     def cInitType(self, indent, var):
598         initKey = self.key.cInitBaseType(indent, "%s.key" % var)
599         if self.value:
600             initValue = self.value.cInitBaseType(indent, "%s.value" % var)
601         else:
602             initValue = ('%sovsdb_base_type_init(&%s.value, '
603                          'OVSDB_TYPE_VOID);' % (indent, var))
604         initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
605         if self.n_max == sys.maxint:
606             n_max = "UINT_MAX"
607         else:
608             n_max = self.n_max
609         initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
610         return "\n".join((initKey, initValue, initMin, initMax))