ovsdb: Add support for referential integrity in the database itself.
authorBen Pfaff <blp@nicira.com>
Mon, 8 Feb 2010 22:09:41 +0000 (14:09 -0800)
committerBen Pfaff <blp@nicira.com>
Mon, 8 Feb 2010 22:16:19 +0000 (14:16 -0800)
24 files changed:
lib/ovsdb-data.c
lib/ovsdb-types.c
lib/ovsdb-types.h
ovsdb/SPECS
ovsdb/ovsdb-idlc.1
ovsdb/ovsdb-idlc.in
ovsdb/ovsdb.c
ovsdb/row.c
ovsdb/row.h
ovsdb/transaction.c
tests/automake.mk
tests/idltest.ann
tests/idltest.ovsschema
tests/ovs-vsctl.at
tests/ovsdb-execution.at
tests/ovsdb-idl.at
tests/ovsdb-schema.at [new file with mode: 0644]
tests/ovsdb-types.at
tests/ovsdb.at
tests/test-ovsdb.c
utilities/ovs-vsctl.8.in
utilities/ovs-vsctl.c
vswitchd/vswitch-idl.ann
vswitchd/vswitch.ovsschema

index e79f84997e023290d21f786914a166a604688f9c..76d046f17a38440e5fc25d91e7e780189cc8832a 100644 (file)
@@ -619,7 +619,10 @@ check_string_constraints(const char *s,
 
 /* 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. */
+ * constraints are met, otherwise an error that explains the violation.
+ *
+ * Checking UUID constraints is deferred to transaction commit time, so this
+ * function does nothing for UUID constraints. */
 struct ovsdb_error *
 ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
                              const struct ovsdb_base_type *base)
index ff819609f8e9734af2fb0557447b5b03665d2f50..1b5c7ed186f6321745360824a0eb02333bc964ee 100644 (file)
@@ -142,6 +142,8 @@ ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
         break;
 
     case OVSDB_TYPE_UUID:
+        base->u.uuid.refTableName = NULL;
+        base->u.uuid.refTable = NULL;
         break;
 
     case OVSDB_N_TYPES:
@@ -172,6 +174,9 @@ ovsdb_base_type_clone(struct ovsdb_base_type *dst,
         break;
 
     case OVSDB_TYPE_UUID:
+        if (dst->u.uuid.refTableName) {
+            dst->u.uuid.refTableName = xstrdup(dst->u.uuid.refTableName);
+        }
         break;
 
     case OVSDB_N_TYPES:
@@ -200,6 +205,7 @@ ovsdb_base_type_destroy(struct ovsdb_base_type *base)
             break;
 
         case OVSDB_TYPE_UUID:
+            free(base->u.uuid.refTableName);
             break;
 
         case OVSDB_N_TYPES:
@@ -263,7 +269,7 @@ ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
                 || base->u.string.maxLen != UINT_MAX);
 
     case OVSDB_TYPE_UUID:
-        return false;
+        return base->u.uuid.refTableName != NULL;
 
     case OVSDB_N_TYPES:
         NOT_REACHED();
@@ -411,6 +417,17 @@ ovsdb_base_type_from_json(struct ovsdb_base_type *base,
             error = ovsdb_syntax_error(json, NULL,
                                        "minLength exceeds maxLength");
         }
+    } else if (base->type == OVSDB_TYPE_UUID) {
+        const struct json *refTable;
+
+        refTable = ovsdb_parser_member(&parser, "refTable",
+                                       OP_ID | OP_OPTIONAL);
+        if (refTable) {
+            base->u.uuid.refTableName = xstrdup(refTable->u.string);
+            /* We can't set base->u.uuid.refTable here because we don't have
+             * enough context (we might not even be running in ovsdb-server).
+             * ovsdb_create() will set refTable later. */
+        }
     }
 
     if (error) {
@@ -485,6 +502,10 @@ ovsdb_base_type_to_json(const struct ovsdb_base_type *base)
         break;
 
     case OVSDB_TYPE_UUID:
+        if (base->u.uuid.refTableName) {
+            json_object_put_string(json, "refTable",
+                                   base->u.uuid.refTableName);
+        }
         break;
 
     case OVSDB_N_TYPES:
index 633b50f6616cce354a8bb4133ebd432edccdf8dd..b11f8277576b3dbec1ef709c4bc56f9232cc4a2b 100644 (file)
@@ -67,6 +67,11 @@ struct ovsdb_base_type {
             unsigned int minLen; /* minLength or 0. */
             unsigned int maxLen; /* maxLength or UINT_MAX. */
         } string;
+
+        struct ovsdb_uuid_constraints {
+            char *refTableName; /* Name of referenced table, or NULL. */
+            struct ovsdb_table *refTable; /* Referenced table, if available. */
+        } uuid;
     } u;
 };
 
@@ -79,7 +84,8 @@ struct ovsdb_base_type {
 #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 }
+#define OVSDB_BASE_UUID_INIT    { .type = OVSDB_TYPE_UUID,      \
+                                  .u.uuid = { NULL, NULL } }
 
 void ovsdb_base_type_init(struct ovsdb_base_type *, enum ovsdb_atomic_type);
 void ovsdb_base_type_clone(struct ovsdb_base_type *,
index 75e592bef606eef5729df4bf5e2012fe98e04f89..c1e3eca679743c7a729506b62346fce6447e6e03 100644 (file)
@@ -209,8 +209,14 @@ is represented by <database-schema>, as described below.
         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.
+    If "type" is "uuid", then "refTable", if present, must be the name
+    of a table within this database.  If "refTable" is set, the
+    allowed UUIDs are limited to UUIDs for rows in the named table.
+
+    "refTable" constraints are "deferred" constraints: they are
+    enforced only at transaction commit time (see the "transact"
+    request below).  The other contraints on <base-type> are
+    "immediate", enforced immediately by each operation.
 
 <atomic-type>
 
@@ -309,9 +315,19 @@ In general, "result" contains some number of successful results,
 possibly followed by an error, in turn followed by enough JSON null
 values to match the number of elements in "params".  There is one
 exception: if all of the operations succeed, but the results cannot be
-committed (e.g. due to I/O errors), then "result" will have one more
-element than "params", with the additional element describing the
-error.
+committed, then "result" will have one more element than "params",
+with the additional element an <error>.  The possible "error" strings
+include at least the following:
+
+    "error": "referential integrity violation"
+
+        When the commit was attempted, a column's value referenced the
+        UUID for a row that did not exist in the table named by the
+        column's <base-type> key or value "refTable".  (This can be
+        caused by inserting a row that references a nonexistent row,
+        by deleting a row that is still referenced by another row, by
+        specifying the UUID for a row in the wrong table, and other
+        ways.)
 
 If "params" contains one or more "wait" operations, then the
 transaction may take an arbitrary amount of time to complete.  The
