ovsdb: Add simple constraints.
authorBen Pfaff <blp@nicira.com>
Mon, 8 Feb 2010 22:09:36 +0000 (14:09 -0800)
committerBen Pfaff <blp@nicira.com>
Mon, 8 Feb 2010 22:16:19 +0000 (14:16 -0800)
36 files changed:
extras/ezio/automake.mk
lib/automake.mk
lib/ovsdb-data.c
lib/ovsdb-data.h
lib/ovsdb-idl.c
lib/ovsdb-parser.h
lib/ovsdb-types.c
lib/ovsdb-types.h
lib/pcre.h [new file with mode: 0644]
lib/unicode.c
lib/unicode.h
m4/openvswitch.m4
ovsdb/SPECS
ovsdb/automake.mk
ovsdb/column.c
ovsdb/condition.c
ovsdb/execution.c
ovsdb/mutation.c
ovsdb/ovsdb-client.c
ovsdb/ovsdb-idlc.in
ovsdb/ovsdb-server.c
ovsdb/table.c
tests/automake.mk
tests/ovs-vsctl.at
tests/ovsdb-data.at
tests/ovsdb-execution.at
tests/ovsdb-mutation.at
tests/ovsdb-types.at
tests/test-ovsdb.c
utilities/automake.mk
utilities/ovs-vsctl.8.in
utilities/ovs-vsctl.c
vswitchd/automake.mk
vswitchd/ovs-brcompatd.c
vswitchd/ovs-vswitchd.c
vswitchd/vswitch.ovsschema

index eae11cd..a615fc8 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, 2009 Nicira Networks, Inc.
+# Copyright (C) 2008, 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -8,7 +8,7 @@
 EXTRA_DIST += extras/ezio/ezio3.ti
 
 if HAVE_CURSES
-if HAVE_PCRE
+if HAVE_PCRE_PARTIAL
 install-data-hook:
        @echo tic -x $(srcdir)/extras/ezio/ezio3.ti
        @if ! tic -x $(srcdir)/extras/ezio/ezio3.ti; then                             \
@@ -48,5 +48,5 @@ extras_ezio_ovs_switchui_LDADD = \
        $(PCRE_LIBS) \
        $(SSL_LIBS) \
        -lm
-endif # HAVE_PCRE
+endif # HAVE_PCRE_PARTIAL
 endif # HAVE_CURSES
index 51d3c11..e5a0648 100644 (file)
@@ -88,6 +88,7 @@ lib_libopenvswitch_a_SOURCES = \
        lib/packets.h \
        lib/pcap.c \
        lib/pcap.h \
+       lib/pcre.h \
        lib/poll-loop.c \
        lib/poll-loop.h \
        lib/port-array.c \
index 8631016..e79f849 100644 (file)
@@ -29,6 +29,7 @@
 #include "json.h"
 #include "shash.h"
 #include "sort.h"
+#include "unicode.h"
 
 static struct json *
 wrap_json(const char *name, struct json *wrapped)
@@ -272,10 +273,10 @@ ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
     return error0;
 }
 
-struct ovsdb_error *
-ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
-                     const struct json *json,
-                     const struct ovsdb_symbol_table *symtab)
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_atom_from_json__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                       const struct json *json,
+                       const struct ovsdb_symbol_table *symtab)
 {
     switch (type) {
     case OVSDB_TYPE_VOID:
@@ -327,6 +328,26 @@ ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
                               ovsdb_atomic_type_to_string(type));
 }
 
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom,
+                     const struct ovsdb_base_type *base,
+                     const struct json *json,
+                     const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    error = ovsdb_atom_from_json__(atom, base->type, json, symtab);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_atom_check_constraints(atom, base);
+    if (error) {
+        ovsdb_atom_destroy(atom, base->type);
+    }
+    return error;
+}
+
 struct json *
 ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
 {
@@ -356,28 +377,9 @@ ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
     }
 }
 
-/* Initializes 'atom' to a value of the given 'type' parsed from 's', which
- * takes one of the following forms:
- *
- *      - OVSDB_TYPE_INTEGER: A decimal integer optionally preceded by a sign.
- *
- *      - OVSDB_TYPE_REAL: A floating-point number in the format accepted by
- *        strtod().
- *
- *      - OVSDB_TYPE_BOOLEAN: "true", "yes", "on", "1" for true, or "false",
- *        "no", "off", or "0" for false.
- *
- *      - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
- *        an arbitrary string.
- *
- *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.
- *
- * Returns a null pointer if successful, otherwise an error message describing
- * the problem.  The caller is responsible for freeing the error.
- */
-char *
-ovsdb_atom_from_string(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
-                       const char *s)
+static char *
+ovsdb_atom_from_string__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                         const char *s)
 {
     switch (type) {
     case OVSDB_TYPE_VOID:
@@ -451,6 +453,45 @@ ovsdb_atom_from_string(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
     return NULL;
 }
 
+/* Initializes 'atom' to a value of type 'base' parsed from 's', which takes
+ * one of the following forms:
+ *
+ *      - OVSDB_TYPE_INTEGER: A decimal integer optionally preceded by a sign.
+ *
+ *      - OVSDB_TYPE_REAL: A floating-point number in the format accepted by
+ *        strtod().
+ *
+ *      - OVSDB_TYPE_BOOLEAN: "true", "yes", "on", "1" for true, or "false",
+ *        "no", "off", or "0" for false.
+ *
+ *      - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
+ *        an arbitrary string.
+ *
+ *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.
+ *
+ * Returns a null pointer if successful, otherwise an error message describing
+ * the problem.  The caller is responsible for freeing the error.
+ */
+char *
+ovsdb_atom_from_string(union ovsdb_atom *atom,
+                       const struct ovsdb_base_type *base, const char *s)
+{
+    struct ovsdb_error *error;
+    char *msg;
+
+    msg = ovsdb_atom_from_string__(atom, base->type, s);
+    if (msg) {
+        return msg;
+    }
+
+    error = ovsdb_atom_check_constraints(atom, base);
+    if (error) {
+        msg = ovsdb_error_to_string(error);
+        ovsdb_error_destroy(error);
+    }
+    return msg;
+}
+
 static bool
 string_needs_quotes(const char *s)
 {
@@ -518,6 +559,141 @@ ovsdb_atom_to_string(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
         NOT_REACHED();
     }
 }
+
+static struct ovsdb_error *
+check_string_constraints(const char *s,
+                         const struct ovsdb_string_constraints *c)
+{
+    size_t n_chars;
+    char *msg;
+
+    msg = utf8_validate(s, &n_chars);
+    if (msg) {
+        struct ovsdb_error *error;
+
+        error = ovsdb_error("constraint violation",
+                            "\"%s\" is not a valid UTF-8 string: %s",
+                            s, msg);
+        free(msg);
+        return error;
+    }
+
+    if (n_chars < c->minLen) {
+        return ovsdb_error(
+            "constraint violation",
+            "\"%s\" length %zu is less than minimum allowed "
+            "length %u", s, n_chars, c->minLen);
+    } else if (n_chars > c->maxLen) {
+        return ovsdb_error(
+            "constraint violation",
+            "\"%s\" length %zu is greater than maximum allowed "
+            "length %u", s, n_chars, c->maxLen);
+    }
+
+#if HAVE_PCRE
+    if (c->re) {
+        int retval;
+
+        retval = pcre_exec(c->re, NULL, s, strlen(s), 0,
+                           PCRE_ANCHORED | PCRE_NO_UTF8_CHECK, NULL, 0);
+        if (retval == PCRE_ERROR_NOMATCH) {
+            if (c->reComment) {
+                return ovsdb_error("constraint violation",
+                                   "\"%s\" is not a %s", s, c->reComment);
+            } else {
+                return ovsdb_error("constraint violation",
+                                   "\"%s\" does not match regular expression "
+                                   "/%s/", s, c->reMatch);
+            }
+        } else if (retval < 0) {
+            /* PCRE doesn't have a function to translate an error code to a
+             * description.  Bizarre.  See pcreapi(3) for error details. */
+            return ovsdb_error("internal error", "PCRE returned error %d",
+                               retval);
+        }
+    }
+#endif  /* HAVE_PCRE */
+
+    return NULL;
+}
+
+/* Checks whether 'atom' meets the constraints (if any) defined in 'base'.
+ * (base->type must specify 'atom''s type.)  Returns a null pointer if the
+ * constraints are met, otherwise an error that explains the violation. */
+struct ovsdb_error *
+ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
+                             const struct ovsdb_base_type *base)
+{
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (atom->integer >= base->u.integer.min
+            && atom->integer <= base->u.integer.max) {
+            return NULL;
+        } else if (base->u.integer.min != INT64_MIN) {
+            if (base->u.integer.max != INT64_MAX) {
+                return ovsdb_error("constraint violation",
+                                   "%"PRId64" is not in the valid range "
+                                   "%"PRId64" to %"PRId64" (inclusive)",
+                                   atom->integer,
+                                   base->u.integer.min, base->u.integer.max);
+            } else {
+                return ovsdb_error("constraint violation",
+                                   "%"PRId64" is less than minimum allowed "
+                                   "value %"PRId64,
+                                   atom->integer, base->u.integer.min);
+            }
+        } else {
+            return ovsdb_error("constraint violation",
+                               "%"PRId64" is greater than maximum allowed "
+                               "value %"PRId64,
+                               atom->integer, base->u.integer.max);
+        }
+        NOT_REACHED();
+
+    case OVSDB_TYPE_REAL:
+        if (atom->real >= base->u.real.min && atom->real <= base->u.real.max) {
+            return NULL;
+        } else if (base->u.real.min != -DBL_MAX) {
+            if (base->u.real.max != DBL_MAX) {
+                return ovsdb_error("constraint violation",
+                                   "%.*g is not in the valid range "
+                                   "%.*g to %.*g (inclusive)",
+                                   DBL_DIG, atom->real,
+                                   DBL_DIG, base->u.real.min,
+                                   DBL_DIG, base->u.real.max);
+            } else {
+                return ovsdb_error("constraint violation",
+                                   "%.*g is less than minimum allowed "
+                                   "value %.*g",
+                                   DBL_DIG, atom->real,
+                                   DBL_DIG, base->u.real.min);
+            }
+        } else {
+            return ovsdb_error("constraint violation",
+                               "%.*g is greater than maximum allowed "
+                               "value %.*g",
+                               DBL_DIG, atom->real,
+                               DBL_DIG, base->u.real.max);
+        }
+        NOT_REACHED();
+
+    case OVSDB_TYPE_BOOLEAN:
+        return NULL;
+
+    case OVSDB_TYPE_STRING:
+        return check_string_constraints(atom->string, &base->u.string);
+
+    case OVSDB_TYPE_UUID:
+        return NULL;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
 \f
 static union ovsdb_atom *
 alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
@@ -551,8 +727,8 @@ ovsdb_datum_init_default(struct ovsdb_datum *datum,
                          const struct ovsdb_type *type)
 {
     datum->n = type->n_min;
-    datum->keys = alloc_default_atoms(type->key_type, datum->n);
-    datum->values = alloc_default_atoms(type->value_type, datum->n);
+    datum->keys = alloc_default_atoms(type->key.type, datum->n);
+    datum->values = alloc_default_atoms(type->value.type, datum->n);
 }
 
 bool
@@ -565,11 +741,11 @@ ovsdb_datum_is_default(const struct ovsdb_datum *datum,
         return false;
     }
     for (i = 0; i < datum->n; i++) {
-        if (!ovsdb_atom_is_default(&datum->keys[i], type->key_type)) {
+        if (!ovsdb_atom_is_default(&datum->keys[i], type->key.type)) {
             return false;
         }
-        if (type->value_type != OVSDB_TYPE_VOID
-            && !ovsdb_atom_is_default(&datum->values[i], type->value_type)) {
+        if (type->value.type != OVSDB_TYPE_VOID
+            && !ovsdb_atom_is_default(&datum->values[i], type->value.type)) {
             return false;
         }
     }
@@ -602,8 +778,8 @@ ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
 {
     unsigned int n = old->n;
     new->n = n;
-    new->keys = clone_atoms(old->keys, type->key_type, n);
-    new->values = clone_atoms(old->values, type->value_type, n);
+    new->keys = clone_atoms(old->keys, type->key.type, n);
+    new->values = clone_atoms(old->values, type->value.type, n);
 }
 
 static void
@@ -622,8 +798,8 @@ free_data(enum ovsdb_atomic_type type,
 void
 ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
 {
-    free_data(type->key_type, datum->keys, datum->n);
-    free_data(type->value_type, datum->values, datum->n);
+    free_data(type->key.type, datum->keys, datum->n);
+    free_data(type->value.type, datum->values, datum->n);
 }
 
 void
@@ -646,7 +822,7 @@ ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
 
     return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
                                    &cbdata->datum->keys[b],
-                                   cbdata->type->key_type);
+                                   cbdata->type->key.type);
 }
 
 static void
@@ -655,7 +831,7 @@ ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
     struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
 
     ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
-    if (cbdata->type->value_type != OVSDB_TYPE_VOID) {
+    if (cbdata->type->value.type != OVSDB_TYPE_VOID) {
         ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
     }
 }
@@ -676,7 +852,7 @@ ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
 
         for (i = 0; i < datum->n - 1; i++) {
             if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
-                                  type->key_type)) {
+                                  type->key.type)) {
                 if (ovsdb_type_is_map(type)) {
                     return ovsdb_error(NULL, "map contains duplicate key");
                 } else {
@@ -689,6 +865,40 @@ ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
     }
 }
 
