ovsdb: Allow constraining the number of rows in a table.
authorBen Pfaff <blp@nicira.com>
Wed, 17 Mar 2010 00:08:06 +0000 (17:08 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 18 Mar 2010 18:32:26 +0000 (11:32 -0700)
ovsdb/SPECS
ovsdb/table.c
ovsdb/table.h
ovsdb/transaction.c
tests/ovsdb-execution.at
tests/ovsdb-table.at

index f5d748c..c926e21 100644 (file)
@@ -101,6 +101,7 @@ is represented by <database-schema>, as described below.
     A JSON object with the following members:
 
         "columns": {<id>: <column-schema>, ...}   required
+        "maxRows": <integer>                      optional
 
     The value of "columns" is a JSON object whose names are column
     names and whose values are <column-schema>s.
@@ -122,6 +123,13 @@ is represented by <database-schema>, as described below.
         the database process is stopped and then started again, each
         "_version" also changes to a new random value.
 
+    If "maxRows" is specified, as a positive integer, it limits the
+    maximum number of rows that may be present in the table.  This is
+    a "deferred" constraint, enforced only at transaction commit time
+    (see the "transact" request below).  If "maxRows" is not
+    specified, the size of the table is limited only by the resources
+    available to the database server.
+
 <column-schema>
 
     A JSON object with the following members:
@@ -362,6 +370,11 @@ include at least the following:
         transaction), and this column is not allowed to be empty
         because its <type> has a "min" of 1.
 
+    "error": "constraint violation"
+
+        The number of rows in a table exceeds the maximum number
+        permitted by the table's "maxRows" value (see <table-schema>).
+
 If "params" contains one or more "wait" operations, then the
 transaction may take an arbitrary amount of time to complete.  The
 database implementation must be capable of accepting, executing, and
index 3f35b86..6a4e7ae 100644 (file)
@@ -18,6 +18,7 @@
 #include "table.h"
 
 #include <assert.h>
+#include <limits.h>
 
 #include "json.h"
 #include "column.h"
@@ -35,7 +36,8 @@ add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
 }
 
 struct ovsdb_table_schema *
-ovsdb_table_schema_create(const char *name, bool mutable)
+ovsdb_table_schema_create(const char *name, bool mutable,
+                          unsigned int max_rows)
 {
     struct ovsdb_column *uuid, *version;
     struct ovsdb_table_schema *ts;
@@ -44,6 +46,7 @@ ovsdb_table_schema_create(const char *name, bool mutable)
     ts->name = xstrdup(name);
     ts->mutable = mutable;
     shash_init(&ts->columns);
+    ts->max_rows = max_rows;
 
     uuid = ovsdb_column_create("_uuid", false, true, &ovsdb_type_uuid);
     add_column(ts, uuid);
@@ -62,7 +65,7 @@ ovsdb_table_schema_clone(const struct ovsdb_table_schema *old)
     struct ovsdb_table_schema *new;
     struct shash_node *node;
 
-    new = ovsdb_table_schema_create(old->name, old->mutable);
+    new = ovsdb_table_schema_create(old->name, old->mutable, old->max_rows);
     SHASH_FOR_EACH (node, &old->columns) {
         const struct ovsdb_column *column = node->data;
 
@@ -94,10 +97,11 @@ ovsdb_table_schema_from_json(const struct json *json, const char *name,
                              struct ovsdb_table_schema **tsp)
 {
     struct ovsdb_table_schema *ts;
-    const struct json *columns, *mutable;
+    const struct json *columns, *mutable, *max_rows;
     struct shash_node *node;
     struct ovsdb_parser parser;
     struct ovsdb_error *error;
+    long long int n_max_rows;
 
     *tsp = NULL;
 
@@ -105,18 +109,31 @@ ovsdb_table_schema_from_json(const struct json *json, const char *name,
     columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
     mutable = ovsdb_parser_member(&parser, "mutable",
                                   OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    max_rows = ovsdb_parser_member(&parser, "maxRows",
+                                   OP_INTEGER | OP_OPTIONAL);
     error = ovsdb_parser_finish(&parser);
     if (error) {
         return error;
     }
 
+    if (max_rows) {
+        if (json_integer(max_rows) <= 0) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "maxRows must be at least 1");
+        }
+        n_max_rows = max_rows->u.integer;
+    } else {
+        n_max_rows = UINT_MAX;
+    }
+
     if (shash_is_empty(json_object(columns))) {
         return ovsdb_syntax_error(json, NULL,
                                   "table must have at least one column");
     }
 
     ts = ovsdb_table_schema_create(name,
-                                   mutable ? json_boolean(mutable) : true);
+                                   mutable ? json_boolean(mutable) : true,
+                                   MIN(n_max_rows, UINT_MAX));
     SHASH_FOR_EACH (node, json_object(columns)) {
         struct ovsdb_column *column;
 
@@ -160,6 +177,9 @@ ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
         }
     }
     json_object_put(json, "columns", columns);
+    if (ts->max_rows != UINT_MAX) {
+        json_object_put(json, "maxRows", json_integer_create(ts->max_rows));
+    }
 
     return json;
 }