index 1a7e8041d0a210f23d5d638513822a88b2a8a0bb..a2de7aec7dbcb5af51a943f6c1431968879fb747 100644 (file)
@@ -39,17 +39,6 @@ It will be output on an \fB#include\fR line in the source file
 generated by the C bindings.  It should include the bracketing
 \fB""\fR or \fB<>\fR.
 .
-.IP "\fB""\fBkeyRefTable\fR"" member of <type>"
-A <type> whose \fBkey\fR is \fB"uuid"\fR may have an additional member
-named \fB"keyRefTable"\fR, whose value is a table name.  This
-expresses the constraint that keys of the given <type> are UUIDs that
-reference rows in the named table.  This allows the IDL to supply a
-structure pointer in place of a raw UUID in its marshalled version of
-the given type.
-.
-.IP "\fB""valueRefTable""\fR member of <type>"
-Analogous to \fB"keyRefTable"\fR in meaning and effect, except that it
-applies to the \fB"value"\fR member of the <type>.
 .SS "Commands"
 .IP "\fBannotate\fI schema annotations\fR"
 Reads \fIschema\fR, which should be a file in JSON format (ordinarily
index 12aa7d9a00ed5eddea203efaf91fcffd3673e26e..b41113143372555d277db4993402c8aa8de7732b 100755 (executable)
@@ -203,6 +203,9 @@ class BaseType:
                 stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
             if self.maxLength != None:
                 stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+        elif self.type == 'uuid':
+            if self.refTable != None:
+                stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
         return '\n'.join([indent + stmt for stmt in stmts])
 
 class Type:
@@ -219,17 +222,11 @@ class Type:
         else:
             keyJson = mustGetMember(json, 'key', [dict, unicode], description)
             key = BaseType.fromJson(keyJson, 'key in %s' % description)
-            keyRefTable = getMember(json, 'keyRefTable', [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
 
index e95d23c415f608b88677064756c1c75096ec76e5..2dea507cbf1cd2426800e87b0dd7a74782b06fbb 100644 (file)
 
 #include "ovsdb.h"
 
+#include "column.h"
 #include "json.h"
 #include "ovsdb-error.h"
 #include "ovsdb-parser.h"
+#include "ovsdb-types.h"
 #include "table.h"
 #include "transaction.h"
 
@@ -79,6 +81,23 @@ ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
     return NULL;
 }
 
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_schema_check_ref_table(const struct ovsdb_column *column,
+                             const struct shash *tables,
+                             const struct ovsdb_base_type *base,
+                             const char *base_name)
+{
+    if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName
+        && !shash_find(tables, base->u.uuid.refTableName)) {
+        return ovsdb_syntax_error(NULL, NULL,
+                                  "column %s %s refers to undefined table %s",
+                                  column->name, base_name,
+                                  base->u.uuid.refTableName);
+    } else {
+        return NULL;
+    }
+}
+
 struct ovsdb_error *
 ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
 {
@@ -120,6 +139,29 @@ ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
 
         shash_add(&schema->tables, table->name, table);
     }
+
+    /* Validate that all refTables refer to the names of tables that exist. */
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        struct shash_node *node2;
+
+        SHASH_FOR_EACH (node2, &table->columns) {
+            struct ovsdb_column *column = node2->data;
+
+            error = ovsdb_schema_check_ref_table(column, &schema->tables,
+                                                 &column->type.key, "key");
+            if (!error) {
+                error = ovsdb_schema_check_ref_table(column, &schema->tables,
+                                                     &column->type.value,
+                                                     "value");
+            }
+            if (error) {
+                ovsdb_schema_destroy(schema);
+                return error;
+            }
+        }
+    }
+
     *schemap = schema;
     return 0;
 }
@@ -148,6 +190,18 @@ ovsdb_schema_to_json(const struct ovsdb_schema *schema)
     return json;
 }
 \f
+static void
+ovsdb_set_ref_table(const struct shash *tables,
+                    struct ovsdb_base_type *base)
+{
+    if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) {
+        struct ovsdb_table *table;
+
+        table = shash_find_data(tables, base->u.uuid.refTableName);
+        base->u.uuid.refTable = table;
+    }
+}
+
 struct ovsdb *
 ovsdb_create(struct ovsdb_schema *schema)
 {
@@ -166,6 +220,19 @@ ovsdb_create(struct ovsdb_schema *schema)
         shash_add(&db->tables, node->name, ovsdb_table_create(ts));
     }
 
+    /* Set all the refTables. */
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        struct shash_node *node2;
+
+        SHASH_FOR_EACH (node2, &table->columns) {
+            struct ovsdb_column *column = node2->data;
+
+            ovsdb_set_ref_table(&db->tables, &column->type.key);
+            ovsdb_set_ref_table(&db->tables, &column->type.value);
+        }
+    }
+
     return db;
 }
 