+/* Checks that each of the atoms in 'datum' conforms to the constraints
+ * specified by its 'type'.  Returns an error if a constraint is violated,
+ * otherwise a null pointer.
+ *
+ * This function is not commonly useful because the most ordinary way to obtain
+ * a datum is ultimately via ovsdb_atom_from_string() or
+ * ovsdb_atom_from_json(), which check constraints themselves. */
+struct ovsdb_error *
+ovsdb_datum_check_constraints(const struct ovsdb_datum *datum,
+                              const struct ovsdb_type *type)
+{
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    for (i = 0; i < datum->n; i++) {
+        error = ovsdb_atom_check_constraints(&datum->keys[i], &type->key);
+        if (error) {
+            return error;
+        }
+    }
+
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        for (i = 0; i < datum->n; i++) {
+            error = ovsdb_atom_check_constraints(&datum->values[i],
+                                                 &type->value);
+            if (error) {
+                return error;
+            }
+        }
+    }
+
+    return NULL;
+}
+
 struct ovsdb_error *
 ovsdb_datum_from_json(struct ovsdb_datum *datum,
                       const struct ovsdb_type *type,
@@ -702,7 +912,7 @@ ovsdb_datum_from_json(struct ovsdb_datum *datum,
         datum->keys = xmalloc(sizeof *datum->keys);
         datum->values = NULL;
 
-        error = ovsdb_atom_from_json(&datum->keys[0], type->key_type,
+        error = ovsdb_atom_from_json(&datum->keys[0], &type->key,
                                      json, symtab);
         if (error) {
             free(datum->keys);
@@ -746,7 +956,7 @@ ovsdb_datum_from_json(struct ovsdb_datum *datum,
                 }
             }
 
-            error = ovsdb_atom_from_json(&datum->keys[i], type->key_type,
+            error = ovsdb_atom_from_json(&datum->keys[i], &type->key,
                                          key, symtab);
             if (error) {
                 goto error;
@@ -754,9 +964,9 @@ ovsdb_datum_from_json(struct ovsdb_datum *datum,
 
             if (is_map) {
                 error = ovsdb_atom_from_json(&datum->values[i],
-                                             type->value_type, value, symtab);
+                                             &type->value, value, symtab);
                 if (error) {
-                    ovsdb_atom_destroy(&datum->keys[i], type->key_type);
+                    ovsdb_atom_destroy(&datum->keys[i], type->key.type);
                     goto error;
                 }
             }
@@ -784,14 +994,14 @@ ovsdb_datum_to_json(const struct ovsdb_datum *datum,
     /* These tests somewhat tolerate a 'datum' that does not exactly match
      * 'type', in particular a datum with 'n' not in the allowed range. */
     if (datum->n == 1 && ovsdb_type_is_scalar(type)) {
-        return ovsdb_atom_to_json(&datum->keys[0], type->key_type);
-    } else if (type->value_type == OVSDB_TYPE_VOID) {
+        return ovsdb_atom_to_json(&datum->keys[0], type->key.type);
+    } else if (type->value.type == OVSDB_TYPE_VOID) {
         struct json **elems;
         size_t i;
 
         elems = xmalloc(datum->n * sizeof *elems);
         for (i = 0; i < datum->n; i++) {
-            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type);
+            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key.type);
         }
 
         return wrap_json("set", json_array_create(elems, datum->n));
@@ -802,8 +1012,8 @@ ovsdb_datum_to_json(const struct ovsdb_datum *datum,
         elems = xmalloc(datum->n * sizeof *elems);
         for (i = 0; i < datum->n; i++) {
             elems[i] = json_array_create_2(
-                ovsdb_atom_to_json(&datum->keys[i], type->key_type),
-                ovsdb_atom_to_json(&datum->values[i], type->value_type));
+                ovsdb_atom_to_json(&datum->keys[i], type->key.type),
+                ovsdb_atom_to_json(&datum->values[i], type->value.type));
         }
 
         return wrap_json("map", json_array_create(elems, datum->n));
@@ -820,20 +1030,19 @@ skip_spaces(const char *p)
 }
 
 static char *
-parse_atom_token(const char **s, enum ovsdb_atomic_type type,
+parse_atom_token(const char **s, const struct ovsdb_base_type *base,
                  union ovsdb_atom *atom)
 {
     char *token, *error;
 
     error = ovsdb_token_parse(s, &token);
     if (!error) {
-        error = ovsdb_atom_from_string(atom, type, token);
+        error = ovsdb_atom_from_string(atom, base, token);
         free(token);
     }
     return error;
 }
 
-
 static char *
 parse_key_value(const char **s, const struct ovsdb_type *type,
                 union ovsdb_atom *key, union ovsdb_atom *value)
@@ -841,19 +1050,19 @@ parse_key_value(const char **s, const struct ovsdb_type *type,
     const char *start = *s;
     char *error;
 
-    error = parse_atom_token(s, type->key_type, key);
-    if (!error && type->value_type != OVSDB_TYPE_VOID) {
+    error = parse_atom_token(s, &type->key, key);
+    if (!error && type->value.type != OVSDB_TYPE_VOID) {
         *s = skip_spaces(*s);
         if (**s == '=') {
             (*s)++;
             *s = skip_spaces(*s);
-            error = parse_atom_token(s, type->value_type, value);
+            error = parse_atom_token(s, &type->value, value);
         } else {
             error = xasprintf("%s: syntax error at \"%c\" expecting \"=\"",
                               start, **s);
         }
         if (error) {
-            ovsdb_atom_destroy(key, type->key_type);
+            ovsdb_atom_destroy(key, type->key.type);
         }
     }
     return error;
@@ -863,9 +1072,9 @@ static void
 free_key_value(const struct ovsdb_type *type,
                union ovsdb_atom *key, union ovsdb_atom *value)
 {
-    ovsdb_atom_destroy(key, type->key_type);
-    if (type->value_type != OVSDB_TYPE_VOID) {
-        ovsdb_atom_destroy(value, type->value_type);
+    ovsdb_atom_destroy(key, type->key.type);
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(value, type->value.type);
     }
 }
 
@@ -987,10 +1196,10 @@ ovsdb_datum_to_string(const struct ovsdb_datum *datum,
             ds_put_cstr(out, ", ");
         }
 
-        ovsdb_atom_to_string(&datum->keys[i], type->key_type, out);
+        ovsdb_atom_to_string(&datum->keys[i], type->key.type, out);
         if (is_map) {
             ds_put_char(out, '=');
-            ovsdb_atom_to_string(&datum->values[i], type->value_type, out);
+            ovsdb_atom_to_string(&datum->values[i], type->value.type, out);
         }
     }
     if (type->n_max > 1 || !datum->n) {
@@ -1016,9 +1225,9 @@ uint32_t
 ovsdb_datum_hash(const struct ovsdb_datum *datum,
                  const struct ovsdb_type *type, uint32_t basis)
 {
-    basis = hash_atoms(type->key_type, datum->keys, datum->n, basis);
-    basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n;
-    basis = hash_atoms(type->value_type, datum->values, datum->n, basis);
+    basis = hash_atoms(type->key.type, datum->keys, datum->n, basis);
+    basis ^= (type->key.type << 24) | (type->value.type << 16) | datum->n;
+    basis = hash_atoms(type->value.type, datum->values, datum->n, basis);
     return basis;
 }
 
@@ -1059,18 +1268,18 @@ ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
         return a->n < b->n ? -1 : 1;
     }
 
-    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n);
+    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key.type, a->n);
     if (cmp) {
         return cmp;
     }
 
-    return (type->value_type == OVSDB_TYPE_VOID ? 0
-            : atom_arrays_compare_3way(a->values, b->values, type->value_type,
+    return (type->value.type == OVSDB_TYPE_VOID ? 0
+            : atom_arrays_compare_3way(a->values, b->values, type->value.type,
                                        a->n));
 }
 
 /* If 'key' is one of the keys in 'datum', returns its index within 'datum',
- * otherwise UINT_MAX.  'key_type' must be the type of the atoms stored in the
+ * otherwise UINT_MAX.  'key.type' must be the type of the atoms stored in the
  * 'keys' array in 'datum'.
  */
 unsigned int
@@ -1095,7 +1304,7 @@ ovsdb_datum_find_key(const struct ovsdb_datum *datum,
 }
 
 /* If 'key' and 'value' is one of the key-value pairs in 'datum', returns its
- * index within 'datum', otherwise UINT_MAX.  'key_type' must be the type of
+ * index within 'datum', otherwise UINT_MAX.  'key.type' must be the type of
  * the atoms stored in the 'keys' array in 'datum'.  'value_type' may be the
  * type of the 'values' atoms or OVSDB_TYPE_VOID to compare only keys.
  */
@@ -1117,7 +1326,7 @@ ovsdb_datum_find_key_value(const struct ovsdb_datum *datum,
 
 /* If atom 'i' in 'a' is also in 'b', returns its index in 'b', otherwise
  * UINT_MAX.  'type' must be the type of 'a' and 'b', except that
- * type->value_type may be set to OVSDB_TYPE_VOID to compare keys but not
+ * type->value.type may be set to OVSDB_TYPE_VOID to compare keys but not
  * values. */
 static unsigned int
 ovsdb_datum_find(const struct ovsdb_datum *a, int i,
@@ -1125,9 +1334,9 @@ ovsdb_datum_find(const struct ovsdb_datum *a, int i,
                  const struct ovsdb_type *type)
 {
     return ovsdb_datum_find_key_value(b,
-                                      &a->keys[i], type->key_type,
+                                      &a->keys[i], type->key.type,
                                       a->values ? &a->values[i] : NULL,
-                                      type->value_type);
+                                      type->value.type);
 }
 
 /* Returns true if every element in 'a' is also in 'b', false otherwise. */
@@ -1167,7 +1376,7 @@ ovsdb_datum_reallocate(struct ovsdb_datum *a, const struct ovsdb_type *type,
                        unsigned int capacity)
 {
     a->keys = xrealloc(a->keys, capacity * sizeof *a->keys);
-    if (type->value_type != OVSDB_TYPE_VOID) {
+    if (type->value.type != OVSDB_TYPE_VOID) {
         a->values = xrealloc(a->values, capacity * sizeof *a->values);
     }
 }
@@ -1182,10 +1391,10 @@ void
 ovsdb_datum_remove_unsafe(struct ovsdb_datum *datum, size_t idx,
                           const struct ovsdb_type *type)
 {
-    ovsdb_atom_destroy(&datum->keys[idx], type->key_type);
+    ovsdb_atom_destroy(&datum->keys[idx], type->key.type);
     datum->keys[idx] = datum->keys[datum->n - 1];
-    if (type->value_type != OVSDB_TYPE_VOID) {
-        ovsdb_atom_destroy(&datum->values[idx], type->value_type);
+    if (type->value.type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(&datum->values[idx], type->value.type);
         datum->values[idx] = datum->values[datum->n - 1];
     }
     datum->n--;
@@ -1208,11 +1417,11 @@ ovsdb_datum_add_unsafe(struct ovsdb_datum *datum,
 {
     size_t idx = datum->n++;
     datum->keys = xrealloc(datum->keys, datum->n * sizeof *datum->keys);
-    ovsdb_atom_clone(&datum->keys[idx], key, type->key_type);
-    if (type->value_type != OVSDB_TYPE_VOID) {
+    ovsdb_atom_clone(&datum->keys[idx], key, type->key.type);
+    if (type->value.type != OVSDB_TYPE_VOID) {
         datum->values = xrealloc(datum->values,
                                  datum->n * sizeof *datum->values);
-        ovsdb_atom_clone(&datum->values[idx], value, type->value_type);
+        ovsdb_atom_clone(&datum->values[idx], value, type->value.type);
     }
 }
 
@@ -1227,21 +1436,21 @@ ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b,
     for (bi = 0; bi < b->n; bi++) {
         unsigned int ai;
 
-        ai = ovsdb_datum_find_key(a, &b->keys[bi], type->key_type);
+        ai = ovsdb_datum_find_key(a, &b->keys[bi], type->key.type);
         if (ai == UINT_MAX) {
             if (n == a->n) {
                 ovsdb_datum_reallocate(a, type, a->n + (b->n - bi));
             }
-            ovsdb_atom_clone(&a->keys[n], &b->keys[bi], type->key_type);
-            if (type->value_type != OVSDB_TYPE_VOID) {
+            ovsdb_atom_clone(&a->keys[n], &b->keys[bi], type->key.type);
+            if (type->value.type != OVSDB_TYPE_VOID) {
                 ovsdb_atom_clone(&a->values[n], &b->values[bi],
-                                 type->value_type);
+                                 type->value.type);
             }
             n++;
-        } else if (replace && type->value_type != OVSDB_TYPE_VOID) {
-            ovsdb_atom_destroy(&a->values[ai], type->value_type);
+        } else if (replace && type->value.type != OVSDB_TYPE_VOID) {
+            ovsdb_atom_destroy(&a->values[ai], type->value.type);
             ovsdb_atom_clone(&a->values[ai], &b->values[bi],
-                             type->value_type);
+                             type->value.type);
         }
     }
     if (n != a->n) {
@@ -1260,9 +1469,9 @@ ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
     bool changed = false;
     size_t i;
 
-    assert(a_type->key_type == b_type->key_type);
-    assert(a_type->value_type == b_type->value_type
-           || b_type->value_type == OVSDB_TYPE_VOID);
+    assert(a_type->key.type == b_type->key.type);
+    assert(a_type->value.type == b_type->value.type
+           || b_type->value.type == OVSDB_TYPE_VOID);
 
     /* XXX The big-O of this could easily be improved. */
     for (i = 0; i < a->n; ) {
index 0638fa1..c8e146a 100644 (file)
@@ -67,18 +67,22 @@ static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
 }
 
 struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
-                                         enum ovsdb_atomic_type,
+                                         const struct ovsdb_base_type *,
                                          const struct json *,
                                          const struct ovsdb_symbol_table *)
     WARN_UNUSED_RESULT;
 struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
                                 enum ovsdb_atomic_type);
 
-char *ovsdb_atom_from_string(union ovsdb_atom *, enum ovsdb_atomic_type,
-                             const char *)
+char *ovsdb_atom_from_string(union ovsdb_atom *,
+                             const struct ovsdb_base_type *, const char *)
     WARN_UNUSED_RESULT;
 void ovsdb_atom_to_string(const union ovsdb_atom *, enum ovsdb_atomic_type,
                           struct ds *);
+
+struct ovsdb_error *ovsdb_atom_check_constraints(
+    const union ovsdb_atom *, const struct ovsdb_base_type *)
+    WARN_UNUSED_RESULT;
 \f
 /* An instance of an OVSDB type (given by struct ovsdb_type).
  *
@@ -119,6 +123,9 @@ void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
 /* Checking and maintaining invariants. */
 struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
                                      const struct ovsdb_type *);
+struct ovsdb_error *ovsdb_datum_check_constraints(
+    const struct ovsdb_datum *, const struct ovsdb_type *)
+    WARN_UNUSED_RESULT;
 
 /* Type conversion. */
 struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
index a0b0cc9..53be4ec 100644 (file)
@@ -1338,6 +1338,7 @@ ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_,
     size_t column_idx = column - class->columns;
 
     assert(row->new != NULL);
+    assert(column_idx < class->n_columns);
     if (hmap_node_is_null(&row->txn_node)) {
         hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
                     uuid_hash(&row->uuid));
@@ -1516,6 +1517,7 @@ static bool
 ovsdb_idl_txn_process_insert_reply(struct ovsdb_idl_txn_insert *insert,
                                    const struct json_array *results)
 {
+    static const struct ovsdb_base_type uuid_type = OVSDB_BASE_UUID_INIT;
     struct ovsdb_error *error;
     struct json *json_uuid;
     union ovsdb_atom uuid;
@@ -1536,7 +1538,7 @@ ovsdb_idl_txn_process_insert_reply(struct ovsdb_idl_txn_insert *insert,
         return false;
     }
 
-    error = ovsdb_atom_from_json(&uuid, OVSDB_TYPE_UUID, json_uuid, NULL);
+    error = ovsdb_atom_from_json(&uuid, &uuid_type, json_uuid, NULL);
     if (error) {
         char *s = ovsdb_error_to_string(error);
         VLOG_WARN_RL(&syntax_rl, "\"insert\" reply \"uuid\" is not a JSON "
index 6efa0a7..a27563a 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -70,6 +70,7 @@ bool ovsdb_parser_has_error(const struct ovsdb_parser *);
 struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
 struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
     WARN_UNUSED_RESULT;
+void ovsdb_parser_destroy(struct ovsdb_parser *);
 
 bool ovsdb_parser_is_id(const char *string);
 
index 659b50d..ff81960 100644 (file)
@@ -17,6 +17,7 @@
 
 #include "ovsdb-types.h"
 
+#include <float.h>
 #include <limits.h>
 
 #include "dynamic-string.h"
 #include "ovsdb-parser.h"
 
 const struct ovsdb_type ovsdb_type_integer =
-    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER);
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_INTEGER_INIT);
 const struct ovsdb_type ovsdb_type_real =
-    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL);
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_REAL_INIT);
 const struct ovsdb_type ovsdb_type_boolean =
-    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN);
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_BOOLEAN_INIT);
 const struct ovsdb_type ovsdb_type_string =
-    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING);
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_STRING_INIT);
 const struct ovsdb_type ovsdb_type_uuid =
-    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID);
-
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_BASE_UUID_INIT);
+\f
+/* ovsdb_atomic_type */
 const char *
 ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
 {
@@ -69,18 +71,6 @@ ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
     return json_string_create(ovsdb_atomic_type_to_string(type));
 }
 
-bool
-ovsdb_type_is_valid(const struct ovsdb_type *type)
-{
-    return (type->key_type != OVSDB_TYPE_VOID
-            && ovsdb_atomic_type_is_valid(type->key_type)
-            && ovsdb_atomic_type_is_valid(type->value_type)
-            && type->n_min <= 1
-            && type->n_min <= type->n_max
-            && (type->value_type == OVSDB_TYPE_VOID
-                || ovsdb_atomic_type_is_valid_key(type->key_type)));
-}
-
 bool
 ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
 {
@@ -118,6 +108,422 @@ ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
         return ovsdb_syntax_error(json, NULL, "atomic-type expected");
     }
 }