index ff99cf1..4d3b9ee 100644 (file)
@@ -29,10 +29,12 @@ struct ovsdb_table_schema {
     char *name;
     bool mutable;
     struct shash columns;       /* Contains "struct ovsdb_column *"s. */
+    unsigned int max_rows;      /* Maximum number of rows. */
 };
 
 struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
-                                                     bool mutable);
+                                                     bool mutable,
+                                                     unsigned int max_rows);
 struct ovsdb_table_schema *ovsdb_table_schema_clone(
     const struct ovsdb_table_schema *);
 void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
index 2e4c73a..218fbce 100644 (file)
@@ -441,6 +441,27 @@ determine_changes(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row)
     return NULL;
 }
 
+static struct ovsdb_error * WARN_UNUSED_RESULT
+check_max_rows(struct ovsdb_txn *txn)
+{
+    struct ovsdb_txn_table *t;
+
+    LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+        size_t n_rows = hmap_count(&t->table->rows);
+        unsigned int max_rows = t->table->schema->max_rows;
+
+        if (n_rows > max_rows) {
+            return ovsdb_error("constraint violation",
+                               "transaction causes \"%s\" table to contain "
+                               "%zu rows, greater than the schema-defined "
+                               "limit of %u row(s)",
+                               t->table->schema->name, n_rows, max_rows);
+        }
+    }
+
+    return NULL;
+}
+
 struct ovsdb_error *
 ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
 {
@@ -459,6 +480,13 @@ ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
         return NULL;
     }
 
+    /* Check maximum rows table constraints. */
+    error = check_max_rows(txn);
+    if (error) {
+        ovsdb_txn_abort(txn);
+        return error;
+    }
+
     /* Update reference counts and check referential integrity. */
     error = update_ref_counts(txn);
     if (error) {
index dc4f3e8..bbdcbb5 100644 (file)
@@ -28,7 +28,8 @@ m4_define([CONSTRAINT_SCHEMA],
        "constrained": {
          "columns": {
            "positive": {"type": {"key": {"type": "integer",
-                                         "minInteger": 1}}}}}}}]])
+                                         "minInteger": 1}}}},
+         "maxRows": 1}}}]])
 
 m4_define([WEAK_SCHEMA],
   [[{"name": "weak",
@@ -462,10 +463,20 @@ OVSDB_CHECK_EXECUTION([insert and update constraints],
       {"op": "update",
        "table": "constrained",
        "where": [],
-       "row": {"positive": -2}}]]]],
+       "row": {"positive": -2}}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {"positive": 1}}]]],
+   [[["constraints",
+      {"op": "insert",
+       "table": "constrained",
+       "row": {"positive": 2}}]]]],
   [[[{"details":"0 is less than minimum allowed value 1","error":"constraint violation"}]
 [{"details":"-1 is less than minimum allowed value 1","error":"constraint violation"}]
 [{"details":"-2 is less than minimum allowed value 1","error":"constraint violation"}]
+[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]},{"details":"transaction causes \"constrained\" table to contain 2 rows, greater than the schema-defined limit of 1 row(s)","error":"constraint violation"}]
 ]])
 
 OVSDB_CHECK_EXECUTION([referential integrity -- simple],
index 25f5ddd..623dd6d 100644 (file)
@@ -10,6 +10,11 @@ OVSDB_CHECK_POSITIVE([immutable table with one column],
       "mutable": false}']],
   [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
 
+OVSDB_CHECK_POSITIVE([table with maxRows of 2],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}, 
+                          "maxRows": 2}']],
+  [[{"columns":{"name":{"type":"string"}},"maxRows":2}]])
+
 OVSDB_CHECK_NEGATIVE([column names may not begin with _],
   [[parse-table mytable \
     '{"columns": {"_column": {"type": "integer"}}}']],
@@ -23,3 +28,8 @@ OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
 OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
   [[parse-table mytable '{"columns": {}}']],
   [[table must have at least one column]])
+
+OVSDB_CHECK_NEGATIVE([table maxRows must be positive],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}, 
+                          "maxRows": 0}']],
+  [[syntax "{"columns":{"name":{"type":"string"}},"maxRows":0}": syntax error: maxRows must be at least 1]])