index 1b8194201df4b7d9d66aee126fa5ed866a135763..52c5ddb2147bd239856aac529ee8d32b84cabf73 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.
@@ -35,6 +35,7 @@ allocate_row(const struct ovsdb_table *table)
     struct ovsdb_row *row = xmalloc(row_size);
     row->table = (struct ovsdb_table *) table;
     row->txn_row = NULL;
+    row->n_refs = 0;
     return row;
 }
 
index 55c4f1426d872699e7cbd3c7719b1bbd6b18cab7..d468194a4fd9b23f08fdbaff0ff206d936ace1dc 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.
@@ -29,6 +29,13 @@ struct ovsdb_row {
     struct ovsdb_table *table;  /* Table to which this belongs. */
     struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */
     struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */
+
+    /* Number of refs to this row from other rows, in this table or other
+     * tables, through 'uuid' columns that have a 'refTable' constraint
+     * pointing to this table.  A row with nonzero 'n_refs' cannot be deleted.
+     * Updated and checked only at transaction commit. */
+    size_t n_refs;
+
     struct ovsdb_datum fields[];
 };
 
index 7ae52ef490004ad128854e8d0267a1d34a63a12d..137ae06b7c9bffe383ab36b96881b4ff208076ff 100644 (file)
@@ -131,12 +131,169 @@ ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
     ovsdb_row_destroy(txn_row->old);
 }
 