+\f
+/* ovsdb_base_type */
+
+void
+ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
+{
+    base->type = type;
+
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        break;
+
+    case OVSDB_TYPE_INTEGER:
+        base->u.integer.min = INT64_MIN;
+        base->u.integer.max = INT64_MAX;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        base->u.real.min = -DBL_MAX;
+        base->u.real.max = DBL_MAX;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        base->u.string.re = NULL;
+        base->u.string.reMatch = NULL;
+        base->u.string.reComment = NULL;
+        base->u.string.minLen = 0;
+        base->u.string.maxLen = UINT_MAX;
+        break;
+
+    case OVSDB_TYPE_UUID:
+        break;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_base_type_clone(struct ovsdb_base_type *dst,
+                      const struct ovsdb_base_type *src)
+{
+    *dst = *src;
+
+    switch (dst->type) {
+    case OVSDB_TYPE_VOID:
+    case OVSDB_TYPE_INTEGER:
+    case OVSDB_TYPE_REAL:
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (dst->u.string.re) {
+            pcre_refcount(dst->u.string.re, 1);
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_base_type_destroy(struct ovsdb_base_type *base)
+{
+    if (base) {
+        switch (base->type) {
+        case OVSDB_TYPE_VOID:
+        case OVSDB_TYPE_INTEGER:
+        case OVSDB_TYPE_REAL:
+        case OVSDB_TYPE_BOOLEAN:
+            break;
+
+        case OVSDB_TYPE_STRING:
+            if (base->u.string.re && !pcre_refcount(base->u.string.re, -1)) {
+                pcre_free(base->u.string.re);
+                free(base->u.string.reMatch);
+                free(base->u.string.reComment);
+            }
+            break;
+
+        case OVSDB_TYPE_UUID:
+            break;
+
+        case OVSDB_N_TYPES:
+            NOT_REACHED();
+
+        default:
+            NOT_REACHED();
+        }
+    }
+}
+
+bool
+ovsdb_base_type_is_valid(const struct ovsdb_base_type *base)
+{
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        return true;
+
+    case OVSDB_TYPE_INTEGER:
+        return base->u.integer.min <= base->u.integer.max;
+
+    case OVSDB_TYPE_REAL:
+        return base->u.real.min <= base->u.real.max;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return true;
+
+    case OVSDB_TYPE_STRING:
+        return base->u.string.minLen <= base->u.string.maxLen;
+
+    case OVSDB_TYPE_UUID:
+        return true;
+
+    case OVSDB_N_TYPES:
+    default:
+        return false;
+    }
+}
+
+bool
+ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
+{
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return (base->u.integer.min != INT64_MIN
+                || base->u.integer.max != INT64_MAX);
+
+    case OVSDB_TYPE_REAL:
+        return (base->u.real.min != -DBL_MAX
+                || base->u.real.max != DBL_MAX);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return false;
+
+    case OVSDB_TYPE_STRING:
+        return (base->u.string.reMatch != NULL
+                || base->u.string.minLen != 0
+                || base->u.string.maxLen != UINT_MAX);
+
+    case OVSDB_TYPE_UUID:
+        return false;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_base_type_clear_constraints(struct ovsdb_base_type *base)
+{
+    enum ovsdb_atomic_type type = base->type;
+    ovsdb_base_type_destroy(base);
+    ovsdb_base_type_init(base, type);
+}
+
+struct ovsdb_error *
+ovsdb_base_type_set_regex(struct ovsdb_base_type *base,
+                          const char *reMatch, const char *reComment)
+{
+    const char *errorString;
+    const char *pattern;
+    int errorOffset;
+
+    /* Compile pattern, anchoring it at both ends. */
+    pattern = reMatch;
+    if (pattern[0] == '\0' || strchr(pattern, '\0')[-1] != '$') {
+        pattern = xasprintf("%s$", pattern);
+    }
+    base->u.string.re = pcre_compile(pattern, (PCRE_ANCHORED | PCRE_UTF8
+                                               | PCRE_JAVASCRIPT_COMPAT),
+                                     &errorString, &errorOffset, NULL);
+    if (pattern != reMatch) {
+        free((char *) pattern);
+    }
+    if (!base->u.string.re) {
+        return ovsdb_syntax_error(NULL, "invalid regular expression",
+                                  "\"%s\" is not a valid regular "
+                                  "expression: %s", reMatch, errorString);
+    }
+
+    /* Save regular expression. */
+    pcre_refcount(base->u.string.re, 1);
+    base->u.string.reMatch = xstrdup(reMatch);
+    base->u.string.reComment = reComment ? xstrdup(reComment) : NULL;
+    return NULL;
+}
+
+static struct ovsdb_error *
+parse_optional_uint(struct ovsdb_parser *parser, const char *member,
+                    unsigned int *uint)
+{
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_INTEGER | OP_OPTIONAL);
+    if (json) {
+        if (json->u.integer < 0 || json->u.integer > UINT_MAX) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "%s out of valid range 0 to %u",
+                                      member, UINT_MAX);
+        }
+        *uint = json->u.integer;
+    }
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_base_type_from_json(struct ovsdb_base_type *base,
+                          const struct json *json)
+{
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+    const struct json *type;
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_atomic_type_from_json(&base->type, json);
+        if (error) {
+            return error;
+        }
+        ovsdb_base_type_init(base, base->type);
+        return NULL;
+    }
+
+    ovsdb_parser_init(&parser, json, "ovsdb type");
+    type = ovsdb_parser_member(&parser, "type", OP_STRING);
+    if (ovsdb_parser_has_error(&parser)) {
+        base->type = OVSDB_TYPE_VOID;
+        return ovsdb_parser_finish(&parser);
+    }
+
+    error = ovsdb_atomic_type_from_json(&base->type, type);
+    if (error) {
+        return error;
+    }
+
+    ovsdb_base_type_init(base, base->type);
+    if (base->type == OVSDB_TYPE_INTEGER) {
+        const struct json *min, *max;
+
+        min = ovsdb_parser_member(&parser, "minInteger",
+                                  OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "maxInteger",
+                                  OP_INTEGER | OP_OPTIONAL);
+        base->u.integer.min = min ? min->u.integer : INT64_MIN;
+        base->u.integer.max = max ? max->u.integer : INT64_MAX;
+        if (base->u.integer.min > base->u.integer.max) {
+            error = ovsdb_syntax_error(json, NULL,
+                                       "minInteger exceeds maxInteger");
+        }
+    } else if (base->type == OVSDB_TYPE_REAL) {
+        const struct json *min, *max;
+
+        min = ovsdb_parser_member(&parser, "minReal", OP_NUMBER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "maxReal", OP_NUMBER | OP_OPTIONAL);
+        base->u.real.min = min ? json_real(min) : -DBL_MAX;
+        base->u.real.max = max ? json_real(max) : DBL_MAX;
+        if (base->u.real.min > base->u.real.max) {
+            error = ovsdb_syntax_error(json, NULL, "minReal exceeds maxReal");
+        }
+    } else if (base->type == OVSDB_TYPE_STRING) {
+        const struct json *reMatch;
+
+        reMatch = ovsdb_parser_member(&parser, "reMatch",
+                                      OP_STRING | OP_OPTIONAL);
+        if (reMatch) {
+            const struct json *reComment;
+
+            reComment = ovsdb_parser_member(&parser, "reComment",
+                                            OP_STRING | OP_OPTIONAL);
+            error = ovsdb_base_type_set_regex(
+                base, json_string(reMatch),
+                reComment ? json_string(reComment) : NULL);
+        }
+
+        if (!error) {
+            error = parse_optional_uint(&parser, "minLength",
+                                        &base->u.string.minLen);
+        }
+        if (!error) {
+            error = parse_optional_uint(&parser, "maxLength",
+                                        &base->u.string.maxLen);
+        }
+        if (!error && base->u.string.minLen > base->u.string.maxLen) {
+            error = ovsdb_syntax_error(json, NULL,
+                                       "minLength exceeds maxLength");
+        }
+    }
+
+    if (error) {
+        ovsdb_error_destroy(ovsdb_parser_finish(&parser));
+    } else {
+        error = ovsdb_parser_finish(&parser);
+    }
+    if (error) {
+        ovsdb_base_type_destroy(base);
+        base->type = OVSDB_TYPE_VOID;
+    }
+    return error;
+}
+
+struct json *
+ovsdb_base_type_to_json(const struct ovsdb_base_type *base)
+{
+    struct json *json;
+
+    if (!ovsdb_base_type_has_constraints(base)) {
+        return json_string_create(ovsdb_atomic_type_to_string(base->type));
+    }
+
+    json = json_object_create();
+    json_object_put_string(json, "type",
+                           ovsdb_atomic_type_to_string(base->type));
+    switch (base->type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (base->u.integer.min != INT64_MIN) {
+            json_object_put(json, "minInteger",
+                            json_integer_create(base->u.integer.min));
+        }
+        if (base->u.integer.max != INT64_MAX) {
+            json_object_put(json, "maxInteger",
+                            json_integer_create(base->u.integer.max));
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (base->u.real.min != -DBL_MAX) {
+            json_object_put(json, "minReal",
+                            json_real_create(base->u.real.min));
+        }
+        if (base->u.real.max != DBL_MAX) {
+            json_object_put(json, "maxReal",
+                            json_real_create(base->u.real.max));
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (base->u.string.reMatch) {
+            json_object_put_string(json, "reMatch", base->u.string.reMatch);
+            if (base->u.string.reComment) {
+                json_object_put_string(json, "reComment",
+                                       base->u.string.reComment);
+            }
+        }
+        if (base->u.string.minLen != 0) {
+            json_object_put(json, "minLength",
+                            json_integer_create(base->u.string.minLen));
+        }
+        if (base->u.string.maxLen != UINT_MAX) {
+            json_object_put(json, "maxLength",
+                            json_integer_create(base->u.string.maxLen));
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        break;
+
+    case OVSDB_N_TYPES:
+        NOT_REACHED();
+
+    default:
+        NOT_REACHED();
+    }
+
+    return json;
+}
+\f
+/* ovsdb_type */
+
+void
+ovsdb_type_clone(struct ovsdb_type *dst, const struct ovsdb_type *src)
+{
+    ovsdb_base_type_clone(&dst->key, &src->key);
+    ovsdb_base_type_clone(&dst->value, &src->value);
+    dst->n_min = src->n_min;
+    dst->n_max = src->n_max;
+}
+
+void
+ovsdb_type_destroy(struct ovsdb_type *type)
+{
+    ovsdb_base_type_destroy(&type->key);
+    ovsdb_base_type_destroy(&type->value);
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+    return (type->key.type != OVSDB_TYPE_VOID
+            && ovsdb_base_type_is_valid(&type->key)
+            && ovsdb_base_type_is_valid(&type->value)
+            && type->n_min <= 1
+            && type->n_min <= type->n_max);
+}
 
 static struct ovsdb_error *
 n_from_json(const struct json *json, unsigned int *n)
@@ -136,8 +542,8 @@ n_from_json(const struct json *json, unsigned int *n)
 char *
 ovsdb_type_to_english(const struct ovsdb_type *type)
 {
-    const char *key = ovsdb_atomic_type_to_string(type->key_type);
-    const char *value = ovsdb_atomic_type_to_string(type->value_type);
+    const char *key = ovsdb_atomic_type_to_string(type->key.type);
+    const char *value = ovsdb_atomic_type_to_string(type->value.type);
     if (ovsdb_type_is_scalar(type)) {
         return xstrdup(key);
     } else {
@@ -166,20 +572,21 @@ ovsdb_type_to_english(const struct ovsdb_type *type)
 struct ovsdb_error *
 ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
 {
-    type->value_type = OVSDB_TYPE_VOID;
+    type->value.type = OVSDB_TYPE_VOID;
     type->n_min = 1;
     type->n_max = 1;
 
     if (json->type == JSON_STRING) {
-        return ovsdb_atomic_type_from_json(&type->key_type, json);
+        return ovsdb_base_type_from_json(&type->key, json);
     } else if (json->type == JSON_OBJECT) {
         const struct json *key, *value, *min, *max;
         struct ovsdb_error *error;
         struct ovsdb_parser parser;
 
         ovsdb_parser_init(&parser, json, "ovsdb type");
-        key = ovsdb_parser_member(&parser, "key", OP_STRING);
-        value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL);
+        key = ovsdb_parser_member(&parser, "key", OP_STRING | OP_OBJECT);
+        value = ovsdb_parser_member(&parser, "value",
+                                    OP_STRING | OP_OBJECT | OP_OPTIONAL);
         min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
         max = ovsdb_parser_member(&parser, "max",
                                   OP_INTEGER | OP_STRING | OP_OPTIONAL);
@@ -188,13 +595,13 @@ ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
             return error;
         }
 
-        error = ovsdb_atomic_type_from_json(&type->key_type, key);
+        error = ovsdb_base_type_from_json(&type->key, key);
         if (error) {
             return error;
         }
 
         if (value) {
-            error = ovsdb_atomic_type_from_json(&type->value_type, value);
+            error = ovsdb_base_type_from_json(&type->value, value);
             if (error) {
                 return error;
             }
@@ -229,15 +636,15 @@ ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
 struct json *
 ovsdb_type_to_json(const struct ovsdb_type *type)
 {
-    if (ovsdb_type_is_scalar(type)) {
-        return ovsdb_atomic_type_to_json(type->key_type);
+    if (ovsdb_type_is_scalar(type)
+        && !ovsdb_base_type_has_constraints(&type->key)) {
+        return ovsdb_base_type_to_json(&type->key);
     } else {
         struct json *json = json_object_create();
-        json_object_put(json, "key",
-                        ovsdb_atomic_type_to_json(type->key_type));
-        if (type->value_type != OVSDB_TYPE_VOID) {
+        json_object_put(json, "key", ovsdb_base_type_to_json(&type->key));
+        if (type->value.type != OVSDB_TYPE_VOID) {
             json_object_put(json, "value",
-                            ovsdb_atomic_type_to_json(type->value_type));
+                            ovsdb_base_type_to_json(&type->value));
         }
         if (type->n_min != 1) {
             json_object_put(json, "min", json_integer_create(type->n_min));
index b9b3d5a..633b50f 100644 (file)
@@ -16,6 +16,8 @@
 #ifndef OVSDB_TYPES_H
 #define OVSDB_TYPES_H 1
 
+#include <float.h>
+#include <pcre.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include "compiler.h"
@@ -35,13 +37,68 @@ enum ovsdb_atomic_type {
 };
 
 static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
-static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type);
 bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
 struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
                                                 const struct json *);
 const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
 struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
 \f
+/* An atomic type plus optional constraints. */
+
+struct ovsdb_base_type {
+    enum ovsdb_atomic_type type;
+    union {
+        struct ovsdb_integer_constraints {
+            int64_t min;        /* minInteger or INT64_MIN. */
+            int64_t max;        /* maxInteger or INT64_MAX. */
+        } integer;
+
+        struct ovsdb_real_constraints {
+            double min;         /* minReal or -DBL_MAX. */
+            double max;         /* minReal or DBL_MAX. */
+        } real;
+
+        /* No constraints for Boolean types. */
+
+        struct ovsdb_string_constraints {
+            pcre *re;           /* Compiled regular expression. */
+            char *reMatch;      /* reMatch or NULL. */
+            char *reComment;    /* reComment or NULL. */
+            unsigned int minLen; /* minLength or 0. */
+            unsigned int maxLen; /* maxLength or UINT_MAX. */
+        } string;
+    } u;
+};
+
+#define OVSDB_BASE_VOID_INIT    { .type = OVSDB_TYPE_VOID }
+#define OVSDB_BASE_INTEGER_INIT { .type = OVSDB_TYPE_INTEGER,           \
+                                  .u.integer = { INT64_MIN, INT64_MAX } }
+#define OVSDB_BASE_REAL_INIT    { .type = OVSDB_TYPE_REAL,          \
+                                  .u.real = { -DBL_MAX, DBL_MAX } }
+#define OVSDB_BASE_BOOLEAN_INIT { .type = OVSDB_TYPE_BOOLEAN }
+#define OVSDB_BASE_STRING_INIT  { .type = OVSDB_TYPE_STRING,        \
+                                  .u.string = { NULL, NULL, NULL,   \
+                                                0, UINT_MAX } }
+#define OVSDB_BASE_UUID_INIT    { .type = OVSDB_TYPE_UUID }
+
+void ovsdb_base_type_init(struct ovsdb_base_type *, enum ovsdb_atomic_type);
+void ovsdb_base_type_clone(struct ovsdb_base_type *,
+                           const struct ovsdb_base_type *);
+void ovsdb_base_type_destroy(struct ovsdb_base_type *);
+
+bool ovsdb_base_type_is_valid(const struct ovsdb_base_type *);
+bool ovsdb_base_type_has_constraints(const struct ovsdb_base_type *);
+void ovsdb_base_type_clear_constraints(struct ovsdb_base_type *);
+struct ovsdb_error *ovsdb_base_type_set_regex(struct ovsdb_base_type *,
+                                              const char *reMatch,
+                                              const char *reComment)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_base_type_from_json(struct ovsdb_base_type *,
+                                              const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_base_type_to_json(const struct ovsdb_base_type *);
+\f
 /* An OVSDB type.
  *
  * Several rules constrain the valid types.  See ovsdb_type_is_valid() (in
@@ -60,14 +117,13 @@ struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
  * can also be considered an optional pair of 'key_type' and 'value_type'.
  */
 struct ovsdb_type {
-    enum ovsdb_atomic_type key_type;
-    enum ovsdb_atomic_type value_type;
+    struct ovsdb_base_type key;
+    struct ovsdb_base_type value;
     unsigned int n_min;
     unsigned int n_max;         /* UINT_MAX stands in for "unlimited". */
 };
 
-#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \
-        { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 }
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY) { KEY, OVSDB_BASE_VOID_INIT, 1, 1 }
 
 extern const struct ovsdb_type ovsdb_type_integer;
 extern const struct ovsdb_type ovsdb_type_real;
@@ -75,6 +131,9 @@ extern const struct ovsdb_type ovsdb_type_boolean;
 extern const struct ovsdb_type ovsdb_type_string;
 extern const struct ovsdb_type ovsdb_type_uuid;
 
+void ovsdb_type_clone(struct ovsdb_type *, const struct ovsdb_type *);
+void ovsdb_type_destroy(struct ovsdb_type *);
+
 bool ovsdb_type_is_valid(const struct ovsdb_type *);
 
 static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
@@ -98,16 +157,9 @@ ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
     return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
 }
 
-static inline bool
-ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type)
-{
-    /* XXX should we disallow reals or booleans as keys? */
-    return ovsdb_atomic_type_is_valid(atomic_type);
-}
-
 static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
 {
-    return (type->value_type == OVSDB_TYPE_VOID
+    return (type->value.type == OVSDB_TYPE_VOID
             && type->n_min == 1 && type->n_max == 1);
 }
 
@@ -123,13 +175,13 @@ static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
 
 static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
 {
-    return (type->value_type == OVSDB_TYPE_VOID
+    return (type->value.type == OVSDB_TYPE_VOID
             && (type->n_min != 1 || type->n_max != 1));
 }
 
 static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
 {
-    return type->value_type != OVSDB_TYPE_VOID;
+    return type->value.type != OVSDB_TYPE_VOID;
 }
 
 #endif /* ovsdb-types.h */
diff --git a/lib/pcre.h b/lib/pcre.h
new file mode 100644 (file)
index 0000000..5ade833
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PCRE_H
+#define PCRE_H 1
+
+#ifdef HAVE_PCRE
+#include_next <pcre.h>
+#else
+typedef void pcre;
+#endif
+
+#endif /* pcre.h */
index 69ebcfc..e8fea86 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "unicode.h"
 
+#include <inttypes.h>
+
+#include "dynamic-string.h"
+#include "util.h"
+
 /* Returns the unicode code point corresponding to leading surrogate 'leading'
  * and trailing surrogate 'trailing'.  The return value will not make any
  * sense if 'leading' or 'trailing' are not in the correct ranges for leading
@@ -36,3 +41,124 @@ utf16_decode_surrogate_pair(int leading, int trailing)
     int x1 = trailing & 0x3ff;
     return (u << 16) | (x0 << 10) | x1;
 }
+
+/* Returns the number of Unicode characters in UTF-8 string 's'. */
+size_t
+utf8_length(const char *s_)
+{
+    const uint8_t *s;
+    size_t length;
+
+    length = 0;
+    for (s = (const uint8_t *) s_; *s != '\0'; s++) {
+        /* The most-significant bits of the first byte in a character are one
+         * of 2#01, 2#00, or 2#11.  2#10 is a continuation byte. */
+        length += (*s & 0xc0) != 0x80;
+    }
+    return length;
+}
+
+static char *
+invalid_utf8_sequence(const uint8_t *s, int n, size_t *lengthp)
+{
+    struct ds msg;
+    int i;
+
+    if (lengthp) {
+        *lengthp = 0;
+    }
+
+    ds_init(&msg);
+    ds_put_cstr(&msg, "invalid UTF-8 sequence");
+    for (i = 0; i < n; i++) {
+        ds_put_format(&msg, " 0x%02"PRIx8, s[i]);
+    }
+    return ds_steal_cstr(&msg);
+}
+
+struct utf8_sequence {
+    uint8_t octets[5][2];
+};
+
+static const struct utf8_sequence *
+lookup_utf8_sequence(uint8_t c)
+{
+    static const struct utf8_sequence seqs[] = {
+        { { { 0x01, 0x7f },
+            { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xc2, 0xdf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xe0, 0xe0 }, { 0xa0, 0xbf }, { 0x80, 0xbf },
+            {0,0}, {0, 0 } } },
+
+        { { { 0xe1, 0xec }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xed, 0xed }, { 0x80, 0x9f }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xee, 0xef }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 }, { 0, 0 } } },
+
+        { { { 0xf0, 0xf0 }, { 0x90, 0xbf }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+
+        { { { 0xf1, 0xf3 }, { 0x80, 0xbf }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+
+        { { { 0xf4, 0xf4 }, { 0x80, 0x8f }, { 0x80, 0xbf }, { 0x80, 0xbf },
+            { 0, 0 } } },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(seqs); i++) {
+        const uint8_t *o = seqs[i].octets[0];
+        if (c >= o[0] && c <= o[1]) {
+            return &seqs[i];
+        }
+    }
+    return NULL;
+}
+
+/* Checks that 's' is a valid, null-terminated UTF-8 string.  If so, returns a
+ * null pointer and sets '*lengthp' to the number of Unicode characters in
+ * 's'.  If not, returns an error message that the caller must free and sets
+ * '*lengthp' to 0.
+ *
+ * 'lengthp' may be NULL if the length is not needed. */
+char *
+utf8_validate(const char *s_, size_t *lengthp)
+{
+    size_t length = 0;
+    const uint8_t *s;
+
+    for (s = (const uint8_t *) s_; *s != '\0'; ) {
+        length++;
+        if (s[0] < 0x80) {
+            s++;
+        } else {
+            const struct utf8_sequence *seq;
+            int i;
+
+            seq = lookup_utf8_sequence(s[0]);
+            if (!seq) {
+                return invalid_utf8_sequence(s, 1, lengthp);
+            }
+
+            for (i = 1; seq->octets[i][0]; i++) {
+                const uint8_t *o = seq->octets[i];
+                if (s[i] < o[0] || s[i] > o[1]) {
+                    return invalid_utf8_sequence(s, i + 1, lengthp);
+                }
+            }
+            s += i;
+        }
+    }
+    if (lengthp) {
+        *lengthp = length;
+    }
+    return NULL;
+}
index 0f20bdc..b2078e6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
 #define UNICODE_H 1
 
 #include <stdbool.h>
+#include <stddef.h>
+#include "compiler.h"
 
 /* Returns true if 'c' is a Unicode code point, otherwise false. */
 static inline bool
@@ -50,4 +52,7 @@ uc_is_surrogate(int c)
 
 int utf16_decode_surrogate_pair(int leading, int trailing);
 
+size_t utf8_length(const char *);
+char *utf8_validate(const char *, size_t *lengthp) WARN_UNUSED_RESULT;
+
 #endif /* unicode.h */
index 7b827e7..affeac3 100644 (file)
@@ -215,11 +215,22 @@ AC_DEFUN([OVS_CHECK_LINUX_VT_H],
    fi])
 
 dnl Checks for libpcre.
+dnl
+dnl ovsdb wants any reasonable version of libpcre (6.6 is what
+dnl XenServer 5.5 has).
+dnl
+dnl ezio-term wants libpcre that supports the PCRE_PARTIAL feature,
+dnl which is libpcre 7.2 or later.
 AC_DEFUN([OVS_CHECK_PCRE],
   [dnl Make sure that pkg-config is installed.
    m4_pattern_forbid([PKG_CHECK_MODULES])
-   PKG_CHECK_MODULES([PCRE], [libpcre >= 7.2], [HAVE_PCRE=yes], [HAVE_PCRE=no])
+   HAVE_PCRE=no
+   HAVE_PCRE_PARTIAL=no
+   PKG_CHECK_MODULES([PCRE], [libpcre >= 6.6], 
+     [HAVE_PCRE=yes
+      PKG_CHECK_EXISTS([libpcre >= 7.2], [HAVE_PCRE_PARTIAL=yes])])
    AM_CONDITIONAL([HAVE_PCRE], [test "$HAVE_PCRE" = yes])
+   AM_CONDITIONAL([HAVE_PCRE_PARTIAL], [test "$HAVE_PCRE_PARTIAL" = yes])
    if test "$HAVE_PCRE" = yes; then
       AC_DEFINE([HAVE_PCRE], [1], [Define to 1 if libpcre is installed.])
    fi])
index 7963a2a..75e592b 100644 (file)
@@ -11,7 +11,7 @@ values.  Additional notation is presented later.
 <string>
 
     A JSON string.  Any Unicode string is allowed, as specified by RFC
-    4627.  Implementations may disallowed null bytes.
+    4627.  Implementations may disallow null bytes.
 
 <id>
 
@@ -37,6 +37,42 @@ values.  Additional notation is presented later.
 
     Any JSON value.
 
+<error>
+
+    A JSON object with the following members:
+
+        "error": <string>                       required
+        "details": <string>                     optional
+
+    The value of the "error" member is a short string, specified in
+    this document, that broadly indicates the class of the error.
+    Most "error" strings are specific to contexts described elsewhere
+    in this document, but the following "error" strings may appear in
+    any context where an <error> is permitted:
+
+    "error": "resources exhausted"
+
+        The operation requires more resources (memory, disk, CPU,
+        etc.) than are currently available to the database server.
+
+    "error": "I/O error"
+
+        Problems accessing the disk, network, or other required
+        resources prevented the operation from completing.
+
+    Database implementations may use "error" strings not specified
+    in this document to indicate errors that do not fit into any of
+    the specified categories.
+
+    Optionally, an <error> may include a "details" member, whose value
+    is a string that describes the error in more detail for the
+    benefit of a human user or administrator.  This document does not
+    specify the format or content of the "details" string.
+
+    An <error> may also have other members that describe the error in
+    more detail.  This document does not specify the names or values
+    of these members.
+
 Schema Format
 -------------
 
@@ -105,8 +141,8 @@ is represented by <database-schema>, as described below.
     object that describes the type of a database column, with the
     following members:
 
-        "key": <atomic-type>               required
-        "value": <atomic-type>             optional
+        "key": <base-type>                 required
+        "value": <base-type>               optional
         "min": <integer>                   optional
         "max": <integer> or "unlimited"    optional
 
@@ -126,6 +162,56 @@ is represented by <database-schema>, as described below.
     If "value" is specified, the type is a map from type "key" to type
     "value".
 
+<base-type>
+
+    The type of a key or value in a database column.  Either an
+    <atomic-type> or a JSON object with the following members:
+
+        "type": <atomic-type>              required
+        "minInteger": <integer>            optional, integers only
+        "maxInteger": <integer>            optional, integers only
+        "minReal": <real>                  optional, reals only
+        "maxReal": <real>                  optional, reals only 
+        "reMatch": <string>                optional, strings only
+        "reComment": <string>              optional, strings only
+        "minLength": <integer>             optional, strings only
+        "maxLength": <integer>             optional, strings only
+        "refTable": <id>                   optional, uuids only
+
+    An <atomic-type> by itself is equivalent to a JSON object with a
+    single member "type" whose value is the <atomic-type>.
+
+    If "type" is "integer", then "minInteger" or "maxInteger" or both
+    may also be specified, restricting the valid integer range.  If
+    both are specified, then the maxInteger must be greater than or
+    equal to minInteger.
+
+    If "type" is "real", then "minReal" or "maxReal" or both may also
+    be specified, restricting the valid real range.  If both are
+    specified, then the maxReal must be greater than or equal to
+    minReal.
+
+    If "type" is "string", then:
+
+        "reMatch" may be a JavaScript (Perl 5-like) regular expression
+        that restricts the allowed values.  The regular expression
+        must match the entire string value, that is, it is treated as
+        if it begins with ^ and ends with $, regardless of whether it
+        really does.
+
+        If "reMatch" is specified, then "reComment" may be a string
+        that describes the allowed values, phrased so that it fits
+        into a sentence such as "This value must be...".
+
+        "minLength" and "maxLength" or both may be specified,
+        restricting the valid length of value strings.  If both are
+        specified, then maxLength must be greater than or equal to
+        minLength.  String length is measured in characters (not bytes
+        or UTF-16 code units).
+
+    The contraints on <base-type> are "immediate", enforced
+    immediately by each operation.
+
 <atomic-type>
 
     One of the strings "integer", "real", "boolean", "string", or
@@ -213,37 +299,8 @@ array corresponds to the same element of the "params" array.  The
       individual operations.  Some operations do not produce any
       results, in which case the object will have no members.
 
-    - A JSON object that contains an "error" member indicates that the
-      operation completed with an error.  The value of the "error"
-      member is a short string, specified in this document, that
-      broadly indicates the class of the error.  Besides the ones
-      listed for a specific operation, any operation may result in one
-      the following "error"s:
-
-      "error": "resources exhausted"
-
-          The operation or the transaction requires more resources
-          (memory, disk, CPU, etc.) than are currently available to
-          the database server.
-
-      "error": "syntax error"
-
-          The operation is not specified correctly: a required request
-          object member is missing, an unknown or unsupported request
-          object member is present, the operation attempts to act on a
-          table that does not exist, the operation modifies a
-          read-only table column, etc.
-
-      Database implementations may use "error" strings not specified
-      in this document to indicate errors that do not fit into any of
-      the specified categories.
-
-      Optionally, the object may include a "details" member, whose
-      value is a string that describes the error in more detail for
-      the benefit of a human user or administrator.  The object may
-      also have other members that describe the error in more detail.
-      This document does not specify the names or values of these
-      members.
+    - An <error>, which indicates that the operation completed with an
+      error.
 
     - A JSON null value indicates that the operation was not attempted
       because a prior operation failed.
@@ -604,6 +661,8 @@ Notation for the Wire Protocol
             difference, product, quotient, or remainder, respectively,
             of <column> and <value>.
 
+            Constraints on <column> are ignored when parsing <value>.
+
         boolean
         string
         uuid
@@ -615,7 +674,8 @@ Notation for the Wire Protocol
             Any <mutator> valid for the set's element type may be
             applied to the set, in which case the mutation is applied
             to each member of the set individually.  <value> must be a
-            scalar value of the same type as the set's element type.
+            scalar value of the same type as the set's element type,
+            except that contraints are ignored.
 
             If <mutator> is "insert", then each of the values in the
             set in <value> is added to <column> if it is not already
@@ -680,9 +740,22 @@ Result object members:
 
 Semantics:
 
-    Inserts "row" into "table".  If "row" does not specify values
-    for all the columns in "table", those columns receive default
-    values.
+    Inserts "row" into "table".
+
+    If "row" does not specify values for all the columns in "table",
+    those columns receive default values.  The default value for a
+    column depends on its type.  The default for a column whose <type>
+    specifies a "min" of 0 is an empty set or empty map.  Otherwise,
+    the default is a single value or a single key-value pair, whose
+    value(s) depend on its <atomic-type>:
+
+        - "integer" or "real": 0
+
+        - "boolean": false
+
+        - "string": "" (the empty string)
+
+        - "uuid": 00000000-0000-0000-0000-000000000000
 
     If "uuid-name" is not supplied, the new row receives a new,
     randomly generated UUID.
@@ -710,6 +783,13 @@ Errors:
         The same "uuid-name" appeared on an earlier "insert" operation
         within this transaction.
 
+    "error": "constraint violation"
+
+        One of the values in "row" does not satisfy the immediate
+        constraints for its column's <base-type>.  This error will
+        occur for columns that are not explicitly set by "row" if the
+        default value does not satisfy the column's constraints.
+
 select
 ......
 
@@ -772,6 +852,12 @@ Semantics:
     The "count" member of the result specifies the number of rows
     that matched.
 
+Errors:
+
+    "error": "constraint violation"
+
+        One of the values in "row" does not satisfy the immediate
+        constraints for its column's <base-type>.
 mutate
 ......
 
@@ -820,8 +906,9 @@ Errors:
 
         The mutation caused the column's value to violate a
         constraint, e.g. it caused a column to have more or fewer
-        values than are allowed or an arithmetic operation caused a
-        set or map to have duplicate elements.
+        values than are allowed, an arithmetic operation caused a set
+        or map to have duplicate elements, or it violated a constraint
+        specified by a column's <base-type>.
 
 delete
 ......
index 8605410..1e05e9f 100644 (file)
@@ -34,7 +34,7 @@ EXTRA_DIST += \
 # ovsdb-tool
 bin_PROGRAMS += ovsdb/ovsdb-tool
 ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
-ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(PCRE_LIBS)
 # ovsdb-tool.1
 man_MANS += ovsdb/ovsdb-tool.1
 DISTCLEANFILES += ovsdb/ovsdb-tool.1
@@ -43,7 +43,7 @@ EXTRA_DIST += ovsdb/ovsdb-tool.1.in
 # ovsdb-client
 bin_PROGRAMS += ovsdb/ovsdb-client
 ovsdb_ovsdb_client_SOURCES = ovsdb/ovsdb-client.c
-ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS) $(PCRE_LIBS)
 # ovsdb-client.1
 man_MANS += ovsdb/ovsdb-client.1
 DISTCLEANFILES += ovsdb/ovsdb-client.1
@@ -52,7 +52,7 @@ EXTRA_DIST += ovsdb/ovsdb-client.1.in
 # ovsdb-server
 sbin_PROGRAMS += ovsdb/ovsdb-server
 ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
-ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS) $(PCRE_LIBS)
 # ovsdb-server.1
 man_MANS += ovsdb/ovsdb-server.1
 DISTCLEANFILES += ovsdb/ovsdb-server.1
index 73dc9c2..0a1f6e4 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ ovsdb_column_create(const char *name, const char *comment,
     column->comment = comment ? xstrdup(comment) : NULL;
     column->mutable = mutable;
     column->persistent = persistent;
-    column->type = *type;
+    ovsdb_type_clone(&column->type, type);
 
     return column;
 }
@@ -46,6 +46,7 @@ ovsdb_column_create(const char *name, const char *comment,
 void
 ovsdb_column_destroy(struct ovsdb_column *column)
 {
+    ovsdb_type_destroy(&column->type);
     free(column->name);
     free(column->comment);
     free(column);
@@ -85,6 +86,9 @@ ovsdb_column_from_json(const struct json *json, const char *name,
                                    comment ? json_string(comment) : NULL,
                                    mutable ? json_boolean(mutable) : true,
                                    persistent, &type);
+
+    ovsdb_type_destroy(&type);
+
     return NULL;
 }
 
@@ -152,7 +156,7 @@ ovsdb_column_set_from_json(const struct json *json,
 
         /* XXX this is O(n**2) */
         for (i = 0; i < json->u.array.n; i++) {
-            struct ovsdb_column *column;
+            const struct ovsdb_column *column;
             const char *s;
 
             if (json->u.array.elems[i]->type != JSON_STRING) {
index f3f4300..abd7936 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -97,8 +97,8 @@ ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
          * n_max == 1?  (They would always be "false" if the value was
          * missing.) */
         if (!ovsdb_type_is_scalar(&type)
-            || (type.key_type != OVSDB_TYPE_INTEGER
-                && type.key_type != OVSDB_TYPE_REAL)) {
+            || (type.key.type != OVSDB_TYPE_INTEGER
+                && type.key.type != OVSDB_TYPE_REAL)) {
             char *s = ovsdb_type_to_english(&type);
             error = ovsdb_syntax_error(
                 json, NULL, "Type mismatch: \"%s\" operator may not be "
@@ -231,7 +231,7 @@ ovsdb_condition_evaluate(const struct ovsdb_row *row,
 
         if (ovsdb_type_is_scalar(type)) {
             int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
-                                              type->key_type);
+                                              type->key.type);
             switch (c->function) {
             case OVSDB_F_LT:
                 return cmp < 0;
index ba8351d..7cf45f6 100644 (file)
@@ -306,13 +306,37 @@ ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
     if (!error) {
         error = parse_row(parser, "row", table, x->symtab, &row, NULL);
     }
+    if (!error) {
+        /* Check constraints for columns not included in "row", in case the
+         * default values do not satisfy the constraints.  We could check only
+         * the columns that have their default values by supplying an
+         * ovsdb_column_set to parse_row() above, but I suspect that this is
+         * cheaper.  */
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_datum *datum = &row->fields[column->index];
+
+            /* If there are 0 keys or pairs, there's nothing to check.
+             * If there is 1, it might be a default value.
+             * If there are more, it can't be a default value, so the value has
+             * already been checked. */
+            if (datum->n == 1) {
+                error = ovsdb_datum_check_constraints(datum, &column->type);
+                if (error) {
+                    ovsdb_row_destroy(row);
+                    break;
+                }
+            }
+        }
+    }
     if (!error) {
         *ovsdb_row_get_uuid_rw(row) = row_uuid;
         ovsdb_txn_row_insert(x->txn, row);
         json_object_put(result, "uuid",
                         ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
                                             &ovsdb_type_uuid));
-        row = NULL;
     }
     return error;
 }
index 53e46f9..1b42280 100644 (file)
 #include "row.h"
 #include "table.h"
 
-enum mutate_error {
-    ME_OK,
-    ME_DOM,
-    ME_RANGE,
-    ME_COUNT,
-    ME_DUP
-};
-
 struct ovsdb_error *
 ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator)
 {
@@ -103,12 +95,12 @@ ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
                                   "No column %s in table %s.",
                                   column_name, ts->name);
     }
-    m->type = m->column->type;
+    ovsdb_type_clone(&m->type, &m->column->type);
 
     mutator_name = json_string(array->elems[1]);
     error = ovsdb_mutator_from_string(mutator_name, &m->mutator);
     if (error) {
-        return error;
+        goto exit;
     }
 
     /* Type-check and relax restrictions on 'type' if appropriate.  */
@@ -119,15 +111,17 @@ ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
     case OVSDB_M_DIV:
     case OVSDB_M_MOD:
         if ((!ovsdb_type_is_scalar(&m->type) && !ovsdb_type_is_set(&m->type))
-            || (m->type.key_type != OVSDB_TYPE_INTEGER
-                && m->type.key_type != OVSDB_TYPE_REAL)
+            || (m->type.key.type != OVSDB_TYPE_INTEGER
+                && m->type.key.type != OVSDB_TYPE_REAL)
             || (m->mutator == OVSDB_M_MOD
-                && m->type.key_type == OVSDB_TYPE_REAL)) {
+                && m->type.key.type == OVSDB_TYPE_REAL)) {
             return type_mismatch(m, json);
         }
+        ovsdb_base_type_clear_constraints(&m->type.key);
         m->type.n_min = m->type.n_max = 1;
-        return ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
-                                     symtab);
+        error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                      symtab);
+        break;
 
     case OVSDB_M_INSERT:
     case OVSDB_M_DELETE:
@@ -143,20 +137,28 @@ ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
         if (error && ovsdb_type_is_map(&m->type)
             && m->mutator == OVSDB_M_DELETE) {
             ovsdb_error_destroy(error);
-            m->type.value_type = OVSDB_TYPE_VOID;
+            m->type.value.type = OVSDB_TYPE_VOID;
             error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
                                           symtab);
         }
-        return error;
+        break;
+
+    default:
+        NOT_REACHED();
     }
 
-    NOT_REACHED();
+exit:
+    if (error) {
+        ovsdb_type_destroy(&m->type);
+    }
+    return error;
 }
 
 static void
 ovsdb_mutation_free(struct ovsdb_mutation *m)
 {
     ovsdb_datum_destroy(&m->arg, &m->type);
+    ovsdb_type_destroy(&m->type);
 }
 
 struct ovsdb_error *
