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 e79f849..76d046f 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 ff81960..1b5c7ed 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 633b50f..b11f827 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 75e592b..c1e3eca 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 1a7e804..a2de7ae 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 12aa7d9..b411131 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 e95d23c..2dea507 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 1b81942..52c5ddb 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 55c4f14..d468194 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 7ae52ef..137ae06 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 8fa6c15..fe493fd 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 2ffd1af..66e8637 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 239a343..545242b 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 06bff53..103b17a 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 bb9d6cf..334e208 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 0be2a11..552f627 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 b7fddc7..4647e69 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 d10bedd..275c90d 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 4b38ecb..3025ce3 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 13985a6..81daf71 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 1eb6e0f..e2e577b 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 b8e457d..7a5cc31 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 ea67342..c1fd98e 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",