+static struct ovsdb_txn_row *
+find_txn_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_txn_row *txn_row;
+
+    if (!table->txn_table) {
+        return NULL;
+    }
+
+    HMAP_FOR_EACH_WITH_HASH (txn_row, struct ovsdb_txn_row, hmap_node,
+                             uuid_hash(uuid), &table->txn_table->txn_rows) {
+        const struct ovsdb_row *row;
+
+        row = txn_row->old ? txn_row->old : txn_row->new;
+        if (uuid_equals(uuid, ovsdb_row_get_uuid(row))) {
+            return txn_row;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_txn_adjust_atom_refs(const union ovsdb_atom *atoms, unsigned int n,
+                           const struct ovsdb_table *table,
+                           int delta, struct ovsdb_error **errorp)
+{
+    unsigned int i;
+
+    for (i = 0; i < n; i++) {
+        const struct uuid *uuid = &atoms[i].uuid;
+        struct ovsdb_txn_row *txn_row = find_txn_row(table, uuid);
+        if (txn_row) {
+            if (txn_row->old) {
+                txn_row->old->n_refs += delta;
+            }
+            if (txn_row->new) {
+                txn_row->new->n_refs += delta;
+            }
+        } else {
+            const struct ovsdb_row *row_ = ovsdb_table_get_row(table, uuid);
+            if (row_) {
+                struct ovsdb_row *row = (struct ovsdb_row *) row_;
+                row->n_refs += delta;
+            } else if (errorp) {
+                if (!*errorp) {
+                    *errorp = ovsdb_error("referential integrity violation",
+                                          "reference to nonexistent row "
+                                          UUID_FMT, UUID_ARGS(uuid));
+                }
+            } else {
+                NOT_REACHED();
+            }
+        }
+    }
+}
+
+static void
+ovsdb_txn_adjust_row_refs(const struct ovsdb_row *r,
+                          const struct ovsdb_column *column, int delta,
+                          struct ovsdb_error **errorp)
+{
+    const struct ovsdb_datum *field = &r->fields[column->index];
+    const struct ovsdb_type *type = &column->type;
+
+    if (type->key.type == OVSDB_TYPE_UUID && type->key.u.uuid.refTable) {
+        ovsdb_txn_adjust_atom_refs(field->keys, field->n,
+                                   type->key.u.uuid.refTable, delta, errorp);
+    }
+    if (type->value.type == OVSDB_TYPE_UUID && type->value.u.uuid.refTable) {
+        ovsdb_txn_adjust_atom_refs(field->values, field->n,
+                                   type->value.u.uuid.refTable, delta, errorp);
+    }
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_adjust_ref_counts__(struct ovsdb_txn *txn, int delta)
+{
+    struct ovsdb_txn_table *t;
+    struct ovsdb_error *error;
+
+    error = NULL;
+    LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+        struct ovsdb_table *table = t->table;
+        struct ovsdb_txn_row *r;
+
+        HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, &table->schema->columns) {
+                const struct ovsdb_column *column = node->data;
+
+                if (r->old) {
+                    ovsdb_txn_adjust_row_refs(r->old, column, -delta, NULL);
+                }
+                if (r->new) {
+                    ovsdb_txn_adjust_row_refs(r->new, column, delta, &error);
+                }
+            }
+        }
+    }
+    return error;
+}
+
+static void
+ovsdb_txn_rollback_counts(struct ovsdb_txn *txn)
+{
+    ovsdb_error_destroy(ovsdb_txn_adjust_ref_counts__(txn, -1));
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_commit_ref_counts(struct ovsdb_txn *txn)
+{
+    struct ovsdb_error *error = ovsdb_txn_adjust_ref_counts__(txn, 1);
+    if (error) {
+        ovsdb_txn_rollback_counts(txn);
+    }
+    return error;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+update_ref_counts(struct ovsdb_txn *txn)
+{
+    struct ovsdb_error *error;
+    struct ovsdb_txn_table *t;
+
+    error = ovsdb_txn_commit_ref_counts(txn);
+    if (error) {
+        return error;
+    }
+
+    LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+        struct ovsdb_txn_row *r;
+
+        HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+            if (!r->new && r->old->n_refs) {
+                error = ovsdb_error("referential integrity violation",
+                                    "cannot delete %s row "UUID_FMT" because "
+                                    "of %zu remaining reference(s)",
+                                    t->table->schema->name,
+                                    UUID_ARGS(ovsdb_row_get_uuid(r->old)),
+                                    r->old->n_refs);
+                ovsdb_txn_rollback_counts(txn);
+                return error;
+            }
+        }
+    }
+
+    return NULL;
+}
+
 struct ovsdb_error *
 ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
 {
     struct ovsdb_replica *replica;
     struct ovsdb_error *error;
 
+    error = update_ref_counts(txn);
+    if (error) {
+        ovsdb_txn_abort(txn);
+        return error;
+    }
+
     LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) {
         error = (replica->class->commit)(replica, txn, durable);
         if (error) {
@@ -215,6 +372,7 @@ ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
         struct ovsdb_row *rw_row;
 
         rw_row = ovsdb_row_clone(ro_row);
+        rw_row->n_refs = ro_row->n_refs;
         uuid_generate(ovsdb_row_get_version_rw(rw_row));
         rw_row->txn_row = ovsdb_txn_row_create(txn, table, ro_row, rw_row);
         hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
index 8fa6c158096a7baa117dd5445defd88cb6894f44..fe493fd367fadb51b5815ee212b193cfe2de0d52 100644 (file)
@@ -26,6 +26,7 @@ TESTSUITE_AT = \
        tests/ovsdb-column.at \
        tests/ovsdb-table.at \
        tests/ovsdb-row.at \
+       tests/ovsdb-schema.at \
        tests/ovsdb-condition.at \
        tests/ovsdb-mutation.at \
        tests/ovsdb-query.at \
index 2ffd1af6842f42a04fd76e91b67767eac4a80c7d..66e86373713d627fbd7cfd91dd8d22cf830a8e98 100644 (file)
@@ -7,7 +7,3 @@
 
 s["idlPrefix"] = "idltest_"
 s["idlHeader"] = "\"tests/idltest.h\""
-s["tables"]["link1"]["columns"]["k"]["type"]["keyRefTable"] = "link1"
-s["tables"]["link1"]["columns"]["ka"]["type"]["keyRefTable"] = "link1"
-s["tables"]["link1"]["columns"]["l2"]["type"]["keyRefTable"] = "link2"
-s["tables"]["link2"]["columns"]["l1"]["type"]["keyRefTable"] = "link1"
index 239a34335d4dd15f7e5897af113c83f2aa4a5839..545242bea87ad8dc32cc38340286d5d61ed3ae7c 100644 (file)
-{"name": "idltest",
- "tables": {
-   "simple": {
-     "columns": {
-       "i": {"type": "integer"},
-       "r": {"type": "real"},
-       "b": {"type": "boolean"},
-       "s": {"type": "string"},
-       "u": {"type": "uuid"},
-       "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
-       "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
-       "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
-       "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
-       "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}},
-   "link1": {
-     "columns": {
-       "i": {"type": "integer"},
-       "k": {"type": {"key": "uuid"}},
-       "ka": {"type": {"key": "uuid",
-                       "min": 0, "max": "unlimited"}},
-       "l2": {"type": {"key": "uuid", "min": 0, "max": 1}}}},
-   "link2": {
-     "columns": {
-       "i": {"type": "integer"},
-       "l1": {"type": {"key": "uuid", "min": 0, "max": 1}}}}}}
+{
+  "name": "idltest", 
+  "tables": {
+    "link1": {
+      "columns": {
+        "i": {
+          "type": "integer"
+        }, 
+        "k": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            }
+          }
+        }, 
+        "ka": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            },
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "l2": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link2"
+            },
+            "min": 0
+          }
+        }
+      }
+    }, 
+    "link2": {
+      "columns": {
+        "i": {
+          "type": "integer"
+        }, 
+        "l1": {
+          "type": {
+            "key": {
+              "type": "uuid",
+              "refTable": "link1"
+            },
+            "min": 0
+          }
+        }
+      }
+    }, 
+    "simple": {
+      "columns": {
+        "b": {
+          "type": "boolean"
+        }, 
+        "ba": {
+          "type": {
+            "key": "boolean", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "i": {
+          "type": "integer"
+        }, 
+        "ia": {
+          "type": {
+            "key": "integer", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "r": {
+          "type": "real"
+        }, 
+        "ra": {
+          "type": {
+            "key": "real", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "s": {
+          "type": "string"
+        }, 
+        "sa": {
+          "type": {
+            "key": "string", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }, 
+        "u": {
+          "type": "uuid"
+        }, 
+        "ua": {
+          "type": {
+            "key": "uuid", 
+            "max": "unlimited", 
+            "min": 0
+          }
+        }
+      }
+    }
+  }
+}
index 06bff53b579a00eab71d82d558eb8f854ef7f9fb..103b17ae00cda8429b762d8b63609691705aa787 100644 (file)
@@ -451,7 +451,7 @@ AT_BANNER([ovs-vsctl unit tests -- database commands])
 AT_SETUP([database commands -- positive checks])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])], 
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])], 
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 cp stdout out1
 AT_CHECK([RUN_OVS_VSCTL([list b])], 
@@ -496,7 +496,7 @@ AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config 'datapath_id="0123456789ab"'
 AT_CHECK([RUN_OVS_VSCTL([clear br br0 external-ids -- get br br0 external_ids])], 
   [0], [{}
 ], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force destroy b br0])], 
+AT_CHECK([RUN_OVS_VSCTL([destroy b br0])], 
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([list b])], 
   [0], [], [], [OVS_VSCTL_CLEANUP])
@@ -506,13 +506,13 @@ AT_CLEANUP
 AT_SETUP([database commands -- negative checks])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])], 
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])], 
   [0], [ignore], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([add-br br1])], 
   [0], [ignore], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([set-controller br1 tcp:127.0.0.1])], 
   [0], [ignore], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force create n targets='"1.2.3.4:567"'])], 