@@ -218,7 +220,174 @@ ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *set)
     }
     free(set->mutations);
 }
+\f
+enum ovsdb_mutation_scalar_error {
+    ME_OK,
+    ME_DOM,
+    ME_RANGE
+};
+
+struct ovsdb_scalar_mutation {
+    int (*mutate_integer)(int64_t *x, int64_t y);
+    int (*mutate_real)(double *x, double y);
+    enum ovsdb_mutator mutator;
+};
+
+static const struct ovsdb_scalar_mutation add_mutation;
+static const struct ovsdb_scalar_mutation sub_mutation;
+static const struct ovsdb_scalar_mutation mul_mutation;
+static const struct ovsdb_scalar_mutation div_mutation;
+static const struct ovsdb_scalar_mutation mod_mutation;
+
+static struct ovsdb_error *
+ovsdb_mutation_scalar_error(enum ovsdb_mutation_scalar_error error,
+                            enum ovsdb_mutator mutator)
+{
+    switch (error) {
+    case ME_OK:
+        return OVSDB_BUG("unexpected success");
+
+    case ME_DOM:
+        return ovsdb_error("domain error", "Division by zero.");
+
+    case ME_RANGE:
+        return ovsdb_error("range error",
+                           "Result of \"%s\" operation is out of range.",
+                           ovsdb_mutator_to_string(mutator));
+
+    default:
+        return OVSDB_BUG("unexpected error");
+    }
+}
+
+static int
+check_real_range(double x)
+{
+    return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
+}
+
+static struct ovsdb_error *
+mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
+              const union ovsdb_atom *arg,
+              const struct ovsdb_scalar_mutation *mutation)
+{
+    const struct ovsdb_base_type *base = &dst_type->key;
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    if (base->type == OVSDB_TYPE_INTEGER) {
+        int64_t y = arg->integer;
+        for (i = 0; i < dst->n; i++) {
+            enum ovsdb_mutation_scalar_error me;
+
+            me = (mutation->mutate_integer)(&dst->keys[i].integer, y);
+            if (me != ME_OK) {
+                return ovsdb_mutation_scalar_error(me, mutation->mutator);
+            }
+        }
+    } else if (base->type == OVSDB_TYPE_REAL) {
+        double y = arg->real;
+        for (i = 0; i < dst->n; i++) {
+            double *x = &dst->keys[i].real;
+            enum ovsdb_mutation_scalar_error me;
+
+            me = (mutation->mutate_real)(x, y);
+            if (me == ME_OK) {
+                me = check_real_range(*x);
+            }
+            if (me != ME_OK) {
+                return ovsdb_mutation_scalar_error(me, mutation->mutator);
+            }
+        }
+    } else {
+        NOT_REACHED();
+    }
+
+    for (i = 0; i < dst->n; i++) {
+        error = ovsdb_atom_check_constraints(&dst->keys[i], base);
+        if (error) {
+            return error;
+        }
+    }
+
+    error = ovsdb_datum_sort(dst, dst_type);
+    if (error) {
+        ovsdb_error_destroy(error);
+        return ovsdb_error("constraint violation",
+                           "Result of \"%s\" operation contains duplicates.",
+                           ovsdb_mutator_to_string(mutation->mutator));
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_mutation_check_count(struct ovsdb_datum *dst,
+                           const struct ovsdb_type *dst_type)
+{
+    if (!ovsdb_datum_conforms_to_type(dst, dst_type)) {
+        char *s = ovsdb_type_to_english(dst_type);
+        struct ovsdb_error *e = ovsdb_error(
+            "constaint violation",
+            "Attempted to store %u elements in %s.", dst->n, s);
+        free(s);
+        return e;
+    }
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_execute(struct ovsdb_row *row,
+                           const struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        const struct ovsdb_mutation *m = &set->mutations[i];
+        struct ovsdb_datum *dst = &row->fields[m->column->index];
+        const struct ovsdb_type *dst_type = &m->column->type;
+        const struct ovsdb_datum *arg = &set->mutations[i].arg;
+        const struct ovsdb_type *arg_type = &m->type;
+        struct ovsdb_error *error;
+
+        switch (m->mutator) {
+        case OVSDB_M_ADD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &add_mutation);
+            break;
+
+        case OVSDB_M_SUB:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &sub_mutation);
+            break;
+
+        case OVSDB_M_MUL:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &mul_mutation);
+            break;
+
+        case OVSDB_M_DIV:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &div_mutation);
+            break;
+
+        case OVSDB_M_MOD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0], &mod_mutation);
+            break;
 
+        case OVSDB_M_INSERT:
+            ovsdb_datum_union(dst, arg, dst_type, false);
+            error = ovsdb_mutation_check_count(dst, dst_type);
+            break;
+
+        case OVSDB_M_DELETE:
+            ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
+            error = ovsdb_mutation_check_count(dst, dst_type);
+            break;
+        }
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+\f
 static int
 add_int(int64_t *x, int64_t y)
 {
@@ -296,12 +465,6 @@ mod_int(int64_t *x, int64_t y)
     return error;
 }
 
-static int
-check_real_range(double x)
-{
-    return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
-}
-
 static int
 add_double(double *x, double y)
 {
@@ -334,129 +497,22 @@ div_double(double *x, double y)
     }
 }
 
-static int
-mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
-              const union ovsdb_atom *arg,
-              int (*mutate_integer)(int64_t *x, int64_t y),
-              int (*mutate_real)(double *x, double y))
-{
-    struct ovsdb_error *error;
-    unsigned int i;
-
-    if (dst_type->key_type == OVSDB_TYPE_INTEGER) {
-        int64_t y = arg->integer;
-        for (i = 0; i < dst->n; i++) {
-            int error = mutate_integer(&dst->keys[i].integer, y);
-            if (error) {
-                return error;
-            }
-        }
-    } else if (dst_type->key_type == OVSDB_TYPE_REAL) {
-        double y = arg->real;
-        for (i = 0; i < dst->n; i++) {
-            double *x = &dst->keys[i].real;
-            int error = mutate_real(x, y);
-            if (!error) {
-                error = check_real_range(*x);
-            }
-            if (error) {
-                return error;
-            }
-        }
-    } else {
-        NOT_REACHED();
-    }
-
-    error = ovsdb_datum_sort(dst, dst_type);
-    if (error) {
-        ovsdb_error_destroy(error);
-        return ME_DUP;
-    }
-    return 0;
-}
-
-struct ovsdb_error *
-ovsdb_mutation_set_execute(struct ovsdb_row *row,
-                           const struct ovsdb_mutation_set *set)
-{
-    size_t i;
-
-    for (i = 0; i < set->n_mutations; i++) {
-        const struct ovsdb_mutation *m = &set->mutations[i];
-        struct ovsdb_datum *dst = &row->fields[m->column->index];
-        const struct ovsdb_type *dst_type = &m->column->type;
-        const struct ovsdb_datum *arg = &set->mutations[i].arg;
-        const struct ovsdb_type *arg_type = &m->type;
-        int error;
-
-        switch (m->mutator) {
-        case OVSDB_M_ADD:
-            error = mutate_scalar(dst_type, dst, &arg->keys[0],
-                                  add_int, add_double);
-            break;
-
-        case OVSDB_M_SUB:
-            error = mutate_scalar(dst_type, dst, &arg->keys[0],
-                                  sub_int, sub_double);
-            break;
-
-        case OVSDB_M_MUL:
-            error = mutate_scalar(dst_type, dst, &arg->keys[0],
-                                  mul_int, mul_double);
-            break;
-
-        case OVSDB_M_DIV:
-            error = mutate_scalar(dst_type, dst, &arg->keys[0],
-                                  div_int, div_double);
-            break;
-
-        case OVSDB_M_MOD:
-            error = mutate_scalar(dst_type, dst, &arg->keys[0],
-                                  mod_int, NULL);
-            break;
-
-        case OVSDB_M_INSERT:
-            ovsdb_datum_union(dst, arg, dst_type, false);
-            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
-            break;
-
-        case OVSDB_M_DELETE:
-            ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
-            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
-            break;
-        }
+static const struct ovsdb_scalar_mutation add_mutation = {
+    add_int, add_double, OVSDB_M_ADD
+};
 
-        switch (error) {
-        case 0:
-            break;
+static const struct ovsdb_scalar_mutation sub_mutation = {
+    sub_int, sub_double, OVSDB_M_SUB
+};
 
-        case ME_DOM:
-            return ovsdb_error("domain error", "Division by zero.");
-
-        case ME_RANGE:
-            return ovsdb_error("range error",
-                               "Result of \"%s\" operation is out of range.",
-                               ovsdb_mutator_to_string(m->mutator));
-
-        case ME_DUP:
-            return ovsdb_error("constraint violation",
-                               "Result of \"%s\" operation contains "
-                               "duplicates.",
-                               ovsdb_mutator_to_string(m->mutator));
-
-        case ME_COUNT: {
-            char *s = ovsdb_type_to_english(dst_type);
-            struct ovsdb_error *e = ovsdb_error(
-                "constaint violation",
-                "Attempted to store %u elements in %s.", dst->n, s);
-            free(s);
-            return e;
-        }
+static const struct ovsdb_scalar_mutation mul_mutation = {
+    mul_int, mul_double, OVSDB_M_MUL
+};
 
-        default:
-            return OVSDB_BUG("unexpected errno");
-        }
-    }
+static const struct ovsdb_scalar_mutation div_mutation = {
+    div_int, div_double, OVSDB_M_DIV
+};
 
-    return NULL;
-}
+static const struct ovsdb_scalar_mutation mod_mutation = {
+    mod_int, NULL, OVSDB_M_MOD
+};
index e7d3b50..a59f6de 100644 (file)
@@ -626,7 +626,7 @@ do_list_columns(int argc UNUSED, char *argv[])
             struct shash_node *column_node;
 
             SHASH_FOR_EACH (column_node, &ts->columns) {
-                struct ovsdb_column *column = column_node->data;
+                const struct ovsdb_column *column = column_node->data;
                 struct json *type = ovsdb_type_to_json(&column->type);
 
                 table_add_row(&t);
index 24387b8..12aa7d9 100755 (executable)
@@ -51,15 +51,6 @@ class DbSchema:
         idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
         return DbSchema(name, comment, tables, idlPrefix, idlHeader)
 
-    def toJson(self):
-        d = {"name": self.name,
-             "tables": {}}
-        for name, table in self.tables.iteritems():
-            d["tables"][name] = table.toJson()
-        if self.comment != None:
-            d["comment"] = self.comment
-        return d
-
 class TableSchema:
     def __init__(self, comment, columns):
         self.comment = comment
@@ -75,14 +66,6 @@ class TableSchema:
                 json, "column %s in %s" % (name, description))
         return TableSchema(comment, columns)
 
-    def toJson(self):
-        d = {"columns": {}}
-        for name, column in self.columns.iteritems():
-            d["columns"][name] = column.toJson()
-        if self.comment != None:
-            d["comment"] = self.comment
-        return d
-
 class ColumnSchema:
     def __init__(self, comment, type, persistent):
         self.comment = comment
@@ -99,49 +82,160 @@ class ColumnSchema:
         persistent = ephemeral != True
         return ColumnSchema(comment, type, persistent)
 
-    def toJson(self):
-        d = {"type": self.type.toJson()}
-        if self.persistent == False:
-            d["ephemeral"] = True
-        if self.comment != None:
-            d["comment"] = self.comment
-        return d
+def escapeCString(src):
+    dst = ""
+    for c in src:
+        if c in "\\\"":
+            dst += "\\" + c
+        elif ord(c) < 32:
+            if c == '\n':
+                dst += '\\n'
+            elif c == '\r':
+                dst += '\\r'
+            elif c == '\a':
+                dst += '\\a'
+            elif c == '\b':
+                dst += '\\b'
+            elif c == '\f':
+                dst += '\\f'
+            elif c == '\t':
+                dst += '\\t'
+            elif c == '\v':
+                dst += '\\v'
+            else:
+                dst += '\\%03o' % ord(c)
+        else:
+            dst += c
+    return dst
+
+class BaseType:
+    def __init__(self, type, refTable=None, minInteger=None, maxInteger=None,
+                 minReal=None, maxReal=None, reMatch=None, reComment=None,
+                 minLength=None, maxLength=None):
+        self.type = type
+        self.refTable = refTable
+        self.minInteger = minInteger
+        self.maxInteger = maxInteger
+        self.minReal = minReal
+        self.maxReal = maxReal
+        self.reMatch = reMatch
+        self.reComment = reComment
+        self.minLength = minLength
+        self.maxLength = maxLength
+
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return BaseType(json)
+        else:
+            atomicType = mustGetMember(json, 'type', [unicode], description)
+            refTable = getMember(json, 'refTable', [unicode], description)
+            minInteger = getMember(json, 'minInteger', [int, long], description)
+            maxInteger = getMember(json, 'maxInteger', [int, long], description)
+            minReal = getMember(json, 'minReal', [int, long, float], description)
+            maxReal = getMember(json, 'maxReal', [int, long, float], description)
+            reMatch = getMember(json, 'reMatch', [unicode], description)
+            reComment = getMember(json, 'reComment', [unicode], description)
+            minLength = getMember(json, 'minLength', [int], description)
+            maxLength = getMember(json, 'minLength', [int], description)
+            return BaseType(atomicType, refTable, minInteger, maxInteger, minReal, maxReal, reMatch, reComment, minLength, maxLength)
+
+    def toEnglish(self):
+        if self.type == 'uuid' and self.refTable:
+            return self.refTable
+        else:
+            return self.type
+
+    def toCType(self, prefix):
+        if self.refTable:
+            return "struct %s%s *" % (prefix, self.refTable.lower())
+        else:
+            return {'integer': 'int64_t ',
+                    'real': 'double ',
+                    'uuid': 'struct uuid ',
+                    'boolean': 'bool ',
+                    'string': 'char *'}[self.type]
+
+    def copyCValue(self, dst, src):
+        args = {'dst': dst, 'src': src}
+        if self.refTable:
+            return ("%(dst)s = %(src)s->header_.uuid;") % args
+        elif self.type == 'string':
+            return "%(dst)s = xstrdup(%(src)s);" % args
+        else:
+            return "%(dst)s = %(src)s;" % args
+
+    def initCDefault(self, var, isOptional):
+        if self.refTable:
+            return "%s = NULL;" % var
+        elif self.type == 'string' and not isOptional:
+            return "%s = \"\";" % var
+        else:
+            return {'integer': '%s = 0;',
+                    'real': '%s = 0.0;',
+                    'uuid': 'uuid_zero(&%s);',
+                    'boolean': '%s = false;',
+                    'string': '%s = NULL;'}[self.type] % var
+
+    def cInitBaseType(self, indent, var):
+        stmts = []
+        stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
+                var, self.type.upper()),)
+        if self.type == 'integer':
+            if self.minInteger != None:
+                stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
+            if self.maxInteger != None:
+                stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger))
+        elif self.type == 'real':
+            if self.minReal != None:
+                stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
+            if self.maxReal != None:
+                stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
+        elif self.type == 'string':
+            if self.reMatch != None:
+                if self.reComment != None:
+                    reComment = '"%s"' % escapeCString(self.reComment)
+                else:
+                    reComment = NULL
+                stmts.append('do_set_regex(&%s, "%s", %s);' % (
+                        var, escapeCString(self.reMatch), reComment))
+            if self.minLength != None:
+                stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
+            if self.maxLength != None:
+                stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+        return '\n'.join([indent + stmt for stmt in stmts])
 
 class Type:
-    def __init__(self, key, keyRefTable=None, value=None, valueRefTable=None,
-                 min=1, max=1):
+    def __init__(self, key, value=None, min=1, max=1):
         self.key = key
-        self.keyRefTable = keyRefTable
         self.value = value
-        self.valueRefTable = valueRefTable
         self.min = min
         self.max = max
     
     @staticmethod
     def fromJson(json, description):
         if type(json) == unicode:
-            return Type(json)
+            return Type(BaseType(json))
         else:
-            key = mustGetMember(json, 'key', [unicode], description)
+            keyJson = mustGetMember(json, 'key', [dict, unicode], description)
+            key = BaseType.fromJson(keyJson, 'key in %s' % description)
             keyRefTable = getMember(json, 'keyRefTable', [unicode], description)
-            value = getMember(json, 'value', [unicode], description)
-            valueRefTable = getMember(json, 'valueRefTable', [unicode], description)
+            if keyRefTable:
+                key.refTable = keyRefTable
+
+            valueJson = getMember(json, 'value', [dict, unicode], description)
+            if valueJson:
+                value = BaseType.fromJson(valueJson,
+                                          'value in %s' % description)
+                valueRefTable = getMember(json, 'valueRefTable', [unicode], description)
+                if valueRefTable:
+                    value.refTable = valueRefTable
+            else:
+                value = None
+
             min = getMember(json, 'min', [int], description, 1)
             max = getMember(json, 'max', [int, unicode], description, 1)
-            return Type(key, keyRefTable, value, valueRefTable, min, max)
-
-    def toJson(self):
-        if self.value == None and self.min == 1 and self.max == 1:
-            return self.key
-        else:
-            d = {"key": self.key}
-            if self.value != None:
-                d["value"] = self.value
-            if self.min != 1:
-                d["min"] = self.min
-            if self.max != 1:
-                d["max"] = self.max
-            return d
+            return Type(key, value, min, max)
 
     def isScalar(self):
         return self.min == 1 and self.max == 1 and not self.value
@@ -149,13 +243,17 @@ class Type:
     def isOptional(self):
         return self.min == 0 and self.max == 1
 
+    def isOptionalPointer(self):
+        return (self.min == 0 and self.max == 1 and not self.value
+                and (self.key.type == 'string' or self.key.refTable))
+
     def toEnglish(self):
-        keyName = atomicTypeToEnglish(self.key, self.keyRefTable)
+        keyName = self.key.toEnglish()
         if self.value:
-            valueName = atomicTypeToEnglish(self.value, self.valueRefTable)
+            valueName = self.value.toEnglish()
 
         if self.isScalar():
-            return atomicTypeToEnglish(self.key, self.keyRefTable)
+            return keyName
         elif self.isOptional():
             if self.value:
                 return "optional %s-%s pair" % (keyName, valueName)
@@ -177,12 +275,26 @@ class Type:
             else:
                 return "set of %s%s" % (quantity, keyName)
                 
+    def cDeclComment(self):
+        if self.min == 1 and self.max == 1 and self.key.type == "string":
+            return "\t/* Always nonnull. */"
+        else:
+            return ""
 
-def atomicTypeToEnglish(base, refTable):
-    if base == 'uuid' and refTable:
-        return refTable
-    else:
-        return base
+    def cInitType(self, indent, var):
+        initKey = self.key.cInitBaseType(indent, "%s.key" % var)
+        if self.value:
+            initValue = self.value.cInitBaseType(indent, "%s.value" % var)
+        else:
+            initValue = ('%sovsdb_base_type_init(&%s.value, '
+                         'OVSDB_TYPE_VOID);' % (indent, var))
+        initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
+        if self.max == "unlimited":
+            max = "UINT_MAX"
+        else:
+            max = self.max
+        initMax = "%s%s.n_max = %s;" % (indent, var, max)
+        return "\n".join((initKey, initValue, initMin, initMax))
 
 def parseSchema(filename):
     return DbSchema.fromJson(json.load(open(filename, "r")))
@@ -192,50 +304,6 @@ def annotateSchema(schemaFile, annotationFile):
     execfile(annotationFile, globals(), {"s": schemaJson})
     json.dump(schemaJson, sys.stdout)
 
-def cBaseType(prefix, type, refTable=None):
-    if type == 'uuid' and refTable:
-        return "struct %s%s *" % (prefix, refTable.lower())
-    else:
-        return {'integer': 'int64_t ',
-                'real': 'double ',
-                'uuid': 'struct uuid ',
-                'boolean': 'bool ',
-                'string': 'char *'}[type]
-
-def cCopyType(indent, dst, src, type, refTable=None):
-    args = {'indent': indent,
-            'dst': dst,
-            'src': src}
-    if type == 'uuid' and refTable:
-        return ("%(indent)s%(dst)s = %(src)s->header_.uuid;") % args
-    elif type == 'string':
-        return "%(indent)s%(dst)s = xstrdup(%(src)s);" % args
-    else:
-        return "%(indent)s%(dst)s = %(src)s;" % args
-
-def typeIsOptionalPointer(type):
-    return (type.min == 0 and type.max == 1 and not type.value
-            and (type.key == 'string'
-                 or (type.key == 'uuid' and type.keyRefTable)))
-
-def cDeclComment(type):
-    if type.min == 1 and type.max == 1 and type.key == "string":
-        return "\t/* Always nonnull. */"
-    else:
-        return ""
-
-def cInitDefault(var, type, refTable, isOptional):
-    if type == 'uuid' and refTable:
-        return "%s = NULL;" % var
-    elif type == 'string' and not isOptional:
-        return "%s = \"\";" % var
-    else:
-        return {'integer': '%s = 0;',
-                'real': '%s = 0.0;',
-                'uuid': 'uuid_zero(&%s);',
-                'boolean': '%s = false;',
-                'string': '%s = NULL;'}[type] % var
-
 def constify(cType, const):
     if (const
         and cType.endswith('*') and not cType.endswith('**')
@@ -251,26 +319,26 @@ def cMembers(prefix, columnName, column, const):
         pointer = ''
     else:
         singleton = False
-        if typeIsOptionalPointer(type):
+        if type.isOptionalPointer():
             pointer = ''
         else:
             pointer = '*'
 
     if type.value:
         key = {'name': "key_%s" % columnName,
-               'type': constify(cBaseType(prefix, type.key, type.keyRefTable) + pointer, const),
+               'type': constify(type.key.toCType(prefix) + pointer, const),
                'comment': ''}
         value = {'name': "value_%s" % columnName,
-                 'type': constify(cBaseType(prefix, type.value, type.valueRefTable) + pointer, const),
+                 'type': constify(type.value.toCType(prefix) + pointer, const),
                  'comment': ''}
         members = [key, value]
     else:
         m = {'name': columnName,
-             'type': constify(cBaseType(prefix, type.key, type.keyRefTable) + pointer, const),
-             'comment': cDeclComment(type)}
+             'type': constify(type.key.toCType(prefix) + pointer, const),
+             'comment': type.cDeclComment()}
         members = [m]
 
-    if not singleton and not typeIsOptionalPointer(type):
+    if not singleton and not type.isOptionalPointer():
         members.append({'name': 'n_%s' % columnName,
                         'type': 'size_t ',
                         'comment': ''})
@@ -291,14 +359,14 @@ def printCIDLHeader(schemaFile):
 #include "ovsdb-idl-provider.h"
 #include "uuid.h"''' % {'prefix': prefix.upper()}
 
-    for tableName, table in schema.tables.iteritems():
+    for tableName, table in sorted(schema.tables.iteritems()):
         structName = "%s%s" % (prefix, tableName.lower())
 
         print "\f"
         print "/* %s table. */" % tableName
         print "struct %s {" % structName
         print "\tstruct ovsdb_idl_row header_;"
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             print "\n\t/* %s column. */" % columnName
             for member in cMembers(prefix, columnName, column, False):
                 print "\t%(type)s%(name)s;%(comment)s" % member
@@ -306,7 +374,7 @@ def printCIDLHeader(schemaFile):
 
         # Column indexes.
         printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
-                   for columnName in table.columns]
+                   for columnName in sorted(table.columns)]
                   + ["%s_N_COLUMNS" % structName.upper()])
 
         print
@@ -328,11 +396,11 @@ void %(s)s_delete(const struct %(s)s *);
 struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
 ''' % {'s': structName, 'S': structName.upper()}
 
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             print 'void %(s)s_verify_%(c)s(const struct %(s)s *);' % {'s': structName, 'c': columnName}
 
         print
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
 
             print 'void %(s)s_set_%(c)s(const struct %(s)s *,' % {'s': structName, 'c': columnName},
             args = ['%(type)s%(name)s' % member for member
@@ -340,7 +408,7 @@ struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
             print '%s);' % ', '.join(args)
 
     # Table indexes.
-    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in schema.tables] + ["%sN_TABLES" % prefix.upper()])
+    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in sorted(schema.tables)] + ["%sN_TABLES" % prefix.upper()])
     print
     for tableName in schema.tables:
         print "#define %(p)stable_%(t)s (%(p)stable_classes[%(P)sTABLE_%(T)s])" % {
@@ -351,6 +419,7 @@ struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
     print "\nextern struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
 
     print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+    print "\nvoid %sinit(void);" % prefix
     print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
 
 def printEnum(members):
@@ -371,11 +440,30 @@ def printCIDLSource(schemaFile):
 
 #include <config.h>
 #include %s
+#include <assert.h>
 #include <limits.h>
-#include "ovsdb-data.h"''' % schema.idlHeader
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+
+static bool inited;
+
+static void UNUSED
+do_set_regex(struct ovsdb_base_type *base, const char *reMatch,
+             const char *reComment)
+{
+    struct ovsdb_error *error;
+
+    error = ovsdb_base_type_set_regex(base, reMatch, reComment);
+    if (error) {
+        char *s = ovsdb_error_to_string(error);
+        ovs_error(0, "%%s", s);
+        free(s);
+        ovsdb_error_destroy(error);
+    }
+}''' % schema.idlHeader
 
     # Cast functions.
-    for tableName, table in schema.tables.iteritems():
+    for tableName, table in sorted(schema.tables.iteritems()):
         structName = "%s%s" % (prefix, tableName.lower())
         print '''
 static struct %(s)s *
@@ -386,7 +474,7 @@ static struct %(s)s *
 ''' % {'s': structName}
 
 
-    for tableName, table in schema.tables.iteritems():
+    for tableName, table in sorted(schema.tables.iteritems()):
         structName = "%s%s" % (prefix, tableName.lower())
         print "\f"
         if table.comment != None:
@@ -395,7 +483,7 @@ static struct %(s)s *
             print "/* %s table. */" % (tableName)
 
         # Parse functions.
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             print '''
 static void
 %(s)s_parse_%(c)s(struct ovsdb_idl_row *row_, const struct ovsdb_datum *datum)
@@ -404,8 +492,6 @@ static void
                                                 'c': columnName}
 
             type = column.type
-            refKey = type.key == "uuid" and type.keyRefTable
-            refValue = type.value == "uuid" and type.valueRefTable
             if type.value:
                 keyVar = "row->key_%s" % columnName
                 valueVar = "row->value_%s" % columnName
@@ -413,24 +499,24 @@ static void
                 keyVar = "row->%s" % columnName
                 valueVar = None
 
-            if ((type.min == 1 and type.max == 1) or
-                typeIsOptionalPointer(type)):
+            if (type.min == 1 and type.max == 1) or type.isOptionalPointer():
                 print
+                print "    assert(inited);"
                 print "    if (datum->n >= 1) {"
-                if not refKey:
-                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key)
+                if not type.key.refTable:
+                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key.type)
                 else:
-                    print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+                    print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
 
                 if valueVar:
-                    if refValue:
-                        print "        %s = datum->values[0].%s;" % (valueVar, type.value)
+                    if type.value.refTable:
+                        print "        %s = datum->values[0].%s;" % (valueVar, type.value.type)
                     else:
-                        print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                        print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
                 print "    } else {"
-                print "        %s" % cInitDefault(keyVar, type.key, type.keyRefTable, type.min == 0)
+                print "        %s" % type.key.initCDefault(keyVar, type.min == 0)
                 if valueVar:
-                    print "        %s" % cInitDefault(valueVar, type.value, type.valueRefTable, type.min == 0)
+                    print "        %s" % type.value.initCDefault(valueVar, type.min == 0)
                 print "    }"
             else:
                 if type.max != 'unlimited':
@@ -440,24 +526,25 @@ static void
                     nMax = "datum->n"
                 print "    size_t i;"
                 print
+                print "    assert(inited);"
                 print "    %s = NULL;" % keyVar
                 if valueVar:
                     print "    %s = NULL;" % valueVar
                 print "    row->n_%s = 0;" % columnName
                 print "    for (i = 0; i < %s; i++) {" % nMax
                 refs = []
-                if refKey:
-                    print "        struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.keyRefTable.lower(), prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+                if type.key.refTable:
+                    print "        struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.key.refTable.lower(), prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
                     keySrc = "keyRow"
                     refs.append('keyRow')
                 else:
-                    keySrc = "datum->keys[i].%s" % type.key
-                if refValue:
-                    print "        struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.valueRefTable.lower(), prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                    keySrc = "datum->keys[i].%s" % type.key.type
+                if type.value and type.value.refTable:
+                    print "        struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.value.refTable.lower(), prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
                     valueSrc = "valueRow"
                     refs.append('valueRow')
                 elif valueVar:
-                    valueSrc = "datum->values[i].%s" % type.value
+                    valueSrc = "datum->values[i].%s" % type.value.type
                 if refs:
                     print "        if (%s) {" % ' && '.join(refs)
                     indent = "            "