+AT_CHECK([RUN_OVS_VSCTL([create n targets='"1.2.3.4:567"'])], 
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 cp stdout netflow-uuid
 AT_CHECK([RUN_OVS_VSCTL([list n `cat netflow-uuid`])],
@@ -577,13 +577,7 @@ AT_CHECK([RUN_OVS_VSCTL([remove n `cat netflow-uuid` targets '"1.2.3.4:567"'])],
 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])
-AT_CHECK([RUN_OVS_VSCTL([create b name=br2])], 
-  [1], [], [ovs-vsctl: "create" requires --force
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([destroy b br0])], 
-  [1], [], [ovs-vsctl: "destroy" requires --force
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force destroy b br2])], 
+AT_CHECK([RUN_OVS_VSCTL([destroy b br2])], 
   [1], [], [ovs-vsctl: no row "br2" in table Bridge
 ], [OVS_VSCTL_CLEANUP])
 OVS_VSCTL_CLEANUP
@@ -595,7 +589,7 @@ dnl The bug is documented in ovs-vsctl.8.
 AT_SETUP([created row UUID is wrong in same execution])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create Bridge name=br0 -- list b])], 
+AT_CHECK([RUN_OVS_VSCTL([create Bridge name=br0 -- list b])], 
   [0], [stdout], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], 
   [[<0>
index bb9d6cf434dc54d8eaa52b3de96c9e7c9272570d..334e20848dc542a193ae92446af917e13db6d827 100644 (file)
@@ -11,6 +11,20 @@ m4_define([ORDINAL_SCHEMA],
 m4_define([CONSTRAINT_SCHEMA],
   [[{"name": "constraints",
      "tables": {
+       "a": {
+         "columns": {
+           "a": {"type": "integer"},
+           "a2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+                            "min": 0, "max": "unlimited"}},
+           "a2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+                            "min": 0, "max": "unlimited"}}}},
+       "b": {
+         "columns": {
+           "b": {"type": "integer"},
+           "b2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+                            "min": 0, "max": "unlimited"}},
+           "b2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+                            "min": 0, "max": "unlimited"}}}},
        "constrained": {
          "columns": {
            "positive": {"type": {"key": {"type": "integer",
@@ -376,6 +390,104 @@ OVSDB_CHECK_EXECUTION([insert and update constraints],
   [[[{"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"}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- simple],
+  [CONSTRAINT_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "b",
+       "row": {"b": 1},
+       "uuid-name": "brow"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2b": ["set", [["named-uuid", "brow"]]]}},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 1,
+               "a2b": ["set", [["named-uuid", "brow"]]]}},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 2,
+               "a2b": ["set", [["named-uuid", "brow"]]]}}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 1]]}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": []}]]],
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 2]]}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]
+[{"count":1},{"details":"cannot delete b row <0> because of 3 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 2 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- mutual references],
+  [CONSTRAINT_SCHEMA],
+  [[[[{"op": "declare",
+       "uuid-name": "row1"},
+      {"op": "declare",
+       "uuid-name": "row2"},
+      {"op": "insert",
+       "table": "a",
+       "row": {"a": 0,
+               "a2b": ["set", [["named-uuid", "row2"]]],
+               "a2a": ["set", [["named-uuid", "row1"]]]},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "b",
+       "row": {"b": 1,
+               "b2b": ["set", [["named-uuid", "row2"]]],
+               "b2a": ["set", [["named-uuid", "row1"]]]},
+       "uuid-name": "row2"}]]],
+   [[[{"op": "insert",
+       "table": "a",
+       "row": {"a2b": ["set", [["uuid", "b516b960-5b19-4fc2-bb82-fe1cbd6d0241"]]]}}]]],
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]],
+   dnl Try the deletions again to make sure that the refcounts got rolled back.
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]}]]],
+   [[[{"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]],
+   [[[{"op": "delete",
+       "table": "a",
+       "where": [["a", "==", 0]]},
+      {"op": "delete",
+       "table": "b",
+       "where": [["b", "==", 1]]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+[{"uuid":["uuid","<2>"]},{"details":"reference to nonexistent row <3>","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete a row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete b row <1> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete a row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"details":"cannot delete b row <1> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1},{"count":1}]
 ]])])
 
 EXECUTION_EXAMPLES
index 0be2a11e14ab1c0d2bc66d9c8f1125452d81b952..552f627f8ed7b698c9a69053cef3832712f6cce9 100644 (file)
@@ -227,55 +227,58 @@ OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
   [['[{"op": "insert",
        "table": "link1",
        "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
-     '[{"op": "update",
+    '+[{"op": "insert",
        "table": "link1",
-       "where": [],
-       "row": {"k": ["uuid", "#0#"]}}]' \
+       "uuid-name": "one",
+       "row": {"i": 1, "k": ["named-uuid", "one"]}},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "k": ["named-uuid", "one"]}}]' \
      '[{"op": "update",
        "table": "link1",
        "where": [],
        "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
-     '[{"op": "insert",
+     '+[{"op": "delete",
        "table": "link1",
-       "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \
-     '[{"op": "update",
+       "where": [["_uuid", "==", ["uuid", "#1#"]]]}]' \
+     '+[{"op": "delete",
        "table": "link1",
-       "where": [],
-       "row": {"k": ["uuid", "#1#"]}}]' \
+       "where": [["_uuid", "==", ["uuid", "#2#"]]]}]' \
+     '[{"op": "delete",
+       "table": "link1",
+       "where": []}]' \
 ]],
   [[000: empty
-001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
-002: i=0 k= ka=[] l2= uuid=<0>
-003: {"error":null,"result":[{"count":1}]}
-004: i=0 k=0 ka=[] l2= uuid=<0>
-005: {"error":null,"result":[{"count":1}]}
-006: i=0 k= ka=[] l2= uuid=<0>
-007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]}
-008: i=0 k= ka=[] l2= uuid=<0>
-008: i=1 k= ka=[] l2= uuid=<1>
-009: {"error":null,"result":[{"count":2}]}
-010: i=0 k=1 ka=[] l2= uuid=<0>
-010: i=1 k=1 ka=[] l2= uuid=<1>
-011: done
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"details":"reference to nonexistent row <1>","error":"referential integrity violation"}]}
+002: {"error":null,"result":[{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+003: i=1 k=1 ka=[] l2= uuid=<2>
+003: i=2 k=1 ka=[] l2= uuid=<3>
+004: {"error":null,"result":[{"count":2},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+005: {"error":null,"result":[{"count":1},{"details":"cannot delete link1 row <2> because of 1 remaining reference(s)","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":1}]}
+007: i=1 k=1 ka=[] l2= uuid=<2>
+008: {"error":null,"result":[{"count":1}]}
+009: empty
+010: done
 ]])
 
 OVSDB_CHECK_IDL([self-linking idl, sets],
   [],
   [['[{"op": "insert",
        "table": "link1",
-       "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]},
+       "row": {"i": 0, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i0"]]]},
        "uuid-name": "i0"},
       {"op": "insert",
        "table": "link1",
-       "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]},
+       "row": {"i": 1, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i1"]]]},
        "uuid-name": "i1"},
       {"op": "insert",
        "table": "link1",
-       "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]},
+       "row": {"i": 2, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i2"]]]},
        "uuid-name": "i2"},
       {"op": "insert",
        "table": "link1",
-       "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]},
+       "row": {"i": 3, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i3"]]]},
        "uuid-name": "i3"}]' \
     '[{"op": "update",
        "table": "link1",
@@ -284,24 +287,25 @@ OVSDB_CHECK_IDL([self-linking idl, sets],
     '[{"op": "update",
        "table": "link1",
        "where": [],
-       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]' \
+    '+[{"op": "delete",
+       "table": "link1",
+       "where": []}]']],
   [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
-002: i=0 k= ka=[0] l2= uuid=<0>
-002: i=1 k= ka=[1] l2= uuid=<1>
-002: i=2 k= ka=[2] l2= uuid=<2>
-002: i=3 k= ka=[3] l2= uuid=<3>
+002: i=0 k=0 ka=[0] l2= uuid=<0>
+002: i=1 k=0 ka=[1] l2= uuid=<1>
+002: i=2 k=0 ka=[2] l2= uuid=<2>
+002: i=3 k=0 ka=[3] l2= uuid=<3>
 003: {"error":null,"result":[{"count":4}]}
-004: i=0 k= ka=[0 1 2 3] l2= uuid=<0>
-004: i=1 k= ka=[0 1 2 3] l2= uuid=<1>
-004: i=2 k= ka=[0 1 2 3] l2= uuid=<2>
-004: i=3 k= ka=[0 1 2 3] l2= uuid=<3>
-005: {"error":null,"result":[{"count":4}]}
-006: i=0 k= ka=[0 2] l2= uuid=<0>
-006: i=1 k= ka=[0 2] l2= uuid=<1>
-006: i=2 k= ka=[0 2] l2= uuid=<2>
-006: i=3 k= ka=[0 2] l2= uuid=<3>
-007: done
+004: i=0 k=0 ka=[0 1 2 3] l2= uuid=<0>
+004: i=1 k=0 ka=[0 1 2 3] l2= uuid=<1>
+004: i=2 k=0 ka=[0 1 2 3] l2= uuid=<2>
+004: i=3 k=0 ka=[0 1 2 3] l2= uuid=<3>
+005: {"error":null,"result":[{"count":4},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":4}]}
+007: empty
+008: done
 ]])
 
 OVSDB_CHECK_IDL([external-linking idl, consistent ops],
@@ -312,11 +316,11 @@ OVSDB_CHECK_IDL([external-linking idl, consistent ops],
        "uuid-name": "row0"},
       {"op": "insert",
        "table": "link1",
-       "row": {"i": 1, "l2": ["set", [["named-uuid", "row0"]]]},
+       "row": {"i": 1, "k": ["named-uuid", "row1"], "l2": ["set", [["named-uuid", "row0"]]]},
        "uuid-name": "row1"}]']],
   [[000: empty
 001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
 002: i=0 l1= uuid=<0>
-002: i=1 k= ka=[] l2=0 uuid=<1>
+002: i=1 k=1 ka=[] l2=0 uuid=<1>
 003: done
 ]])
diff --git a/tests/ovsdb-schema.at b/tests/ovsdb-schema.at
new file mode 100644 (file)
index 0000000..6cd2fa2
--- /dev/null
@@ -0,0 +1,47 @@
+AT_BANNER([OVSDB -- schemas])
+
+OVSDB_CHECK_POSITIVE([schema with valid refTables],
+  [[parse-schema \
+      '{"name": "mydb",
+        "tables": {
+          "a": {
+            "columns": {
+              "map": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "b"},
+                  "value": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}},
+          "b": {
+            "columns": {
+              "aRef": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}}}}']],
+  [[{"name":"mydb","tables":{"a":{"columns":{"map":{"type":{"key":{"refTable":"b","type":"uuid"},"value":{"refTable":"a","type":"uuid"}}}}},"b":{"columns":{"aRef":{"type":{"key":{"refTable":"a","type":"uuid"}}}}}}}]])
+     
+OVSDB_CHECK_NEGATIVE([schema with invalid refTables],
+  [[parse-schema \
+      '{"name": "mydb",
+        "tables": {
+          "a": {
+            "columns": {
+              "map": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "c"},
+                  "value": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}},
+          "b": {
+            "columns": {
+              "aRef": {
+                "type": {
+                  "key": {
+                    "type": "uuid",
+                    "refTable": "a"}}}}}}}']],
+  [[test-ovsdb: syntax error: column map key refers to undefined table c]])
index b7fddc7e2746533d417f3ed7bb7f6ae044be82d1..4647e69de45079f728d6e14ac571ddeb4eb8695d 100644 (file)
@@ -70,6 +70,13 @@ 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_POSITIVE([uuid refTable], 
+  [[parse-base-type '{"type": "uuid", "refTable": "myTable"}' ]],
+  [{"refTable":"myTable","type":"uuid"}])
+OVSDB_CHECK_NEGATIVE([uuid refTable must be valid id], 
+  [[parse-base-type '{"type": "uuid", "refTable": "a-b-c"}' ]],
+  [Type mismatch for member 'refTable'])
+
 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],
index d10bedd3d0602efa6340312f63a5d2ed03ba0b37..275c90d6d824daa437c361f463c415105a41d020 100644 (file)
@@ -41,6 +41,7 @@ m4_include([tests/ovsdb-data.at])
 m4_include([tests/ovsdb-column.at])
 m4_include([tests/ovsdb-table.at])
 m4_include([tests/ovsdb-row.at])
+m4_include([tests/ovsdb-schema.at])
 m4_include([tests/ovsdb-condition.at])
 m4_include([tests/ovsdb-mutation.at])
 m4_include([tests/ovsdb-query.at])
index 4b38ecb5a533f2a92909bf7acc524440e4687f19..3025ce390510a076b91b9d60548c9ef4a654e5a0 100644 (file)
@@ -159,6 +159,8 @@ usage(void)
            "  query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
            "    add each ROW to TABLE, then query and print the rows that\n"
            "    satisfy each CONDITION and have distinct COLUMNS.\n"
+           "  parse-schema JSON\n"
+           "    parse JSON as an OVSDB schema, and re-serialize\n"
            "  transact COMMAND\n"
            "    execute each specified transactional COMMAND:\n"
            "      commit\n"
@@ -1138,6 +1140,19 @@ do_query_distinct(int argc UNUSED, char *argv[])
     exit(exit_code);
 }
 
+static void
+do_parse_schema(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    print_and_free_json(ovsdb_schema_to_json(schema));
+    ovsdb_schema_destroy(schema);
+}
+
 static void
 do_execute(int argc UNUSED, char *argv[])
 {
@@ -1773,20 +1788,28 @@ do_idl(int argc, char *argv[])
         rpc = NULL;
     }
 
+    setvbuf(stdout, NULL, _IOLBF, 0);
+
     symtab = ovsdb_symbol_table_create();
     for (i = 2; i < argc; i++) {
+        char *arg = argv[i];
         struct jsonrpc_msg *request, *reply;
         int error;
 
-        seqno = print_updated_idl(idl, rpc, step++, seqno);
+        if (*arg == '+') {
+            /* The previous transaction didn't change anything. */
+            arg++;
+        } else {
+            seqno = print_updated_idl(idl, rpc, step++, seqno);
+        }
 
-        if (!strcmp(argv[i], "reconnect")) {
+        if (!strcmp(arg, "reconnect")) {
             printf("%03d: reconnect\n", step++);
             ovsdb_idl_force_reconnect(idl);
-        } else if (argv[i][0] != '[') {
-            idl_set(idl, argv[i], step++);
+        } else if (arg[0] != '[') {
+            idl_set(idl, arg, step++);
         } else {
-            struct json *json = parse_json(argv[i]);
+            struct json *json = parse_json(arg);
             substitute_uuids(json, symtab);
             request = jsonrpc_create_request("transact", json, NULL);
             error = jsonrpc_transact_block(rpc, request, &reply);
@@ -1833,6 +1856,7 @@ static struct command all_commands[] = {
     { "query", 3, 3, do_query },
     { "query-distinct", 4, 4, do_query_distinct },
     { "transact", 1, INT_MAX, do_transact },
+    { "parse-schema", 1, 1, do_parse_schema },
     { "execute", 2, INT_MAX, do_execute },
     { "trigger", 2, INT_MAX, do_trigger },
     { "idl", 1, INT_MAX, do_idl },
index 13985a64925cfd563caa3258b12fff170eae4a24..81daf719df04c5788b97a0d2fc7f4c481d0c1009 100644 (file)
@@ -444,13 +444,6 @@ as \fB{}\fR, and curly braces may be optionally enclose non-empty maps
 as well.
 .
 .ST "Database Command Syntax"
-.PP
-By default, database commands refuse to make some kinds of
-modifications that could violate database structuring constraints.  If
-you are sure that you know what you are doing, use \fB\-\-force\fR to
-override this safety measure.  Constraints that are enforced by the
-database server itself, instead of by \fBovs\-vsctl\fR, cannot be
-overridden this way.
 .
 .IP "\fBlist \fItable \fR[\fIrecord\fR]..."
 List the values of all columns of each specified \fIrecord\fR.  If no
@@ -503,18 +496,14 @@ 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.
 .
-.IP "\fB\-\-force create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+.IP "create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
 Creates a new record in \fItable\fR and sets the initial values of
 each \fIcolumn\fR.  Columns not explicitly set will receive their
 default values.  Outputs the UUID of the new row.
-.IP
-This command requires the \fB\-\-force\fR option.
 .
-.IP "\fB\-\-force \fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
+.IP "\fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
 Deletes each specified \fIrecord\fR from \fItable\fR.  Unless
 \fB\-\-if\-exists\fR is specified, each \fIrecord\fRs must exist.
-.IP
-This command requires the \fB\-\-force\fR option.
 .SH "EXAMPLES"
 Create a new bridge named br0 and add port eth0 to it:
 .IP
index 1eb6e0fe346c3a92e7caa9726eb5c035ceba2236..e2e577be8758564efde4c4e47211c6110197099a 100644 (file)
@@ -2113,16 +2113,11 @@ cmd_clear(struct vsctl_context *ctx)
 static void
 cmd_create(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     const char *table_name = ctx->argv[1];
     const struct vsctl_table_class *table;
     const struct ovsdb_idl_row *row;
     int i;
 
-    if (!force) {
-        vsctl_fatal("\"create\" requires --force");
-    }
-
     table = get_table(table_name);
     row = ovsdb_idl_txn_insert(ctx->txn, table->class);
     for (i = 2; i < ctx->argc; i++) {
@@ -2158,16 +2153,11 @@ post_create(struct vsctl_context *ctx)
 static void
 cmd_destroy(struct vsctl_context *ctx)
 {
-    bool force = shash_find(&ctx->options, "--force");
     bool must_exist = !shash_find(&ctx->options, "--if-exists");
     const char *table_name = ctx->argv[1];
     const struct vsctl_table_class *table;
     int i;
 
-    if (!force) {
-        vsctl_fatal("\"destroy\" requires --force");
-    }
-
     table = get_table(table_name);
     for (i = 2; i < ctx->argc; i++) {
         const struct ovsdb_idl_row *row;
@@ -2397,12 +2387,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, ""},
-    {"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, ""},
+    {"create", 2, INT_MAX, cmd_create, post_create, ""},
+    {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"},
 
     {NULL, 0, 0, NULL, NULL, NULL},
 };
index b8e457d5b469e36df035dd59ff91d81b7a3960fe..7a5cc3147f2b3b73330f56b872c4b4897cff7f5b 100644 (file)
@@ -7,15 +7,3 @@
 
 s["idlPrefix"] = "ovsrec_"
 s["idlHeader"] = "\"vswitchd/vswitch-idl.h\""
-s["tables"]["Open_vSwitch"]["columns"]["bridges"]["type"]["keyRefTable"] = "Bridge"
-s["tables"]["Open_vSwitch"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
-s["tables"]["Open_vSwitch"]["columns"]["ssl"]["type"]["keyRefTable"] = "SSL"
-s["tables"]["Bridge"]["columns"]["ports"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Bridge"]["columns"]["mirrors"]["type"]["keyRefTable"] = "Mirror"
-s["tables"]["Bridge"]["columns"]["netflow"]["type"]["keyRefTable"] = "NetFlow"
-s["tables"]["Bridge"]["columns"]["sflow"]["type"]["keyRefTable"] = "sFlow"
-s["tables"]["Bridge"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
-s["tables"]["Port"]["columns"]["interfaces"]["type"]["keyRefTable"] = "Interface"
-s["tables"]["Mirror"]["columns"]["select_src_port"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Mirror"]["columns"]["select_dst_port"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Mirror"]["columns"]["output_port"]["type"]["keyRefTable"] = "Port"
index ea6734278ab17fb12be6d11829c4d68f9f47442b..c1fd98e72329a8887e8a46c5c1d64180abf218ba 100644 (file)
@@ -6,16 +6,22 @@
      "columns": {
        "bridges": {
          "comment": "Set of bridges managed by the daemon.",
-         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Bridge"},
+                  "min": 0, "max": "unlimited"}},
        "controller": {
          "comment": "Default Controller used by bridges.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Controller"},
+                   "min": 0, "max": 1}},
        "managers": {
          "comment": "Remote database clients to which the Open vSwitch's database server should connect or to which it should listen.",
          "type": {"key": "string", "min": 0, "max": "unlimited"}},
        "ssl": {
          "comment": "SSL used globally by the daemon.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "SSL"},
+                  "min": 0, "max": 1}},
        "next_cfg": {
          "comment": "Sequence number for client to increment when it modifies the configuration and wishes to wait for Open vSwitch to finish applying the changes.",
          "type": "integer"},
          "ephemeral": true},
        "ports": {
          "comment": "Ports included in the bridge.",
-         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port"},
+                  "min": 0, "max": "unlimited"}},
        "mirrors": {
          "comment": "Port mirroring configuration.",
-         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Mirror"},
+                  "min": 0, "max": "unlimited"}},
        "netflow": {
          "comment": "NetFlow configuration.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "NetFlow"},
+                  "min": 0, "max": 1}},
        "sflow": {
          "comment": "sFlow configuration.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "sFlow"},
+                  "min": 0, "max": 1}},
        "controller": {
          "comment": "OpenFlow controller.  If unset, defaults to that specified by the parent Open_vSwitch.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Controller"},
+                  "min": 0, "max": 1}},
        "other_config": {
          "comment": "Key-value pairs for configuring rarely used bridge features.  The currently defined key-value pairs are: \"datapath-id\", exactly 12 hex digits to set the OpenFlow datapath ID to a specific value; \"hwaddr\", exactly 12 hex digits in the form \"XX:XX:XX:XX:XX:XX\" to set the hardware address of the local port and influence the datapath ID.",
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
@@ -70,7 +86,9 @@
          "type": "string"},
        "interfaces": {
          "comment": "The Port's Interfaces.  If there is more than one, this is a bonded Port.",
-         "type": {"key": "uuid", "min": 1, "max": "unlimited"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Interface"},
+                  "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": {"type": "integer",
          "type": "string"},
        "select_src_port": {
          "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"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port"},
+                   "min": 0, "max": "unlimited"}},
        "select_dst_port": {
          "comment": "Ports on which departing packets are selected for mirroring.",
-         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port"}, "min": 0, "max": "unlimited"}},
        "select_vlan": {
          "comment": "VLANs on which packets are selected for mirroring.  An empty set selects packets on all VLANs.",
          "type": {"key": {"type": "integer",
                   "min": 0, "max": 4096}},
        "output_port": {
          "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
-         "type": {"key": "uuid", "min": 0, "max": 1}},
+         "type": {"key": {"type": "uuid",
+                          "refTable": "Port"}, "min": 0, "max": 1}},
        "output_vlan": {
          "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
          "type": {"key": {"type": "integer",