@@ -478,15 +565,16 @@ static void
             print "}"
 
         # Unparse functions.
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             type = column.type
-            if (type.min != 1 or type.max != 1) and not typeIsOptionalPointer(type):
+            if (type.min != 1 or type.max != 1) and not type.isOptionalPointer():
                 print '''
 static void
 %(s)s_unparse_%(c)s(struct ovsdb_idl_row *row_)
 {
     struct %(s)s *row = %(s)s_cast(row_);
-''' % {'s': structName, 'c': columnName}
+
+    assert(inited);''' % {'s': structName, 'c': columnName}
                 if type.value:
                     keyVar = "row->key_%s" % columnName
                     valueVar = "row->value_%s" % columnName
@@ -540,11 +628,12 @@ struct %(s)s *
        'T': tableName.upper()}
 
         # Verify functions.
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             print '''
 void
 %(s)s_verify_%(c)s(const struct %(s)s *row)
 {
+    assert(inited);
     ovsdb_idl_txn_verify(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s]);
 }''' % {'s': structName,
         'S': structName.upper(),
@@ -552,7 +641,7 @@ void
         'C': columnName.upper()}
 
         # Set functions.
-        for columnName, column in table.columns.iteritems():
+        for columnName, column in sorted(table.columns.iteritems()):
             type = column.type
             print '\nvoid'
             members = cMembers(prefix, columnName, column, True)
@@ -573,20 +662,22 @@ void
             print "    struct ovsdb_datum datum;"
             if type.min == 1 and type.max == 1:
                 print
+                print "    assert(inited);"
                 print "    datum.n = 1;"
                 print "    datum.keys = xmalloc(sizeof *datum.keys);"
-                print cCopyType("    ", "datum.keys[0].%s" % type.key, keyVar, type.key, type.keyRefTable)
+                print "    " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
                 if type.value:
                     print "    datum.values = xmalloc(sizeof *datum.values);"
-                    print cCopyType("    ", "datum.values[0].%s" % type.value, valueVar, type.value, type.valueRefTable)
+                    print "    "+ type.value.copyCValue("datum.values[0].%s" % type.value.type, valueVar)
                 else:
                     print "    datum.values = NULL;"
-            elif typeIsOptionalPointer(type):
+            elif type.isOptionalPointer():
                 print
+                print "    assert(inited);"
                 print "    if (%s) {" % keyVar
                 print "        datum.n = 1;"
                 print "        datum.keys = xmalloc(sizeof *datum.keys);"
-                print cCopyType("        ", "datum.keys[0].%s" % type.key, keyVar, type.key, type.keyRefTable)
+                print "        " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
                 print "    } else {"
                 print "        datum.n = 0;"
                 print "        datum.keys = NULL;"
@@ -595,6 +686,7 @@ void
             else:
                 print "    size_t i;"
                 print
+                print "    assert(inited);"
                 print "    datum.n = %s;" % nVar
                 print "    datum.keys = xmalloc(%s * sizeof *datum.keys);" % nVar
                 if type.value:
@@ -602,9 +694,9 @@ void
                 else:
                     print "    datum.values = NULL;"
                 print "    for (i = 0; i < %s; i++) {" % nVar
-                print cCopyType("        ", "datum.keys[i].%s" % type.key, "%s[i]" % keyVar, type.key, type.keyRefTable)
+                print "        " + type.key.copyCValue("datum.keys[i].%s" % type.key.type, "%s[i]" % keyVar)
                 if type.value:
-                    print cCopyType("        ", "datum.values[i].%s" % type.value, "%s[i]" % valueVar, type.value, type.valueRefTable)
+                    print "        " + type.value.copyCValue("datum.values[i].%s" % type.value.type, "%s[i]" % valueVar)
                 print "    }"
             print "    ovsdb_idl_txn_write(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s], &datum);" \
                 % {'s': structName,
@@ -613,35 +705,29 @@ void
             print "}"
 
         # Table columns.
-        print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS] = {" % (
+        print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (
             structName, structName.upper())
-        for columnName, column in table.columns.iteritems():
-            type = column.type
-            
-            if type.value:
-                valueTypeName = type.value.upper()
-            else:
-                valueTypeName = "VOID"
-            if type.max == "unlimited":
-                max = "UINT_MAX"
-            else:
-                max = type.max
-            print """\
-    {"%(c)s",
-     {OVSDB_TYPE_%(kt)s, OVSDB_TYPE_%(vt)s, %(min)s, %(max)s},
-     %(s)s_parse_%(c)s,
-     %(s)s_unparse_%(c)s},""" % {'c': columnName,
-                                 's': structName,
-                                 'kt': type.key.upper(),
-                                 'vt': valueTypeName,
-                                 'min': type.min,
-                                 'max': max}
-        print "};"
+        print """
+static void\n%s_columns_init(void)
+{
+    struct ovsdb_idl_column *c;\
+""" % structName
+        for columnName, column in sorted(table.columns.iteritems()):
+            cs = "%s_col_%s" % (structName, columnName)
+            d = {'cs': cs, 'c': columnName, 's': structName}
+            print
+            print "    /* Initialize %(cs)s. */" % d
+            print "    c = &%(cs)s;" % d
+            print "    c->name = \"%(c)s\";" % d
+            print column.type.cInitType("    ", "c->type")
+            print "    c->parse = %(s)s_parse_%(c)s;" % d
+            print "    c->unparse = %(s)s_unparse_%(c)s;" % d
+        print "}"
 
     # Table classes.
     print "\f"
     print "struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
-    for tableName, table in schema.tables.iteritems():
+    for tableName, table in sorted(schema.tables.iteritems()):
         structName = "%s%s" % (prefix, tableName.lower())
         print "    {\"%s\"," % tableName
         print "     %s_columns, ARRAY_SIZE(%s_columns)," % (
@@ -654,6 +740,21 @@ void
     print "    %stable_classes, ARRAY_SIZE(%stable_classes)" % (prefix, prefix)
     print "};"
 
+    # global init function
+    print """
+void
+%sinit(void)
+{
+    if (inited) {
+        return;
+    }
+    inited = true;
+""" % prefix
+    for tableName, table in sorted(schema.tables.iteritems()):
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    %s_columns_init();" % structName
+    print "}"
+
 def ovsdb_escape(string):
     def escape(match):
         c = match.group(0)
index ab7e6c3..887751e 100644 (file)
@@ -149,8 +149,8 @@ query_db_remotes(const char *name_, const struct ovsdb *db,
                   name_, table_name, column_name);
     }
 
-    if (column->type.key_type != OVSDB_TYPE_STRING
-        || column->type.value_type != OVSDB_TYPE_VOID) {
+    if (column->type.key.type != OVSDB_TYPE_STRING
+        || column->type.value.type != OVSDB_TYPE_VOID) {
         ovs_fatal(0, "remote \"%s\": type of table \"%s\" column \"%s\" is "
                   "not string or set of strings",
                   name_, table_name, column_name);
index 96d3101..1901852 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -144,7 +144,7 @@ ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
     columns = json_object_create();
 
     SHASH_FOR_EACH (node, &ts->columns) {
-        struct ovsdb_column *column = node->data;
+        const struct ovsdb_column *column = node->data;
         if (node->name[0] != '_') {
             json_object_put(columns, column->name,
                             ovsdb_column_to_json(column));
index f862c33..8fa6c15 100644 (file)
@@ -214,7 +214,7 @@ tests_test_ovsdb_SOURCES = \
        tests/idltest.c \
        tests/idltest.h
 EXTRA_DIST += tests/uuidfilt.pl
-tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS) $(PCRE_LIBS)
 
 # idltest schema and IDL
 OVSIDL_BUILT +=        tests/idltest.c tests/idltest.h tests/idltest.ovsidl
index 0d35d17..06bff53 100644 (file)
@@ -459,17 +459,18 @@ AT_CHECK([RUN_OVS_VSCTL([list b])],
 cp stdout out2
 AT_CHECK([perl $srcdir/uuidfilt.pl out1 out2], [0], 
   [[<0>
-_uuid                (RO): <0>
-controller           (RO): []
-datapath_id          (RO): []
-datapath_type        (RO): ""
-external_ids         (RW): {}
-flood_vlans          (RW): []
-mirrors              (RO): []
-name                 (RO): "br0"
-netflow              (RO): []
-other_config         (RW): {}
-ports                (RO): []
+_uuid               : <0>
+controller          : []
+datapath_id         : []
+datapath_type       : ""
+external_ids        : {}
+flood_vlans         : []
+mirrors             : []
+name                : "br0"
+netflow             : []
+other_config        : {}
+ports               : []
+sflow               : []
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK(
   [RUN_OVS_VSCTL(
@@ -518,12 +519,12 @@ AT_CHECK([RUN_OVS_VSCTL([list n `cat netflow-uuid`])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([perl $srcdir/uuidfilt.pl netflow-uuid stdout], [0], 
   [[<0>
-_uuid                (RO): <0>
-active_timeout       (RW): 0
-add_id_to_interface  (RW): false
-engine_id            (RW): []
-engine_type          (RW): []
-targets              (RW): ["1.2.3.4:567"]
+_uuid               : <0>
+active_timeout      : 0
+add_id_to_interface : false
+engine_id           : []
+engine_type         : []
+targets             : ["1.2.3.4:567"]
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 AT_CHECK([RUN_OVS_VSCTL([list interx x])], 
   [1], [], [ovs-vsctl: unknown table "interx"
@@ -556,35 +557,23 @@ AT_CHECK([RUN_OVS_VSCTL([get b br0 external_ids:x])],
   [1], [], [ovs-vsctl: no key "x" in Bridge record "br0" column external_ids
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=-1])], 
-  [1], [], [ovs-vsctl: -1 is outside the valid range 1 to 4095 (inclusive)
+  [1], [], [ovs-vsctl: constraint violation: -1 is not in the valid range 0 to 4095 (inclusive)
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])], 
-  [1], [], [ovs-vsctl: 4096 is outside the valid range 1 to 4095 (inclusive)
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([set b br0 datapath_id=4096])], 
-  [1], [], [ovs-vsctl: datapath_id=4096: cannot modify read-only column datapath_id in table Bridge
+  [1], [], [ovs-vsctl: constraint violation: 4096 is not in the valid range 0 to 4095 (inclusive)
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])], 
-  [1], [], [ovs-vsctl: xyz is not valid (it does not match ^in-band|out-of-band$)
+  [1], [], [ovs-vsctl: constraint violation: "xyz" is not a either "in-band" or "out-of-band"
 ], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])], 
   [1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([add b br1 datapath_id x=y])], 
-  [1], [], [ovs-vsctl: cannot modify read-only column datapath_id in table Bridge
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force add b br1 datapath_id x y])], 
+AT_CHECK([RUN_OVS_VSCTL([add b br1 datapath_id x y])], 
   [1], [], [ovs-vsctl: "add" operation would put 2 values in column datapath_id of table Bridge but the maximum number is 1
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([remove b br1 datapath_id x=y])], 
-  [1], [], [ovs-vsctl: cannot modify read-only column datapath_id in table Bridge
-], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([remove n `cat netflow-uuid` targets '"1.2.3.4:567"'])], 
   [1], [], [ovs-vsctl: "remove" operation would put 0 values in column targets of table NetFlow but the minimun number is 1
 ], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([clear bri br0 netflow])], 
-  [1], [], [ovs-vsctl: cannot modify read-only column netflow in table Bridge
-], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([clear n `cat netflow-uuid` targets])], 
   [1], [], [ovs-vsctl: "clear" operation cannot be applied to column targets of table NetFlow, which is not allowed to be empty
 ], [OVS_VSCTL_CLEANUP])
@@ -610,17 +599,18 @@ AT_CHECK([RUN_OVS_VSCTL([--force create Bridge name=br0 -- list b])],
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], 
   [[<0>
-_uuid                (RO): <1>
-controller           (RO): []
-datapath_id          (RO): []
-datapath_type        (RO): ""
-external_ids         (RW): {}
-flood_vlans          (RW): []
-mirrors              (RO): []
-name                 (RO): "br0"
-netflow              (RO): []
-other_config         (RW): {}
-ports                (RO): []
+_uuid               : <1>
+controller          : []
+datapath_id         : []
+datapath_type       : ""
+external_ids        : {}
+flood_vlans         : []
+mirrors             : []
+name                : "br0"
+netflow             : []
+other_config        : {}
+ports               : []
+sflow               : []
 ]], [ignore], [test ! -e pid || kill `cat pid`])
 OVS_VSCTL_CLEANUP
 AT_CLEANUP
index e868477..03ec3b3 100644 (file)
@@ -1,4 +1,4 @@
-AT_BANNER([OVSDB -- atoms])
+AT_BANNER([OVSDB -- atoms without constraints])
 
 OVSDB_CHECK_POSITIVE([integer atom from JSON], 
   [[parse-atoms '["integer"]' \
@@ -152,29 +152,39 @@ OVSDB_CHECK_POSITIVE([uuid atom sorting],
     ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
   [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
 
-OVSDB_CHECK_NEGATIVE([real not acceptable integer JSON atom],
+OVSDB_CHECK_POSITIVE([real not acceptable integer JSON atom],
   [[parse-atoms '["integer"]' '[0.5]' ]],
-  [expected integer])
+  [syntax "0.5": syntax error: expected integer])
+
+dnl <C0> is not allowed anywhere in a UTF-8 string.
+dnl <ED A0 80> is a surrogate and not allowed in UTF-8.
+OVSDB_CHECK_POSITIVE([no invalid UTF-8 sequences in strings],
+  [parse-atoms '[["string"]]' \
+     '@<:@"m4_esyscmd([printf "\xc0"])"@:>@' \
+     '@<:@"m4_esyscmd([printf "\xed\xa0\x80"])"@:>@' \
+],
+  [constraint violation: "m4_esyscmd([printf "\xc0"])" is not a valid UTF-8 string: invalid UTF-8 sequence 0xc0
+constraint violation: "m4_esyscmd([printf "\xed\xa0\x80"])" is not a valid UTF-8 string: invalid UTF-8 sequence 0xed 0xa0])
 
 OVSDB_CHECK_NEGATIVE([real not acceptable integer string atom],
   [[parse-atom-strings '["integer"]' '0.5' ]],
   ["0.5" is not a valid integer])
 
-OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean JSON atom],
+OVSDB_CHECK_POSITIVE([string "true" not acceptable boolean JSON atom],
   [[parse-atoms '["boolean"]' '["true"]' ]],
-  [expected boolean])
+  [syntax ""true"": syntax error: expected boolean])
 
 OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean string atom],
   [[parse-atom-strings '["boolean"]' '"true"' ]],
   [""true"" is not a valid boolean (use "true" or "false")])
 
-OVSDB_CHECK_NEGATIVE([integer not acceptable string JSON atom],
+OVSDB_CHECK_POSITIVE([integer not acceptable string JSON atom],
   [[parse-atoms '["string"]' '[1]']],
-  [expected string])
+  [syntax "1": syntax error: expected string])
 
-OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as JSON array],
+OVSDB_CHECK_POSITIVE([uuid atom must be expressed as JSON array],
   [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
-  [[expected ["uuid", <string>]]])
+  [[syntax ""550e8400-e29b-41d4-a716-446655440000"": syntax error: expected ["uuid", <string>]]])
 
 OVSDB_CHECK_NEGATIVE([empty string atom must be quoted],
   [[parse-atom-strings '["string"]' '']],
@@ -187,6 +197,143 @@ OVSDB_CHECK_NEGATIVE([quotes must be balanced],
 OVSDB_CHECK_NEGATIVE([uuids must be valid],
   [parse-atom-strings '[["uuid"]]' '1234-5678'],
   ["1234-5678" is not a valid UUID])
+\f
+AT_BANNER([OVSDB -- atoms with constraints])
+
+OVSDB_CHECK_POSITIVE([integers >= 5], 
+  [[parse-atoms '[{"type": "integer", "minInteger": 5}]' \
+    '[0]' \
+    '[4]' \
+    '[5]' \
+    '[6]' \
+    '[12345]']], 
+  [constraint violation: 0 is less than minimum allowed value 5
+constraint violation: 4 is less than minimum allowed value 5
+5
+6
+12345])
+
+OVSDB_CHECK_POSITIVE([integers <= -1], 
+  [[parse-atoms '[{"type": "integer", "maxInteger": -1}]' \
+    '[0]' \
+    '[-1]' \
+    '[-2]' \
+    '[-123]']], 
+  [constraint violation: 0 is greater than maximum allowed value -1
+-1
+-2
+-123])
+
+OVSDB_CHECK_POSITIVE([integers in range -10 to 10], 
+  [[parse-atoms '[{"type": "integer", "minInteger": -10, "maxInteger": 10}]' \
+    '[-20]' \
+    '[-11]' \
+    '[-10]' \
+    '[-9]' \
+    '[1]' \
+    '[9]' \
+    '[10]' \
+    '[11]' \
+    '[123576]']], 
+  [constraint violation: -20 is not in the valid range -10 to 10 (inclusive)
+constraint violation: -11 is not in the valid range -10 to 10 (inclusive)
+-10
+-9
+1
+9
+10
+constraint violation: 11 is not in the valid range -10 to 10 (inclusive)
+constraint violation: 123576 is not in the valid range -10 to 10 (inclusive)])
+
+OVSDB_CHECK_POSITIVE([reals >= 5], 
+  [[parse-atoms '[{"type": "real", "minReal": 5}]' \
+    '[0]' \
+    '[4]' \
+    '[5]' \
+    '[6]' \
+    '[12345]']], 
+  [constraint violation: 0 is less than minimum allowed value 5
+constraint violation: 4 is less than minimum allowed value 5
+5
+6
+12345])
+
+OVSDB_CHECK_POSITIVE([reals <= -1], 
+  [[parse-atoms '[{"type": "real", "maxReal": -1}]' \
+    '[0]' \
+    '[-1]' \
+    '[-2]' \
+    '[-123]']], 
+  [constraint violation: 0 is greater than maximum allowed value -1
+-1
+-2
+-123])
+
+OVSDB_CHECK_POSITIVE([reals in range -10 to 10], 
+  [[parse-atoms '[{"type": "real", "minReal": -10, "maxReal": 10}]' \
+    '[-20]' \
+    '[-11]' \
+    '[-10]' \
+    '[-9]' \
+    '[1]' \
+    '[9]' \
+    '[10]' \
+    '[11]' \
+    '[123576]']], 
+  [constraint violation: -20 is not in the valid range -10 to 10 (inclusive)
+constraint violation: -11 is not in the valid range -10 to 10 (inclusive)
+-10
+-9
+1
+9
+10
+constraint violation: 11 is not in the valid range -10 to 10 (inclusive)
+constraint violation: 123576 is not in the valid range -10 to 10 (inclusive)])
+
+OVSDB_CHECK_POSITIVE([strings matching /(a(b)?)c?/],
+  [[parse-atoms '{"type": "string", "reMatch": "(a(b)?)?c?"}' \
+    '[""]' \
+    '["a"]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["ac"]' \
+    '["b"]' \
+    '["bc"]' \
+    '["c"]']],
+  [[""
+"a"
+"ab"
+"abc"
+"ac"
+constraint violation: "b" does not match regular expression /(a(b)?)?c?/
+constraint violation: "bc" does not match regular expression /(a(b)?)?c?/
+"c"]])
+
+OVSDB_CHECK_POSITIVE([strings at least 2 characters long],
+  [[parse-atoms '{"type": "string", "minLength": 2}' \
+    '[""]' \
+    '["a"]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["\ud834\udd1e"]']],
+  [[constraint violation: "" length 0 is less than minimum allowed length 2
+constraint violation: "a" length 1 is less than minimum allowed length 2
+"ab"
+"abc"
+constraint violation: "𝄞" length 1 is less than minimum allowed length 2]])
+
+OVSDB_CHECK_POSITIVE([strings no more than 2 characters long],
+  [[parse-atoms '{"type": "string", "maxLength": 2}' \
+    '[""]' \
+    '["a"]' \
+    '["ab"]' \
+    '["abc"]' \
+    '["\ud834\udd1e"]']],
+  [[""
+"a"
+"ab"
+constraint violation: "abc" length 3 is greater than maximum allowed length 2
+"𝄞"]])
 
 AT_BANNER([OSVDB -- simple data])
 
@@ -237,7 +384,7 @@ OVSDB_CHECK_POSITIVE([string string datum],
 ""
 "true"
 "\"\\/\b\f\n\r\t"])
-
+\f
 AT_BANNER([OVSDB -- set data])
 
 OVSDB_CHECK_POSITIVE([JSON optional boolean],
@@ -375,7 +522,7 @@ OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in string set],
      355ad037-f1da-40aa-b47c-ff9c7e8c6a38,
      7ef21525-0088-4a28-a418-5518413e43ea']],
   [set contains duplicate value])
-
+\f
 AT_BANNER([OVSDB -- map data])
 
 OVSDB_CHECK_POSITIVE([JSON map of 1 integer to boolean],
index 1113f94..bb9d6cf 100644 (file)
@@ -8,6 +8,14 @@ m4_define([ORDINAL_SCHEMA],
            "number": {"type": "integer"},
            "name": {"type": "string"}}}}}]])
 
+m4_define([CONSTRAINT_SCHEMA],
+  [[{"name": "constraints",
+     "tables": {
+       "constrained": {
+         "columns": {
+           "positive": {"type": {"key": {"type": "integer",
+                                         "minInteger": 1}}}}}}}]])
+
 # OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
 #
 # Runs "test-ovsdb execute" with the given SCHEMA and each of the
@@ -352,6 +360,22 @@ OVSDB_CHECK_EXECUTION([inequality wait with missing row],
        "rows": [{"name": "one", "number": 1}]}]]]],
   [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
 ]])
-])
+
+OVSDB_CHECK_EXECUTION([insert and update constraints],
+  [CONSTRAINT_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "constrained",
+       "row": {}}]]],
+   [[[{"op": "insert",
+       "table": "constrained",
+       "row": {"positive": -1}}]]],
+   [[[{"op": "update",
+       "table": "constrained",
+       "where": [],
+       "row": {"positive": -2}}]]]],
+  [[[{"details":"0 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"details":"-1 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"details":"-2 is less than minimum allowed value 1","error":"constraint violation"}]
+]])])
 
 EXECUTION_EXAMPLES
index b10aff1..cf4ca01 100644 (file)
@@ -282,6 +282,45 @@ row 5: domain error: Division by zero.
 row 6: domain error: Division by zero.
 ], [mutation])
 
+OVSDB_CHECK_POSITIVE([executing mutations on integers with constraints],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": {"type": "integer",
+                                         "minInteger": 0,
+                                         "maxInteger": 2}}}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: {"i":1}
+row 1: {"i":2}
+row 2: constraint violation: 3 is not in the valid range 0 to 2 (inclusive)
+
+mutation  1:
+row 0: constraint violation: -2 is not in the valid range 0 to 2 (inclusive)
+row 1: constraint violation: -1 is not in the valid range 0 to 2 (inclusive)
+row 2: {"i":0}
+
+mutation  2:
+row 0: no change
+row 1: constraint violation: 3 is not in the valid range 0 to 2 (inclusive)
+row 2: constraint violation: 6 is not in the valid range 0 to 2 (inclusive)
+
+mutation  3:
+row 0: no change
+row 1: {"i":0}
+row 2: {"i":0}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+], [mutation])
+
 OVSDB_CHECK_POSITIVE([executing mutations on reals],
   [[execute-mutations \
     '{"columns": {"r": {"type": "real"}}}' \
@@ -356,9 +395,45 @@ row 1: domain error: Division by zero.
 row 2: domain error: Division by zero.
 ], [mutation])
 
+OVSDB_CHECK_POSITIVE([executing mutations on reals with constraints],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": {"key": {"type": "real",
+                                         "minReal": -2.5,
+                                         "maxReal": 1.75}}}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]]]' \
+    '[{"r": 0},
+      {"r": -2.5},
+      {"r": 1.25}']]],
+  [mutation  0:
+row 0: {"r":0.5}
+row 1: {"r":-2}
+row 2: {"r":1.75}
+
+mutation  1:
+row 0: {"r":-1.5}
+row 1: constraint violation: -4 is not in the valid range -2.5 to 1.75 (inclusive)
+row 2: {"r":-0.25}
+
+mutation  2:
+row 0: no change
+row 1: constraint violation: -6.25 is not in the valid range -2.5 to 1.75 (inclusive)
+row 2: constraint violation: 3.125 is not in the valid range -2.5 to 1.75 (inclusive)
+
+mutation  3:
+row 0: no change
+row 1: {"r":-0.625}
+row 2: {"r":0.3125}
+], [mutation])
+
 OVSDB_CHECK_POSITIVE([executing mutations on integer sets],
   [[execute-mutations \
-    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '{"columns": {"i": {"type": {"key": {"type": "integer", 
+                                         "maxInteger": 5},
+                                 "min": 0, 
+                                 "max": "unlimited"}}}}' \
     '[[["i", "+=", 1]],
       [["i", "-=", 2]],
       [["i", "*=", 3]],
@@ -388,7 +463,7 @@ mutation  2:
 row 0: no change
 row 1: no change
 row 2: {"i":["set",[0,3]]}
-row 3: {"i":["set",[0,3,6]]}
+row 3: constraint violation: 6 is greater than maximum allowed value 5
 
 mutation  3:
 row 0: no change
@@ -429,7 +504,9 @@ row 3: {"i":["set",[0,1]]}
 
 OVSDB_CHECK_POSITIVE([executing mutations on real sets],
   [[execute-mutations \
-    '{"columns": {"r": {"type": {"key": "real", "min": 0, "max": "unlimited"}}}}' \
+    '{"columns": {"r": {"type": {"key": {"type": "real",
+                                         "maxReal": 6},
+                                 "min": 0, "max": "unlimited"}}}}' \
     '[[["r", "+=", 0.5]],
       [["r", "-=", 1.5]],
       [["r", "*=", 2.5]],
@@ -459,7 +536,7 @@ mutation  2:
 row 0: no change
 row 1: {"r":["set",[1.25]]}
 row 2: {"r":["set",[1.25,3.75]]}
-row 3: {"r":["set",[1.25,3.75,6.25]]}
+row 3: constraint violation: 6.25 is greater than maximum allowed value 6
 
 mutation  3:
 row 0: no change
@@ -648,7 +725,6 @@ row 2: no change
 row 3: {"u":["set",[]]}
 ]], [mutation])
 
-
 OVSDB_CHECK_POSITIVE([executing mutations on integer maps],
   [[execute-mutations \
     '{"columns": {"i": {"type": {"key": "integer", "value": "integer", "min": 0, "max": "unlimited"}}}}' \
index ebaffff..b7fddc7 100644 (file)
@@ -13,6 +13,68 @@ OVSDB_CHECK_POSITIVE([uuid],
 OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
   [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
 
+AT_BANNER([OVSDB -- base types])
+
+OVSDB_CHECK_POSITIVE([integer >= 5], 
+  [[parse-base-type '{"type": "integer", "minInteger": 5}' ]],
+  [{"minInteger":5,"type":"integer"}])
+OVSDB_CHECK_POSITIVE([integer <= 7], 
+  [[parse-base-type '{"type": "integer", "maxInteger": 7}' ]],
+  [{"maxInteger":7,"type":"integer"}])
+OVSDB_CHECK_POSITIVE([integer between -5 and 10], 
+  [[parse-base-type '{"type": "integer", "minInteger": -5, "maxInteger": 10}']],
+  [{"maxInteger":10,"minInteger":-5,"type":"integer"}])
+OVSDB_CHECK_NEGATIVE([integer max may not be less than min],
+  [[parse-base-type '{"type": "integer", "minInteger": 5, "maxInteger": 3}']],
+  [minInteger exceeds maxInteger])
+
+OVSDB_CHECK_POSITIVE([real >= -1.5], 
+  [[parse-base-type '{"type": "real", "minReal": -1.5}']],
+  [{"minReal":-1.5,"type":"real"}])
+OVSDB_CHECK_POSITIVE([real <= 1e5], 
+  [[parse-base-type '{"type": "real", "maxReal": 1e5}']],
+  [{"maxReal":100000,"type":"real"}])
+OVSDB_CHECK_POSITIVE([real between -2.5 and 3.75], 
+  [[parse-base-type '{"type": "real", "minReal": -2.5, "maxReal": 3.75}']],
+  [{"maxReal":3.75,"minReal":-2.5,"type":"real"}])
+OVSDB_CHECK_NEGATIVE([real max may not be less than min], 
+  [[parse-base-type '{"type": "real", "minReal": 555, "maxReal": 444}']],
+  [minReal exceeds maxReal])
+
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-base-type '[{"type": "boolean"}]' ]], ["boolean"])
+
+OVSDB_CHECK_POSITIVE([string reMatch], 
+  [[parse-base-type '{"type": "string", "reMatch": "\\d{3}-\\d{3}-\\d{4}"}']],
+  [{"reMatch":"\\d{3}-\\d{3}-\\d{4}","type":"string"}])
+OVSDB_CHECK_POSITIVE([string reMatch + reComment], 
+  [[parse-base-type '{"type": "string", "reMatch": "\\d{3}-\\d{3}-\\d{4}", "reComment": "US-style telephone number"}']],
+  [{"reComment":"US-style telephone number","reMatch":"\\d{3}-\\d{3}-\\d{4}","type":"string"}])
+OVSDB_CHECK_NEGATIVE([reMatch must be a valid JavaScript regexp],
+  [[parse-base-type '{"type": "string", "reMatch": "ab@:>@cd"}']],
+  [[test-ovsdb: invalid regular expression: "ab@:>@cd" is not a valid regular expression: @:>@ is an invalid data character in JavaScript compatibility mode]])
+
+OVSDB_CHECK_POSITIVE([string minLength], 
+  [[parse-base-type '{"type": "string", "minLength": 1}']],
+  [{"minLength":1,"type":"string"}])
+OVSDB_CHECK_POSITIVE([string maxLength], 
+  [[parse-base-type '{"type": "string", "maxLength": 5}']],
+  [{"maxLength":5,"type":"string"}])
+OVSDB_CHECK_POSITIVE([string minLength and maxLength], 
+  [[parse-base-type '{"type": "string", "minLength": 1, "maxLength": 5}']],
+  [{"maxLength":5,"minLength":1,"type":"string"}])
+OVSDB_CHECK_NEGATIVE([maxLength must not be less than minLength], 
+  [[parse-base-type '{"type": "string", "minLength": 5, "maxLength": 3}']],
+  [minLength exceeds maxLength])
+OVSDB_CHECK_NEGATIVE([maxLength must not be negative], 
+  [[parse-base-type '{"type": "string", "maxLength": -1}']],
+  [maxLength out of valid range 0 to 4294967295])
+
+OVSDB_CHECK_NEGATIVE([void is not a valid base-type],
+  [[parse-base-type '["void"]' ]], ["void" is not an atomic-type])
+OVSDB_CHECK_NEGATIVE(["type" member must be present],
+  [[parse-base-type '{}']], [Parsing ovsdb type failed: Required 'type' member is missing.])
+
 AT_BANNER([OVSDB -- simple types])
 
 OVSDB_CHECK_POSITIVE([simple integer], 
index a40b780..4b38ecb 100644 (file)
@@ -123,6 +123,8 @@ usage(void)
            "    open FILE with FLAGS, run COMMANDs\n"
            "  parse-atomic-type TYPE\n"
            "    parse TYPE as OVSDB atomic type, and re-serialize\n"
+           "  parse-base-type TYPE\n"
+           "    parse TYPE as OVSDB base type, and re-serialize\n"
            "  parse-type JSON\n"
            "    parse JSON as OVSDB type, and re-serialize\n"
            "  parse-atoms TYPE ATOM...\n"
@@ -332,6 +334,19 @@ do_parse_atomic_type(int argc UNUSED, char *argv[])
     print_and_free_json(ovsdb_atomic_type_to_json(type));
 }
 
+static void
+do_parse_base_type(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_base_type base;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_base_type_to_json(&base));
+    ovsdb_base_type_destroy(&base);
+}
+
 static void
 do_parse_type(int argc UNUSED, char *argv[])
 {
@@ -342,56 +357,63 @@ do_parse_type(int argc UNUSED, char *argv[])
     check_ovsdb_error(ovsdb_type_from_json(&type, json));
     json_destroy(json);
     print_and_free_json(ovsdb_type_to_json(&type));
+    ovsdb_type_destroy(&type);
 }
 
 static void
 do_parse_atoms(int argc, char *argv[])
 {
-    enum ovsdb_atomic_type type;
+    struct ovsdb_base_type base;
     struct json *json;
     int i;
 
     json = unbox_json(parse_json(argv[1]));
-    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
     json_destroy(json);
 
     for (i = 2; i < argc; i++) {
+        struct ovsdb_error *error;
         union ovsdb_atom atom;
 
         json = unbox_json(parse_json(argv[i]));
-        check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL));
+        error = ovsdb_atom_from_json(&atom, &base, json, NULL);
         json_destroy(json);
 
-        print_and_free_json(ovsdb_atom_to_json(&atom, type));
-
-        ovsdb_atom_destroy(&atom, type);
+        if (error) {
+            print_and_free_ovsdb_error(error);
+        } else {
+            print_and_free_json(ovsdb_atom_to_json(&atom, base.type));
+            ovsdb_atom_destroy(&atom, base.type);
+        }
     }
+    ovsdb_base_type_destroy(&base);
 }
 
 static void
 do_parse_atom_strings(int argc, char *argv[])
 {
-    enum ovsdb_atomic_type type;
+    struct ovsdb_base_type base;
     struct json *json;
     int i;
 
     json = unbox_json(parse_json(argv[1]));
-    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
     json_destroy(json);
 
     for (i = 2; i < argc; i++) {
         union ovsdb_atom atom;
         struct ds out;
 
-        die_if_error(ovsdb_atom_from_string(&atom, type, argv[i]));
+        die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i]));
 
         ds_init(&out);
-        ovsdb_atom_to_string(&atom, type, &out);
+        ovsdb_atom_to_string(&atom, base.type, &out);
         puts(ds_cstr(&out));
         ds_destroy(&out);
 
-        ovsdb_atom_destroy(&atom, type);
+        ovsdb_atom_destroy(&atom, base.type);
     }
+    ovsdb_base_type_destroy(&base);
 }
 
 static void
@@ -416,6 +438,7 @@ do_parse_data(int argc, char *argv[])
 
         ovsdb_datum_destroy(&datum, &type);
     }
+    ovsdb_type_destroy(&type);
 }
 
 static void
@@ -442,6 +465,7 @@ do_parse_data_strings(int argc, char *argv[])
 
         ovsdb_datum_destroy(&datum, &type);
     }
+    ovsdb_type_destroy(&type);
 }
 
 static enum ovsdb_atomic_type compare_atoms_atomic_type;
@@ -458,14 +482,14 @@ compare_atoms(const void *a_, const void *b_)
 static void
 do_sort_atoms(int argc UNUSED, char *argv[])
 {
-    enum ovsdb_atomic_type type;
+    struct ovsdb_base_type base;
     union ovsdb_atom *atoms;
     struct json *json, **json_atoms;
     size_t n_atoms;
     int i;
 
     json = unbox_json(parse_json(argv[1]));
-    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
     json_destroy(json);
 
     json = unbox_json(parse_json(argv[2]));
@@ -477,23 +501,24 @@ do_sort_atoms(int argc UNUSED, char *argv[])
     n_atoms = json->u.array.n;
     atoms = xmalloc(n_atoms * sizeof *atoms);
     for (i = 0; i < n_atoms; i++) {
-        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type,
+        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], &base,
                                                json->u.array.elems[i], NULL));
     }
     json_destroy(json);
 
     /* Sort atoms. */
-    compare_atoms_atomic_type = type;
+    compare_atoms_atomic_type = base.type;
     qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
 
     /* Convert internal representation back to JSON. */
     json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
     for (i = 0; i < n_atoms; i++) {
-        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type);
-        ovsdb_atom_destroy(&atoms[i], type);
+        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], base.type);
+        ovsdb_atom_destroy(&atoms[i], base.type);
     }
     print_and_free_json(json_array_create(json_atoms, n_atoms));
     free(atoms);
+    ovsdb_base_type_destroy(&base);
 }
 
 static void
@@ -1733,6 +1758,8 @@ do_idl(int argc, char *argv[])
     int error;
     int i;
 
+    idltest_init();
+
     idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
     if (argc > 2) {
         struct stream *stream;
@@ -1788,6 +1815,7 @@ do_idl(int argc, char *argv[])
 static struct command all_commands[] = {
     { "log-io", 2, INT_MAX, do_log_io },
     { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+    { "parse-base-type", 1, 1, do_parse_base_type },
     { "parse-type", 1, 1, do_parse_type },
     { "parse-atoms", 2, INT_MAX, do_parse_atoms },
     { "parse-atom-strings", 2, INT_MAX, do_parse_atom_strings },
index 5feb01c..b849231 100644 (file)
@@ -77,7 +77,7 @@ utilities_ovs_openflowd_LDADD = \
        $(SSL_LIBS)
 
 utilities_ovs_vsctl_SOURCES = utilities/ovs-vsctl.c vswitchd/vswitch-idl.c
-utilities_ovs_vsctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+utilities_ovs_vsctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS) $(PCRE_LIBS)
 
 utilities_ovs_wdt_SOURCES = utilities/ovs-wdt.c
 
index 84565b9..13985a6 100644 (file)
@@ -470,23 +470,23 @@ For a map column, without \fB\-\-if\-exists\fR it is an error if
 \fIcolumn\fR is not a map column or if \fIkey\fR is not specified,
 \fB\-\-if\-exists\fR has no effect.
 .
-.IP "[\fB\-\-force\fR] \fBset \fItable record column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+.IP "\fBset \fItable record column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
 Sets the value of each specified \fIcolumn\fR in the given
 \fIrecord\fR in \fItable\fR to \fIvalue\fR.  For map columns, a
 \fIkey\fR may optionally be specified, in which case the value
 associated with \fIkey\fR in that column is changed (or added, if none
 exists), instead of the entire map.
 .
-.IP "[\fB\-\-force\fR] \fBadd \fItable record column \fR[\fIkey\fB=\fR]\fIvalue\fR..."
+.IP "\fBadd \fItable record column \fR[\fIkey\fB=\fR]\fIvalue\fR..."
 Adds the specified value or key-value pair to \fIcolumn\fR in
 \fIrecord\fR in \fItable\fR.  If \fIcolumn\fR is a map, then \fIkey\fR
 is required, otherwise it is prohibited.  If \fIkey\fR already exists
 in a map column, then the current \fIvalue\fR is not replaced (use the
 \fBset\fR command to replace an existing value).
 .
-.IP "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIvalue\fR..."
-.IQ "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIkey\fR..."
-.IQ "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIkey\fB=\fR\fIvalue\fR..."
+.IP "\fBremove \fItable record column \fR\fIvalue\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fR..."
+.IQ "\fBremove \fItable record column \fR\fIkey\fB=\fR\fIvalue\fR..."
 Removes the specified values or key-value pairs from \fIcolumn\fR in
 \fIrecord\fR in \fItable\fR.  The first form applies to columns that
 are not maps: each specified \fIvalue\fR is removed from the column.
@@ -498,7 +498,7 @@ pair is removed only if both key and value match.
 It is not an error if the column does not contain the specified key or
 value or pair.
 .
-.IP "\fB[\fB\-\-force\fR] \fBclear\fR \fItable record column\fR..."
+.IP "\fBclear\fR \fItable record column\fR..."
 Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
 or empty map, as appropriate.  This command applies only to columns
 that are allowed to be empty.
index af90100..1eb6e0f 100644 (file)
@@ -22,7 +22,6 @@
 #include <float.h>
 #include <getopt.h>
 #include <inttypes.h>
-#include <regex.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdlib.h>
@@ -125,6 +124,7 @@ main(int argc, char *argv[])
     vlog_init();
     vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
     vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+    ovsrec_init();
 
     /* Log our arguments.  This is often valuable for debugging systems. */
     args = process_escape_args(argv);
@@ -138,8 +138,6 @@ main(int argc, char *argv[])
         time_alarm(timeout);
     }
 
-    /* Do basic command syntax checking. */
-
     /* Now execute the commands. */
     idl = the_idl = ovsdb_idl_create(db, &ovsrec_idl_class);
     seqno = ovsdb_idl_get_seqno(idl);
@@ -1482,141 +1480,6 @@ cmd_set_ssl(struct vsctl_context *ctx)
 \f
 /* Parameter commands. */
 
-/* POSIX extended regular expression for an 8-bit unsigned decimal integer. */
-#define OCTET_RE "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
-
-/* POSIX extended regular expression for an IP address. */
-#define IP_RE "("OCTET_RE"\\."OCTET_RE"\\."OCTET_RE"\\."OCTET_RE")"
-
-/* POSIX extended regular expression for a netmask. */
-#define NETMASK_RE                              \
-        "255.255.255."NETMASK_END_RE"|"         \
-        "255.255."NETMASK_END_RE".0|"           \
-        "255."NETMASK_END_RE".0.0|"             \
-        NETMASK_END_RE".0.0.0"
-#define NETMASK_END_RE "(255|254|252|248|240|224|192|128|0)"
-
-/* POSIX extended regular expression for an Ethernet address. */
-#define XX_RE "[0-9a-fA-F][0-9a-fA-F]"
-#define MAC_RE XX_RE":"XX_RE":"XX_RE":"XX_RE":"XX_RE":"XX_RE
-
-/* POSIX extended regular expression for a TCP or UDP port number. */
-#define PORT_RE                                 \
-    "([0-9]|"                                   \
-    "[1-9][0-9]|"                               \
-    "[1-9][0-9][0-9]|"                          \
-    "[1-9][0-9][0-9][0-9]|"                     \
-    "[1-5][0-9][0-9][0-9][0-9]|"                \
-    "6[1-4][0-9][0-9][0-9]|"                    \
-    "65[1-4][0-9][0-9]|"                        \
-    "655[1-2][0-9]|"                            \
-    "6553[1-5])"
-
-enum {
-    VSCF_READONLY = 1 << 0,
-    VSCF_HIDDEN = 1 << 1
-};
-
-struct vsctl_column {
-    struct ovsdb_idl_column *idl;
-    int flags;
-    const char *constraint;
-};
-
-static const struct vsctl_column bridge_columns[] = {
-    {&ovsrec_bridge_col_controller, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_datapath_id, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_datapath_type, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_external_ids, 0, NULL},
-    {&ovsrec_bridge_col_flood_vlans, 0, "[1,4095]"},
-    {&ovsrec_bridge_col_mirrors, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_name, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_netflow, VSCF_READONLY, NULL},
-    {&ovsrec_bridge_col_other_config, 0, NULL},
-    {&ovsrec_bridge_col_ports, VSCF_READONLY, NULL},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column controller_columns[] = {
-    {&ovsrec_controller_col_connection_mode, 0, "in-band|out-of-band"},
-    {&ovsrec_controller_col_controller_burst_limit, 0, "[25,]"},
-    {&ovsrec_controller_col_controller_rate_limit, 0, "[100,]"},
-    {&ovsrec_controller_col_discover_accept_regex, 0, NULL},
-    {&ovsrec_controller_col_discover_update_resolv_conf, 0, NULL},
-    {&ovsrec_controller_col_fail_mode, 0, "standalone|secure"},
-    {&ovsrec_controller_col_inactivity_probe, 0, "[5000,]"},
-    {&ovsrec_controller_col_local_gateway, 0, IP_RE},
-    {&ovsrec_controller_col_local_ip, 0, IP_RE},
-    {&ovsrec_controller_col_local_netmask, 0, NETMASK_RE},
-    {&ovsrec_controller_col_max_backoff, 0, "[1000,]"},
-    {&ovsrec_controller_col_target, 0, NULL},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column interface_columns[] = {
-    {&ovsrec_interface_col_external_ids, 0, NULL},
-    {&ovsrec_interface_col_ingress_policing_burst, 0, "[10,]"},
-    {&ovsrec_interface_col_ingress_policing_rate, 0, "[100,]"},
-    {&ovsrec_interface_col_mac, 0, MAC_RE},
-    {&ovsrec_interface_col_name, VSCF_READONLY, NULL},
-    {&ovsrec_interface_col_ofport, VSCF_READONLY, NULL},
-    {&ovsrec_interface_col_options, 0, NULL},
-    {&ovsrec_interface_col_type, VSCF_READONLY, NULL},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column mirror_columns[] = {
-    {&ovsrec_mirror_col_name, VSCF_READONLY, NULL},
-    {&ovsrec_mirror_col_output_port, 0, "Port"},
-    {&ovsrec_mirror_col_output_vlan, 0, "[1,4095]"},
-    {&ovsrec_mirror_col_select_dst_port, 0, "Port"},
-    {&ovsrec_mirror_col_select_src_port, 0, "Port"},
-    {&ovsrec_mirror_col_select_vlan, 0, "[1,4095]"},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column netflow_columns[] = {
-    {&ovsrec_netflow_col_active_timeout, 0, "[-1,]"},
-    {&ovsrec_netflow_col_add_id_to_interface, 0, NULL},
-    {&ovsrec_netflow_col_engine_id, 0, "[0,255]"},
-    {&ovsrec_netflow_col_engine_type, 0, "[0,255]"},
-    {&ovsrec_netflow_col_targets, 0, IP_RE":"PORT_RE},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column open_vswitch_columns[] = {
-    {&ovsrec_open_vswitch_col_bridges, VSCF_READONLY, NULL},
-    {&ovsrec_open_vswitch_col_controller, VSCF_READONLY, NULL},
-    {&ovsrec_open_vswitch_col_cur_cfg, VSCF_HIDDEN, NULL},
-    {&ovsrec_open_vswitch_col_managers, 0, "p?(ssl|tcp|unix):.*"},
-    {&ovsrec_open_vswitch_col_next_cfg, VSCF_HIDDEN, NULL},
-    {&ovsrec_open_vswitch_col_ssl, VSCF_READONLY, NULL},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column port_columns[] = {
-    {&ovsrec_port_col_bond_downdelay, 0, "[0,]"},
-    {&ovsrec_port_col_bond_fake_iface, VSCF_READONLY, NULL},
-    {&ovsrec_port_col_bond_updelay, 0, "[0,]"},
-    {&ovsrec_port_col_external_ids, 0, NULL},
-    {&ovsrec_port_col_fake_bridge, VSCF_READONLY, NULL},
-    {&ovsrec_port_col_interfaces, VSCF_READONLY, NULL},
-    {&ovsrec_port_col_mac, 0, MAC_RE},
-    {&ovsrec_port_col_name, VSCF_READONLY, NULL},
-    {&ovsrec_port_col_other_config, 0, NULL},
-    {&ovsrec_port_col_tag, 0, "[0,4095]"},
-    {&ovsrec_port_col_trunks, 0, "[0,4095]"},
-    {NULL, 0, NULL},
-};
-
-static const struct vsctl_column ssl_columns[] = {
-    {&ovsrec_ssl_col_bootstrap_ca_cert, 0, NULL},
-    {&ovsrec_ssl_col_ca_cert, 0, NULL},
-    {&ovsrec_ssl_col_certificate, 0, NULL},
-    {&ovsrec_ssl_col_private_key, 0, NULL},
-    {NULL, 0, NULL},
-};
-
 struct vsctl_row_id {
     const struct ovsdb_idl_table_class *table;
     const struct ovsdb_idl_column *name_column;
@@ -1625,16 +1488,15 @@ struct vsctl_row_id {
 
 struct vsctl_table_class {
     struct ovsdb_idl_table_class *class;
-    const struct vsctl_column *columns;
     struct vsctl_row_id row_ids[2];
 };
 
 static const struct vsctl_table_class tables[] = {
-    {&ovsrec_table_bridge, bridge_columns,
+    {&ovsrec_table_bridge,
      {{&ovsrec_table_bridge, &ovsrec_bridge_col_name, NULL},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_controller, controller_columns,
+    {&ovsrec_table_controller,
      {{&ovsrec_table_bridge,
        &ovsrec_bridge_col_name,
        &ovsrec_bridge_col_controller},
@@ -1642,32 +1504,32 @@ static const struct vsctl_table_class tables[] = {
        NULL,
        &ovsrec_open_vswitch_col_controller}}},
 
-    {&ovsrec_table_interface, interface_columns,
+    {&ovsrec_table_interface,
      {{&ovsrec_table_interface, &ovsrec_interface_col_name, NULL},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_mirror, mirror_columns,
+    {&ovsrec_table_mirror,
      {{&ovsrec_table_mirror, &ovsrec_mirror_col_name, NULL},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_netflow, netflow_columns,
+    {&ovsrec_table_netflow,
      {{&ovsrec_table_bridge,
        &ovsrec_bridge_col_name,
        &ovsrec_bridge_col_netflow},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_open_vswitch, open_vswitch_columns,
+    {&ovsrec_table_open_vswitch,
      {{&ovsrec_table_open_vswitch, NULL, NULL},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_port, port_columns,
+    {&ovsrec_table_port,
      {{&ovsrec_table_port, &ovsrec_port_col_name, NULL},
       {NULL, NULL, NULL}}},
 
-    {&ovsrec_table_ssl, ssl_columns,
+    {&ovsrec_table_ssl,
      {{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}},
 
-    {NULL, NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
 };
 
 static void
@@ -1751,7 +1613,7 @@ get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table,
         unsigned int best_score = 0;
 
         /* It might make sense to relax this assertion. */
-        assert(id->name_column->type.key_type == OVSDB_TYPE_STRING);
+        assert(id->name_column->type.key.type == OVSDB_TYPE_STRING);
 
         referrer = NULL;
         for (row = ovsdb_idl_first_row(ctx->idl, id->table);
@@ -1786,8 +1648,8 @@ get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table,
     if (id->uuid_column) {
         struct ovsdb_datum uuid;
 
-        assert(id->uuid_column->type.key_type == OVSDB_TYPE_UUID);
-        assert(id->uuid_column->type.value_type == OVSDB_TYPE_VOID);
+        assert(id->uuid_column->type.key.type == OVSDB_TYPE_UUID);
+        assert(id->uuid_column->type.value.type == OVSDB_TYPE_VOID);
 
         ovsdb_idl_txn_read(referrer, id->uuid_column, &uuid);
         if (uuid.n == 1) {
@@ -1838,22 +1700,20 @@ must_get_row(struct vsctl_context *ctx,
 
 static char *
 get_column(const struct vsctl_table_class *table, const char *column_name,
-           const struct vsctl_column **columnp)
+           const struct ovsdb_idl_column **columnp)
 {
-    const struct vsctl_column *column;
-    const struct vsctl_column *best_match = NULL;
+    const struct ovsdb_idl_column *best_match = NULL;
     unsigned int best_score = 0;
+    size_t i;
 
-    for (column = table->columns; column->idl; column++) {
-        if (!(column->flags & VSCF_HIDDEN)) {
-            unsigned int score = score_partial_match(column->idl->name,
-                                                     column_name);
-            if (score > best_score) {
-                best_match = column;
-                best_score = score;
-            } else if (score == best_score) {
-                best_match = NULL;
-            }
+    for (i = 0; i < table->class->n_columns; i++) {
+        const struct ovsdb_idl_column *column = &table->class->columns[i];
+        unsigned int score = score_partial_match(column->name, column_name);
+        if (score > best_score) {
+            best_match = column;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
         }
     }
 
@@ -1871,7 +1731,7 @@ get_column(const struct vsctl_table_class *table, const char *column_name,
 
 static char * WARN_UNUSED_RESULT
 parse_column_key_value(const char *arg, const struct vsctl_table_class *table,
-                       const struct vsctl_column **columnp,
+                       const struct ovsdb_idl_column **columnp,
                        char **keyp, char **valuep)
 {
     const char *p = arg;
@@ -1969,45 +1829,45 @@ cmd_get(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     for (i = 3; i < ctx->argc; i++) {
-        const struct vsctl_column *column;
+        const struct ovsdb_idl_column *column;
         struct ovsdb_datum datum;
         char *key_string;
 
         die_if_error(parse_column_key_value(ctx->argv[i], table,
                                             &column, &key_string, NULL));
 
-        ovsdb_idl_txn_read(row, column->idl, &datum);
+        ovsdb_idl_txn_read(row, column, &datum);
         if (key_string) {
             union ovsdb_atom key;
             unsigned int idx;
 
-            if (column->idl->type.value_type == OVSDB_TYPE_VOID) {
+            if (column->type.value.type == OVSDB_TYPE_VOID) {
                 vsctl_fatal("cannot specify key to get for non-map column %s",
-                            column->idl->name);
+                            column->name);
             }
 
             die_if_error(ovsdb_atom_from_string(&key,
-                                                column->idl->type.key_type,
+                                                &column->type.key,
                                                 key_string));
 
             idx = ovsdb_datum_find_key(&datum, &key,
-                                       column->idl->type.key_type);
+                                       column->type.key.type);
             if (idx == UINT_MAX) {
                 if (!if_exists) {
                     vsctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
                                 key_string, table->class->name, record_id,
-                                column->idl->name);
+                                column->name);
                 }
             } else {
                 ovsdb_atom_to_string(&datum.values[idx],
-                                     column->idl->type.value_type, out);
+                                     column->type.value.type, out);
             }
-            ovsdb_atom_destroy(&key, column->idl->type.key_type);
+            ovsdb_atom_destroy(&key, column->type.key.type);
         } else {
-            ovsdb_datum_to_string(&datum, &column->idl->type, out);
+            ovsdb_datum_to_string(&datum, &column->type, out);
         }
         ds_put_char(out, '\n');
-        ovsdb_datum_destroy(&datum, &column->idl->type);
+        ovsdb_datum_destroy(&datum, &column->type);
 
         free(key_string);
     }
@@ -2017,25 +1877,21 @@ static void
 list_record(const struct vsctl_table_class *table,
             const struct ovsdb_idl_row *row, struct ds *out)
 {
-    const struct vsctl_column *column;
+    size_t i;
 
-    ds_put_format(out, "%-20s (RO): "UUID_FMT"\n", "_uuid",
+    ds_put_format(out, "%-20s: "UUID_FMT"\n", "_uuid",
                   UUID_ARGS(&row->uuid));
-    for (column = table->columns; column->idl; column++) {
+    for (i = 0; i < table->class->n_columns; i++) {
+        const struct ovsdb_idl_column *column = &table->class->columns[i];
         struct ovsdb_datum datum;
 
-        if (column->flags & VSCF_HIDDEN) {
-            continue;
-        }
-
-        ovsdb_idl_txn_read(row, column->idl, &datum);
+        ovsdb_idl_txn_read(row, column, &datum);
 
-        ds_put_format(out, "%-20s (%s): ", column->idl->name,
-                      column->flags & VSCF_READONLY ? "RO" : "RW");
-        ovsdb_datum_to_string(&datum, &column->idl->type, out);
+        ds_put_format(out, "%-20s: ", column->name);
+        ovsdb_datum_to_string(&datum, &column->type, out);
         ds_put_char(out, '\n');
 
-        ovsdb_datum_destroy(&datum, &column->idl->type);
+        ovsdb_datum_destroy(&datum, &column->type);
     }
 }
 
@@ -2070,106 +1926,17 @@ cmd_list(struct vsctl_context *ctx)
     }
 }
 
-static void
-check_string_constraint(const struct ovsdb_datum *datum,
-                        const char *constraint)
-{
-    unsigned int i;
-    char *regex;
-    regex_t re;
-    int retval;
-
-    regex = xasprintf("^%s$", constraint);
-    retval = regcomp(&re, regex, REG_NOSUB | REG_EXTENDED);
-    if (retval) {
-        size_t length = regerror(retval, &re, NULL, 0);
-        char *buffer = xmalloc(length);
-        regerror(retval, &re, buffer, length);
-        vsctl_fatal("internal error compiling regular expression %s: %s",
-                    regex, buffer);
-    }
-
-    for (i = 0; i < datum->n; i++) {
-        const char *key = datum->keys[i].string;
-        if (regexec(&re, key, 0, NULL, 0)) {
-            vsctl_fatal("%s is not valid (it does not match %s)", key, regex);
-        }
-    }
-    free(regex);
-    regfree(&re);
-}
-
-static void
-check_integer_constraint(const struct ovsdb_datum *datum,
-                         const char *constraint)
-{
-    int64_t min, max;
-    unsigned int i;
-    int n = -1;
-
-    sscanf(constraint, "[%"SCNd64",%"SCNd64"]%n", &min, &max, &n);
-    if (n == -1) {
-        sscanf(constraint, "[%"SCNd64",]%n", &min, &n);
-        if (n == -1) {
-            sscanf(constraint, "[,%"SCNd64"]%n", &max, &n);
-            if (n == -1) {
-                VLOG_DBG("internal error: bad integer contraint \"%s\"",
-                         constraint);
-                return;
-            } else {
-                min = INT64_MIN;
-            }
-        } else {
-            max = INT64_MAX;
-        }
-    }
-
-    for (i = 0; i < datum->n; i++) {
-        int64_t value = datum->keys[i].integer;
-        if (value < min || value > max) {
-            if (max == INT64_MAX) {
-                vsctl_fatal("%"PRId64" is less than the minimum "
-                            "allowed value %"PRId64, value, min);
-            } else if (min == INT64_MIN) {
-                vsctl_fatal("%"PRId64" is greater than the maximum "
-                            "allowed value %"PRId64, value, max);
-            } else {
-                vsctl_fatal("%"PRId64" is outside the valid range %"PRId64" "
-                            "to %"PRId64" (inclusive)", value, min, max);
-            }
-        }
-    }
-}
-
-static void
-check_constraint(const struct ovsdb_datum *datum,
-                 const struct ovsdb_type *type, const char *constraint)
-{
-    if (constraint && datum->n) {
-        if (type->key_type == OVSDB_TYPE_STRING) {
-            check_string_constraint(datum, constraint);
-        } else if (type->key_type == OVSDB_TYPE_INTEGER) {
-            check_integer_constraint(datum, constraint);
-        }
-    }
-}
-
 static void
 set_column(const struct vsctl_table_class *table,
-           const struct ovsdb_idl_row *row,
-           const char *arg, bool force)
+           const struct ovsdb_idl_row *row, const char *arg)
 {
-    const struct vsctl_column *column;
+    const struct ovsdb_idl_column *column;
     char *key_string, *value_string;
     char *error;
 
     error = parse_column_key_value(arg, table, &column, &key_string,
                                    &value_string);
     die_if_error(error);
-    if (column->flags & VSCF_READONLY && !force) {
-        vsctl_fatal("%s: cannot modify read-only column %s in table %s",
-                    arg, column->idl->name, table->class->name);
-    }
     if (!value_string) {
         vsctl_fatal("%s: missing value", arg);
     }
@@ -2178,39 +1945,33 @@ set_column(const struct vsctl_table_class *table,
         union ovsdb_atom key, value;
         struct ovsdb_datum old, new;
 
-        if (column->idl->type.value_type == OVSDB_TYPE_VOID) {
+        if (column->type.value.type == OVSDB_TYPE_VOID) {
             vsctl_fatal("cannot specify key to set for non-map column %s",
-                        column->idl->name);
+                        column->name);
         }
 
-        die_if_error(ovsdb_atom_from_string(&key,
-                                            column->idl->type.key_type,
+        die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
                                             key_string));
-        die_if_error(ovsdb_atom_from_string(&value,
-                                            column->idl->type.value_type,
+        die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
                                             value_string));
 
         ovsdb_datum_init_empty(&new);
-        ovsdb_datum_add_unsafe(&new, &key, &value, &column->idl->type);
+        ovsdb_datum_add_unsafe(&new, &key, &value, &column->type);
 
-        ovsdb_atom_destroy(&key, column->idl->type.key_type);
-        ovsdb_atom_destroy(&value, column->idl->type.value_type);
+        ovsdb_atom_destroy(&key, column->type.key.type);
+        ovsdb_atom_destroy(&value, column->type.value.type);
 
-        ovsdb_idl_txn_read(row, column->idl, &old);
-        ovsdb_datum_union(&old, &new, &column->idl->type, true);
-        ovsdb_idl_txn_write(row, column->idl, &old);
+        ovsdb_idl_txn_read(row, column, &old);
+        ovsdb_datum_union(&old, &new, &column->type, true);
+        ovsdb_idl_txn_write(row, column, &old);
 
-        ovsdb_datum_destroy(&new, &column->idl->type);
+        ovsdb_datum_destroy(&new, &column->type);
     } else {
         struct ovsdb_datum datum;
 
-        die_if_error(ovsdb_datum_from_string(&datum, &column->idl->type,
+        die_if_error(ovsdb_datum_from_string(&datum, &column->type,
                                              value_string));
-        if (!force) {
-            check_constraint(&datum, &column->idl->type,
-                             column->constraint);
-        }
-        ovsdb_idl_txn_write(row, column->idl, &datum);
+        ovsdb_idl_txn_write(row, column, &datum);
     }
 
     free(key_string);
@@ -2220,7 +1981,6 @@ set_column(const struct vsctl_table_class *table,
 static void
 cmd_set(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     const char *table_name = ctx->argv[1];
     const char *record_id = ctx->argv[2];
     const struct vsctl_table_class *table;
@@ -2230,19 +1990,18 @@ cmd_set(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     for (i = 3; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i], force);
+        set_column(table, row, ctx->argv[i]);
     }
 }
 
 static void
 cmd_add(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     const char *table_name = ctx->argv[1];
     const char *record_id = ctx->argv[2];
     const char *column_name = ctx->argv[3];
     const struct vsctl_table_class *table;
-    const struct vsctl_column *column;
+    const struct ovsdb_idl_column *column;
     const struct ovsdb_idl_row *row;
     const struct ovsdb_type *type;
     struct ovsdb_datum old;
@@ -2251,13 +2010,9 @@ cmd_add(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     die_if_error(get_column(table, column_name, &column));
-    if (column->flags & VSCF_READONLY && !force) {
-        vsctl_fatal("cannot modify read-only column %s in table %s",
-                    column->idl->name, table->class->name);
-    }
 
-    type = &column->idl->type;
-    ovsdb_idl_txn_read(row, column->idl, &old);
+    type = &column->type;
+    ovsdb_idl_txn_read(row, column, &old);
     for (i = 4; i < ctx->argc; i++) {
         struct ovsdb_type add_type;
         struct ovsdb_datum add;
@@ -2273,21 +2028,20 @@ cmd_add(struct vsctl_context *ctx)
         vsctl_fatal("\"add\" operation would put %u %s in column %s of "
                     "table %s but the maximum number is %u",
                     old.n,
-                    type->value_type == OVSDB_TYPE_VOID ? "values" : "pairs",
-                    column->idl->name, table->class->name, type->n_max);
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_max);
     }
-    ovsdb_idl_txn_write(row, column->idl, &old);
+    ovsdb_idl_txn_write(row, column, &old);
 }
 
 static void
 cmd_remove(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     const char *table_name = ctx->argv[1];
     const char *record_id = ctx->argv[2];
     const char *column_name = ctx->argv[3];
     const struct vsctl_table_class *table;
-    const struct vsctl_column *column;
+    const struct ovsdb_idl_column *column;
     const struct ovsdb_idl_row *row;
     const struct ovsdb_type *type;
     struct ovsdb_datum old;
@@ -2296,13 +2050,9 @@ cmd_remove(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     die_if_error(get_column(table, column_name, &column));
-    if (column->flags & VSCF_READONLY && !force) {
-        vsctl_fatal("cannot modify read-only column %s in table %s",
-                    column->idl->name, table->class->name);
-    }
 
-    type = &column->idl->type;
-    ovsdb_idl_txn_read(row, column->idl, &old);
+    type = &column->type;
+    ovsdb_idl_txn_read(row, column, &old);
     for (i = 4; i < ctx->argc; i++) {
         struct ovsdb_type rm_type;
         struct ovsdb_datum rm;
@@ -2314,7 +2064,7 @@ cmd_remove(struct vsctl_context *ctx)
         error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]);
         if (error && ovsdb_type_is_map(&rm_type)) {
             free(error);
-            rm_type.value_type = OVSDB_TYPE_VOID;
+            rm_type.value.type = OVSDB_TYPE_VOID;
             die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]));
         }
         ovsdb_datum_subtract(&old, type, &rm, &rm_type);
@@ -2324,16 +2074,15 @@ cmd_remove(struct vsctl_context *ctx)
         vsctl_fatal("\"remove\" operation would put %u %s in column %s of "
                     "table %s but the minimun number is %u",
                     old.n,
-                    type->value_type == OVSDB_TYPE_VOID ? "values" : "pairs",
-                    column->idl->name, table->class->name, type->n_min);
+                    type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->name, table->class->name, type->n_min);
     }
-    ovsdb_idl_txn_write(row, column->idl, &old);
+    ovsdb_idl_txn_write(row, column, &old);
 }
 
 static void
 cmd_clear(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     const char *table_name = ctx->argv[1];
     const char *record_id = ctx->argv[2];
     const struct vsctl_table_class *table;
@@ -2343,24 +2092,21 @@ cmd_clear(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     for (i = 3; i < ctx->argc; i++) {
-        const struct vsctl_column *column;
+        const struct ovsdb_idl_column *column;
         const struct ovsdb_type *type;
         struct ovsdb_datum datum;
 
         die_if_error(get_column(table, ctx->argv[i], &column));
 
-        type = &column->idl->type;
-        if (column->flags & VSCF_READONLY && !force) {
-            vsctl_fatal("cannot modify read-only column %s in table %s",
-                        column->idl->name, table->class->name);
-        } else if (type->n_min > 0) {
+        type = &column->type;
+        if (type->n_min > 0) {
             vsctl_fatal("\"clear\" operation cannot be applied to column %s "
                         "of table %s, which is not allowed to be empty",
-                        column->idl->name, table->class->name);
+                        column->name, table->class->name);
         }
 
         ovsdb_datum_init_empty(&datum);
-        ovsdb_idl_txn_write(row, column->idl, &datum);
+        ovsdb_idl_txn_write(row, column, &datum);
     }
 }
 
@@ -2380,7 +2126,7 @@ cmd_create(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = ovsdb_idl_txn_insert(ctx->txn, table->class);
     for (i = 2; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i], force);
+        set_column(table, row, ctx->argv[i]);
     }
     ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
 }
@@ -2651,12 +2397,12 @@ static const struct vsctl_command_syntax all_commands[] = {
     /* Parameter commands. */
     {"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"},
     {"list", 1, INT_MAX, cmd_list, NULL, ""},
-    {"set", 3, INT_MAX, cmd_set, NULL, "--force"},
-    {"add", 4, INT_MAX, cmd_add, NULL, "--force"},
-    {"remove", 4, INT_MAX, cmd_remove, NULL, "--force"},
-    {"clear", 3, INT_MAX, cmd_clear, NULL, "--force"},
     {"create", 2, INT_MAX, cmd_create, post_create, "--force"},
     {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--force,--if-exists"},
+    {"set", 3, INT_MAX, cmd_set, NULL, ""},
+    {"add", 4, INT_MAX, cmd_add, NULL, ""},
+    {"remove", 4, INT_MAX, cmd_remove, NULL, ""},
+    {"clear", 3, INT_MAX, cmd_clear, NULL, ""},
 
     {NULL, 0, 0, NULL, NULL, NULL},
 };
index c38add6..d5f9056 100644 (file)
@@ -20,14 +20,15 @@ vswitchd_ovs_vswitchd_LDADD = \
        ofproto/libofproto.a \
        lib/libsflow.a \
        lib/libopenvswitch.a \
-       $(SSL_LIBS)
+       $(SSL_LIBS) \
+       $(PCRE_LIBS)
 
 vswitchd_ovs_brcompatd_SOURCES = \
        vswitchd/ovs-brcompatd.c \
        vswitchd/vswitch-idl.c \
        vswitchd/vswitch-idl.h
 
-vswitchd_ovs_brcompatd_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+vswitchd_ovs_brcompatd_LDADD = lib/libopenvswitch.a $(SSL_LIBS) $(PCRE_LIBS)
 
 EXTRA_DIST += \
        vswitchd/ovs-vswitchd.8.in \
index 585ab2c..9994fdb 100644 (file)
@@ -1154,6 +1154,7 @@ main(int argc, char *argv[])
     remote = parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
     process_init();
+    ovsrec_init();
 
     die_if_already_running();
     daemonize_start();
index ea997b0..7b0661e 100644 (file)
@@ -70,6 +70,7 @@ main(int argc, char *argv[])
     signal(SIGPIPE, SIG_IGN);
     sighup = signal_register(SIGHUP);
     process_init();
+    ovsrec_init();
 
     die_if_already_running();
     daemonize_start();
index 022d65f..ea67342 100644 (file)
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
        "flood_vlans": {
          "comment": "VLAN IDs of VLANs on which MAC address learning should be disabled, so that packets are flooded instead of being sent to specific ports that are believed to contain packets' destination MACs.  This should ordinarily be used to disable MAC learning on VLANs used for mirroring (RSPAN VLANs).  It may also be useful for debugging.",
-         "type": {"key": "integer", "min": 0, "max": 4096}
-}}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0, 
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}}}},
    "Port": {
      "comment": "A port within a Bridge.  May contain a single Interface or multiple (bonded) Interfaces.",
      "columns": {
          "type": {"key": "uuid", "min": 1, "max": "unlimited"}},
        "trunks": {
          "comment": "The 802.1Q VLAN(s) that this port trunks.  Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
-         "type": {"key": "integer", "min": 0, "max": 4096}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}},
        "tag": {
          "comment": "This port's implicitly tagged VLAN.  Should be empty if this is a trunk port.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 1}},
        "mac": {
-         "comment": "The MAC address to use for this port for the purpose of choosing the bridge's MAC address.  This column does not necessarily reflect the port's actual MAC address, nor will setting it change the port's actual MAC address.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "comment": "The MAC address to use for this port for the purpose of choosing the bridge's MAC address.  This column does not necessarily reflect the port's actual MAC address, nor will setting it change the port's actual MAC address.",
+         "type": {"key": {"type": "string",
+                          "reMatch": "[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}",
+                          "reComment": "an Ethernet address in the form XX:XX:XX:XX:XX:XX, where each X is a hex digit"},
+                  "min": 0, "max": 1}},
        "bond_updelay": {
          "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up.  Ignored for non-bonded ports.",
          "type": "integer"},
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
        "ingress_policing_rate": {
          "comment": "Maximum rate for data received on this interface, in kbps.  Set to 0 to disable policing.",
-         "type": "integer"},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0}}},
        "ingress_policing_burst": {
          "comment": "Maximum burst size for data received on this interface, in kb.  The default burst size if set to 0 is 1000 kb.",
-         "type": "integer"},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0}}},
        "mac": {
-         "comment": "Ethernet address to set for this interface.  If unset then the default MAC address is used.  May not be supported on all interfaces.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "comment": "Ethernet address to set for this interface.  If unset then the default MAC address is used.  May not be supported on all interfaces.",
+         "type": {"key": {"type": "string",
+                          "reMatch": "[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}",
+                          "reComment": "an Ethernet address in the form XX:XX:XX:XX:XX:XX, where each X is a hex digit"},
+                  "min": 0, "max": 1}},
        "external_ids": {
          "comment": "Key-value pairs that identify this interface's role in external systems.  The currently defined key-value pairs are: \"xs-vif-uuid\", the UUID of the Citrix XenServer VIF associated with this interface; \"xs-network-uuid\", the UUID of the Citrix XenServer network to which this interface is attached; \"xs-vif-vm-uuid\", the UUID of the Citrix XenServer VM to which this interface belongs; \"xs-vif-mac\", the value of the \"MAC\" field in the Citrix XenServer VIF record for this interface.",
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
          "comment": "Arbitrary identifier for the Mirror.",
          "type": "string"},
        "select_src_port": {
-         "comment": "Ports on which arriving packets are selected for mirroring.",
+         "comment": "Ports on which arriving packets are selected for mirroring.  If this column and select_dst_port are both empty, then all packets on all ports are selected for mirroring.",
          "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
        "select_dst_port": {
          "comment": "Ports on which departing packets are selected for mirroring.",
          "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
        "select_vlan": {
-         "comment": "VLANs on which packets are selected for mirroring.",
-         "type": {"key": "integer", "min": 0, "max": 4096}},
+         "comment": "VLANs on which packets are selected for mirroring.  An empty set selects packets on all VLANs.",
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 4096}},
        "output_port": {
          "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
          "type": {"key": "uuid", "min": 0, "max": 1}},
        "output_vlan": {
          "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
-         "type": {"key": "integer", "min": 0, "max": 1}}}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 1,
+                          "maxInteger": 4095},
+                  "min": 0, "max": 1}}}},
    "NetFlow": {
      "comment": "A NetFlow target.",
      "columns": {
        "targets": {
          "comment": "NetFlow targets in the form \"IP:PORT\".",
-         "type": {"key": "string", "min": 1, "max": "unlimited"}},
+         "type": {"key": {"type": "string",
+                          "reMatch": "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){3}:[0-9]{1,5}",
+                          "reComment": "an IP address followed by a UDP port number, separated by \":\""},
+                  "min": 1, "max": "unlimited"}},
        "engine_type": {
          "comment": "Engine type to use in NetFlow messages.  Defaults to datapath index if not specified.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 255},
+                  "min": 0, "max": 1}},
        "engine_id": {
          "comment": "Engine ID to use in NetFlow messages.  Defaults to datapath index if not specified.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 0,
+                          "maxInteger": 255},
+                  "min": 0, "max": 1}},
        "add_id_to_interface": {
          "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?",
          "type": "boolean"},
        "active_timeout": {
          "comment": "Active timeout interval, in seconds.  A value of 0 requests the default timeout; a negative value disables active timeouts.",
-         "type": "integer"}}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": -1}}}}},
    "sFlow": {
      "comment": "A sFlow target.",
      "columns": {
          "type": "string"},
        "max_backoff": {
          "comment": "Maximum number of milliseconds to wait between connection attempts.  Default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 1000},
+                  "min": 0, "max": 1}},
        "inactivity_probe": {
          "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message.  Default is implementation-specific.",
          "type": {"key": "integer", "min": 0, "max": 1}},
        "fail_mode": {
          "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "type": {"key": {"type": "string",
+                          "reMatch": "standalone|secure",
+                          "reComment": "either \"standalone\" or \"secure\""},
+                  "min": 0, "max": 1}},
        "discover_accept_regex": {
          "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
          "type": {"key": "string", "min": 0, "max": 1}},
          "type": {"key": "boolean", "min": 0, "max": 1}},
        "connection_mode": {
          "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "type": {"key": {"type": "string",
+                          "reMatch": "in-band|out-of-band",
+                          "reComment": "either \"in-band\" or \"out-of-band\""},
+                  "min": 0, "max": 1}},
        "local_ip": {
          "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "type": {"key": {"type": "string",
+                          "reMatch": "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){3}",
+                          "reComment": "an IP address"},
+                  "min": 0, "max": 1}},
        "local_netmask": {
          "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "type": {"key": {"type": "string", 
+                          "reMatch": "255\\.255\\.255\\.(255|254|252|248|240|224|192|128|0)|255\\.255\\.(255|254|252|248|240|224|192|128|0)\\.0|255\\.(255|254|252|248|240|224|192|128|0)\\.0\\.0|(255|254|252|248|240|224|192|128|0)\\.0\\.0\\.0",
+                          "reComment": "a netmask"},
+                  "min": 0, "max": 1}},
        "local_gateway": {
          "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
+         "type": {"key": {"type": "string",
+                          "reMatch": "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){3}",
+                          "reComment": "an IP address"},
+                  "min": 0, "max": 1}},
        "controller_rate_limit": {
          "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second.  If not specified, the default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
+         "type": {"key": {"type": "integer",
+                          "minInteger": 100},
+                  "min": 0, "max": 1}},
        "controller_burst_limit": {
          "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets.  If not specified, the default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}}}},
+         "type": {"key": {"type": "integer", 
+                          "minInteger": 25},
+                  "min": 0, "max": 1}}}},
    "SSL": {
      "comment": "SSL configuration for an Open_vSwitch.",
      "columns": {