Initial implementation of OVSDB.
authorBen Pfaff <blp@nicira.com>
Wed, 4 Nov 2009 23:11:44 +0000 (15:11 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 5 Nov 2009 01:12:10 +0000 (17:12 -0800)
56 files changed:
Makefile.am
lib/automake.mk
lib/compiler.h
lib/ovsdb-data.c [new file with mode: 0644]
lib/ovsdb-data.h [new file with mode: 0644]
lib/ovsdb-error.c [new file with mode: 0644]
lib/ovsdb-error.h [new file with mode: 0644]
lib/ovsdb-parser.c [new file with mode: 0644]
lib/ovsdb-parser.h [new file with mode: 0644]
lib/ovsdb-types.c [new file with mode: 0644]
lib/ovsdb-types.h [new file with mode: 0644]
lib/sort.c [new file with mode: 0644]
lib/sort.h [new file with mode: 0644]
lib/vlog-modules.def
ovsdb/SPECS [new file with mode: 0644]
ovsdb/automake.mk [new file with mode: 0644]
ovsdb/column.c [new file with mode: 0644]
ovsdb/column.h [new file with mode: 0644]
ovsdb/condition.c [new file with mode: 0644]
ovsdb/condition.h [new file with mode: 0644]
ovsdb/execution.c [new file with mode: 0644]
ovsdb/file.c [new file with mode: 0644]
ovsdb/file.h [new file with mode: 0644]
ovsdb/jsonrpc-server.c [new file with mode: 0644]
ovsdb/jsonrpc-server.h [new file with mode: 0644]
ovsdb/ovsdb-server.c [new file with mode: 0644]
ovsdb/ovsdb-tool.c [new file with mode: 0644]
ovsdb/ovsdb.c [new file with mode: 0644]
ovsdb/ovsdb.h [new file with mode: 0644]
ovsdb/query.c [new file with mode: 0644]
ovsdb/query.h [new file with mode: 0644]
ovsdb/row.c [new file with mode: 0644]
ovsdb/row.h [new file with mode: 0644]
ovsdb/table.c [new file with mode: 0644]
ovsdb/table.h [new file with mode: 0644]
ovsdb/transaction.c [new file with mode: 0644]
ovsdb/transaction.h [new file with mode: 0644]
ovsdb/trigger.c [new file with mode: 0644]
ovsdb/trigger.h [new file with mode: 0644]
tests/automake.mk
tests/library.at
tests/ovsdb-column.at [new file with mode: 0644]
tests/ovsdb-condition.at [new file with mode: 0644]
tests/ovsdb-data.at [new file with mode: 0644]
tests/ovsdb-execution.at [new file with mode: 0644]
tests/ovsdb-file.at [new file with mode: 0644]
tests/ovsdb-query.at [new file with mode: 0644]
tests/ovsdb-row.at [new file with mode: 0644]
tests/ovsdb-table.at [new file with mode: 0644]
tests/ovsdb-transaction.at [new file with mode: 0644]
tests/ovsdb-trigger.at [new file with mode: 0644]
tests/ovsdb-types.at [new file with mode: 0644]
tests/ovsdb.at [new file with mode: 0644]
tests/test-ovsdb.c [new file with mode: 0644]
tests/testsuite.at
tests/uuidfilt.pl [new file with mode: 0755]

index 52f5e594a51761c87487df7d8c8b933932174b36..22ebb219ce2c026cc22f96e26162a8cfff042655 100644 (file)
@@ -77,5 +77,6 @@ include include/automake.mk
 include third-party/automake.mk
 include debian/automake.mk
 include vswitchd/automake.mk
+include ovsdb/automake.mk
 include xenserver/automake.mk
 include extras/ezio/automake.mk
index 07f7ce71ae556034f929ea3e5a508a6347e84c58..918e4bb42ae5aa6a640cca234a65aaa5aee8e454 100644 (file)
@@ -77,6 +77,15 @@ lib_libopenvswitch_a_SOURCES = \
        lib/ofp-print.h \
        lib/ofpbuf.c \
        lib/ofpbuf.h \
+       lib/ovsdb-client.h \
+       lib/ovsdb-data.c \
+       lib/ovsdb-data.h \
+       lib/ovsdb-error.c \
+       lib/ovsdb-error.h \
+       lib/ovsdb-parser.c \
+       lib/ovsdb-parser.h \
+       lib/ovsdb-types.c \
+       lib/ovsdb-types.h \
        lib/packets.c \
        lib/packets.h \
        lib/pcap.c \
@@ -104,6 +113,8 @@ lib_libopenvswitch_a_SOURCES = \
        lib/signals.h \
        lib/socket-util.c \
        lib/socket-util.h \
+       lib/sort.c \
+       lib/sort.h \
        lib/stp.c \
        lib/stp.h \
        lib/stream-fd.c \
index 17e245fce770ac6d6ba5998d70942ef5e14386f8..216dd6af0685c3b5b1f1bfde16a778e5e26e755e 100644 (file)
@@ -26,5 +26,6 @@
 #define ALWAYS_INLINE __attribute__((always_inline))
 #define likely(x) __builtin_expect((x),1)
 #define unlikely(x) __builtin_expect((x),0)
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
 
 #endif /* compiler.h */
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
new file mode 100644 (file)
index 0000000..46298cb
--- /dev/null
@@ -0,0 +1,756 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-data.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "shash.h"
+#include "sort.h"
+
+static struct json *
+wrap_json(const char *name, struct json *wrapped)
+{
+    return json_array_create_2(json_string_create(name), wrapped);
+}
+
+void
+ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        atom->integer = 0;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        atom->real = 0.0;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        atom->boolean = false;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        atom->string = xmemdup("", 1);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        uuid_zero(&atom->uuid);
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+                 enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        new->integer = old->integer;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        new->real = old->real;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        new->boolean = old->boolean;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        new->string = xstrdup(old->string);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        new->uuid = old->uuid;
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b)
+{
+    union ovsdb_atom tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+uint32_t
+ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                uint32_t basis)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return hash_int(atom->integer, basis);
+
+    case OVSDB_TYPE_REAL:
+        return hash_double(atom->real, basis);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return hash_boolean(atom->boolean, basis);
+
+    case OVSDB_TYPE_STRING:
+        return hash_string(atom->string, basis);
+
+    case OVSDB_TYPE_UUID:
+        return hash_int(uuid_hash(&atom->uuid), basis);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+int
+ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+                        const union ovsdb_atom *b,
+                        enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return a->integer < b->integer ? -1 : a->integer > b->integer;
+
+    case OVSDB_TYPE_REAL:
+        return a->real < b->real ? -1 : a->real > b->real;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return a->boolean - b->boolean;
+
+    case OVSDB_TYPE_STRING:
+        return strcmp(a->string, b->string);
+
+    case OVSDB_TYPE_UUID:
+        return uuid_compare_3way(&a->uuid, &b->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct ovsdb_error *
+unwrap_json(const struct json *json, const char *name,
+            enum json_type value_type, const struct json **value)
+{
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 2
+        || json->u.array.elems[0]->type != JSON_STRING
+        || (name && strcmp(json->u.array.elems[0]->u.string, name))
+        || json->u.array.elems[1]->type != value_type)
+    {
+        return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name,
+                                  json_type_to_string(value_type));
+    }
+    *value = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+parse_json_pair(const struct json *json,
+                const struct json **elem0, const struct json **elem1)
+{
+    if (json->type != JSON_ARRAY || json->u.array.n != 2) {
+        return ovsdb_syntax_error(json, NULL, "expected 2-element array");
+    }
+    *elem0 = json->u.array.elems[0];
+    *elem1 = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+    WARN_UNUSED_RESULT;
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error0;
+    const struct json *value;
+
+    error0 = unwrap_json(json, "uuid", JSON_STRING, &value);
+    if (!error0) {
+        const char *uuid_string = json_string(value);
+        if (!uuid_from_string(uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+    } else if (symtab) {
+        struct ovsdb_error *error1;
+
+        error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value);
+        if (!error1) {
+            const char *name = json_string(value);
+            const struct uuid *named_uuid;
+
+            ovsdb_error_destroy(error0);
+
+            named_uuid = ovsdb_symbol_table_get(symtab, name);
+            if (named_uuid) {
+                *uuid = *named_uuid;
+                return NULL;
+            } else {
+                return ovsdb_syntax_error(json, NULL,
+                                          "unknown named-uuid \"%s\"", name);
+            }
+        }
+        ovsdb_error_destroy(error1);
+    }
+
+    return error0;
+}
+
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                     const struct json *json,
+                     const struct ovsdb_symbol_table *symtab)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (json->type == JSON_INTEGER) {
+            atom->integer = json->u.integer;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (json->type == JSON_INTEGER) {
+            atom->real = json->u.integer;
+            return NULL;
+        } else if (json->type == JSON_REAL) {
+            atom->real = json->u.real;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (json->type == JSON_TRUE) {
+            atom->boolean = true;
+            return NULL;
+        } else if (json->type == JSON_FALSE) {
+            atom->boolean = false;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (json->type == JSON_STRING) {
+            atom->string = xstrdup(json->u.string);
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return ovsdb_syntax_error(json, NULL, "expected %s",
+                              ovsdb_atomic_type_to_string(type));
+}
+
+struct json *
+ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return json_integer_create(atom->integer);
+
+    case OVSDB_TYPE_REAL:
+        return json_real_create(atom->real);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return json_boolean_create(atom->boolean);
+
+    case OVSDB_TYPE_STRING:
+        return json_string_create(atom->string);
+
+    case OVSDB_TYPE_UUID:
+        return wrap_json("uuid", json_string_create_nocopy(
+                             xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid))));
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+static union ovsdb_atom *
+alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *atoms;
+        unsigned int i;
+
+        atoms = xmalloc(n * sizeof *atoms);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_init_default(&atoms[i], type);
+        }
+        return atoms;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_init_default(struct ovsdb_datum *datum,
+                         const struct ovsdb_type *type)
+{
+    datum->n = type->n_min;
+    datum->keys = alloc_default_atoms(type->key_type, datum->n);
+    datum->values = alloc_default_atoms(type->value_type, datum->n);
+}
+
+static union ovsdb_atom *
+clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *new;
+        unsigned int i;
+
+        new = xmalloc(n * sizeof *new);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_clone(&new[i], &old[i], type);
+        }
+        return new;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
+                  const struct ovsdb_type *type)
+{
+    unsigned int n = old->n;
+    new->n = n;
+    new->keys = clone_atoms(old->keys, type->key_type, n);
+    new->values = clone_atoms(old->values, type->value_type, n);
+}
+
+static void
+free_data(enum ovsdb_atomic_type type,
+          union ovsdb_atom *atoms, size_t n_atoms)
+{
+    if (ovsdb_atom_needs_destruction(type)) {
+        unsigned int i;
+        for (i = 0; i < n_atoms; i++) {
+            ovsdb_atom_destroy(&atoms[i], type);
+        }
+    }
+    free(atoms);
+}
+
+void
+ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    free_data(type->key_type, datum->keys, datum->n);
+    free_data(type->value_type, datum->values, datum->n);
+}
+
+void
+ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
+{
+    struct ovsdb_datum tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+struct ovsdb_datum_sort_cbdata {
+    const struct ovsdb_type *type;
+    struct ovsdb_datum *datum;
+};
+
+static int
+ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
+                                   &cbdata->datum->keys[b],
+                                   cbdata->type->key_type);
+}
+
+static void
+ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
+    if (cbdata->type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    if (datum->n < 2) {
+        return NULL;
+    } else {
+        struct ovsdb_datum_sort_cbdata cbdata;
+        size_t i;
+
+        cbdata.type = type;
+        cbdata.datum = datum;
+        sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
+             &cbdata);
+
+        for (i = 0; i < datum->n - 1; i++) {
+            if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
+                                  type->key_type)) {
+                if (ovsdb_type_is_map(type)) {
+                    return ovsdb_error(NULL, "map contains duplicate key");
+                } else {
+                    return ovsdb_error(NULL, "set contains duplicate");
+                }
+            }
+        }
+
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_datum_from_json(struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type,
+                      const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    if (ovsdb_type_is_scalar(type)) {
+        datum->n = 1;
+        datum->keys = xmalloc(sizeof *datum->keys);
+        datum->values = NULL;
+
+        error = ovsdb_atom_from_json(&datum->keys[0], type->key_type,
+                                     json, symtab);
+        if (error) {
+            free(datum->keys);
+        }
+        return error;
+    } else {
+        bool is_map = ovsdb_type_is_map(type);
+        const char *class = is_map ? "map" : "set";
+        const struct json *inner;
+        unsigned int i;
+        size_t n;
+
+        assert(is_map || ovsdb_type_is_set(type));
+
+        error = unwrap_json(json, class, JSON_ARRAY, &inner);
+        if (error) {
+            return error;
+        }
+
+        n = inner->u.array.n;
+        if (n < type->n_min || n > type->n_max) {
+            return ovsdb_syntax_error(json, NULL, "%s must have %u to "
+                                      "%u members but %zu are present",
+                                      class, type->n_min, type->n_max, n);
+        }
+
+        datum->n = 0;
+        datum->keys = xmalloc(n * sizeof *datum->keys);
+        datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL;
+        for (i = 0; i < n; i++) {
+            const struct json *element = inner->u.array.elems[i];
+            const struct json *key = NULL;
+            const struct json *value = NULL;
+
+            if (!is_map) {
+                key = element;
+            } else {
+                error = parse_json_pair(element, &key, &value);
+                if (error) {
+                    goto error;
+                }
+            }
+
+            error = ovsdb_atom_from_json(&datum->keys[i], type->key_type,
+                                         key, symtab);
+            if (error) {
+                goto error;
+            }
+
+            if (is_map) {
+                error = ovsdb_atom_from_json(&datum->values[i],
+                                             type->value_type, value, symtab);
+                if (error) {
+                    ovsdb_atom_destroy(&datum->keys[i], type->key_type);
+                    goto error;
+                }
+            }
+
+            datum->n++;
+        }
+
+        error = ovsdb_datum_sort(datum, type);
+        if (error) {
+            goto error;
+        }
+
+        return NULL;
+
+    error:
+        ovsdb_datum_destroy(datum, type);
+        return error;
+    }
+}
+
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+                    const struct ovsdb_type *type)
+{
+    /* These tests somewhat tolerate a 'datum' that does not exactly match
+     * 'type', in particular a datum with 'n' not in the allowed range. */
+    if (datum->n == 1 && ovsdb_type_is_scalar(type)) {
+        return ovsdb_atom_to_json(&datum->keys[0], type->key_type);
+    } else if (type->value_type == OVSDB_TYPE_VOID) {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type);
+        }
+
+        return wrap_json("set", json_array_create(elems, datum->n));
+    } else {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = json_array_create_2(
+                ovsdb_atom_to_json(&datum->keys[i], type->key_type),
+                ovsdb_atom_to_json(&datum->values[i], type->value_type));
+        }
+
+        return wrap_json("map", json_array_create(elems, datum->n));
+    }
+}
+
+static uint32_t
+hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
+           unsigned int n, uint32_t basis)
+{
+    if (type != OVSDB_TYPE_VOID) {
+        unsigned int i;
+
+        for (i = 0; i < n; i++) {
+            basis = ovsdb_atom_hash(&atoms[i], type, basis);
+        }
+    }
+    return basis;
+}
+
+uint32_t
+ovsdb_datum_hash(const struct ovsdb_datum *datum,
+                 const struct ovsdb_type *type, uint32_t basis)
+{
+    basis = hash_atoms(type->key_type, datum->keys, datum->n, basis);
+    basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n;
+    basis = hash_atoms(type->value_type, datum->values, datum->n, basis);
+    return basis;
+}
+
+static int
+atom_arrays_compare_3way(const union ovsdb_atom *a,
+                  const union ovsdb_atom *b,
+                  enum ovsdb_atomic_type type,
+                  size_t n)
+{
+    unsigned int i;
+
+    for (i = 0; i < n; i++) {
+        int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_datum_equals(const struct ovsdb_datum *a,
+                   const struct ovsdb_datum *b,
+                   const struct ovsdb_type *type)
+{
+    return !ovsdb_datum_compare_3way(a, b, type);
+}
+
+int
+ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    int cmp;
+
+    if (a->n != b->n) {
+        return a->n < b->n ? -1 : 1;
+    }
+
+    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n);
+    if (cmp) {
+        return cmp;
+    }
+
+    return (type->value_type == OVSDB_TYPE_VOID ? 0
+            : atom_arrays_compare_3way(a->values, b->values, type->value_type,
+                                       a->n));
+}
+
+static bool
+ovsdb_datum_contains(const struct ovsdb_datum *a, int i,
+                     const struct ovsdb_datum *b,
+                     const struct ovsdb_type *type)
+{
+    int low = 0;
+    int high = b->n;
+    while (low < high) {
+        int j = (low + high) / 2;
+        int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j], type->key_type);
+        if (cmp < 0) {
+            high = j;
+        } else if (cmp > 0) {
+            low = j + 1;
+        } else {
+            return (type->value_type == OVSDB_TYPE_VOID
+                    || ovsdb_atom_equals(&a->values[i], &b->values[j],
+                                         type->value_type));
+        }
+    }
+    return false;
+}
+
+/* Returns true if every element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_includes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (!ovsdb_datum_contains(a, i, b, type)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* Returns true if no element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_contains(a, i, b, type)) {
+            return false;
+        }
+    }
+    return true;
+}
+\f
+struct ovsdb_symbol_table {
+    struct shash sh;
+};
+
+struct ovsdb_symbol_table *
+ovsdb_symbol_table_create(void)
+{
+    struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab);
+    shash_init(&symtab->sh);
+    return symtab;
+}
+
+void
+ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab)
+{
+    if (symtab) {
+        struct shash_node *node, *next;
+
+        SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) {
+            free(node->data);
+            shash_delete(&symtab->sh, node);
+        }
+        shash_destroy(&symtab->sh);
+        free(symtab);
+    }
+}
+
+const struct uuid *
+ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab,
+                       const char *name)
+{
+    return shash_find_data(&symtab->sh, name);
+}
+
+void
+ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name,
+                       const struct uuid *uuid)
+{
+    struct uuid *entry = shash_find_data(&symtab->sh, name);
+    if (!entry) {
+        shash_add(&symtab->sh, name, xmemdup(uuid, sizeof *uuid));
+    } else {
+        *entry = *uuid;
+    }
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
new file mode 100644 (file)
index 0000000..35c4e30
--- /dev/null
@@ -0,0 +1,128 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_DATA_H
+#define OVSDB_DATA_H 1
+
+#include <stdlib.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_symbol_table;
+
+/* One value of an atomic type (given by enum ovs_atomic_type). */
+union ovsdb_atom {
+    int64_t integer;
+    double real;
+    bool boolean;
+    char *string;
+    struct uuid uuid;
+};
+
+void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type);
+void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *,
+                      enum ovsdb_atomic_type);
+void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *);
+
+static inline bool
+ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+{
+    return type == OVSDB_TYPE_STRING;
+}
+
+static inline void
+ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    if (type == OVSDB_TYPE_STRING) {
+        free(atom->string);
+    }
+}
+
+uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                         uint32_t basis);
+
+int ovsdb_atom_compare_3way(const union ovsdb_atom *,
+                            const union ovsdb_atom *,
+                            enum ovsdb_atomic_type);
+
+static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
+                                     const union ovsdb_atom *b,
+                                     enum ovsdb_atomic_type type)
+{
+    return !ovsdb_atom_compare_3way(a, b, type);
+}
+
+struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
+                                         enum ovsdb_atomic_type,
+                                         const struct json *,
+                                         const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
+                                enum ovsdb_atomic_type);
+\f
+/* One value of an OVSDB type (given by struct ovsdb_type). */
+struct ovsdb_datum {
+    unsigned int n;             /* Number of 'keys' and 'values'. */
+    union ovsdb_atom *keys;     /* Each of the ovsdb_type's 'key_type'. */
+    union ovsdb_atom *values;   /* Each of the ovsdb_type's 'value_type'. */
+};
+
+void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *,
+                       const struct ovsdb_type *);
+void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
+
+struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
+                                          const struct ovsdb_type *,
+                                          const struct json *,
+                                          const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
+                                 const struct ovsdb_type *);
+
+uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
+                          const struct ovsdb_type *, uint32_t basis);
+int ovsdb_datum_compare_3way(const struct ovsdb_datum *,
+                             const struct ovsdb_datum *,
+                             const struct ovsdb_type *);
+bool ovsdb_datum_equals(const struct ovsdb_datum *,
+                        const struct ovsdb_datum *,
+                        const struct ovsdb_type *);
+bool ovsdb_datum_includes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+
+static inline bool
+ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
+                             const struct ovsdb_type *type)
+{
+    return datum->n >= type->n_min && datum->n <= type->n_max;
+}
+\f
+/* A table mapping from names to data items.  Currently the data items are
+ * always UUIDs; perhaps this will be expanded in the future. */
+
+struct ovsdb_symbol_table *ovsdb_symbol_table_create(void);
+void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *);
+const struct uuid *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *,
+                                          const char *name);
+void ovsdb_symbol_table_put(struct ovsdb_symbol_table *, const char *name,
+                            const struct uuid *);
+
+#endif /* ovsdb-data.h */
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
new file mode 100644 (file)
index 0000000..c0eddf2
--- /dev/null
@@ -0,0 +1,221 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-error.h"
+
+#include <inttypes.h>
+
+#include "backtrace.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "util.h"
+
+struct ovsdb_error {
+    const char *tag;            /* String for "error" member. */
+    char *details;              /* String for "details" member. */
+    char *syntax;               /* String for "syntax" member. */
+    int errno_;                 /* Unix errno value, 0 if none. */
+};
+
+static struct ovsdb_error *
+ovsdb_error_valist(const char *tag, const char *details, va_list args)
+{
+    struct ovsdb_error *error = xmalloc(sizeof *error);
+    error->tag = tag ? tag : "ovsdb error";
+    error->details = details ? xvasprintf(details, args) : NULL;
+    error->syntax = NULL;
+    error->errno_ = 0;
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_error(const char *tag, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag, details, args);
+    va_end(args);
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_io_error(int errno_, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist("I/O error", details, args);
+    va_end(args);
+
+    error->errno_ = errno_;
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_syntax_error(const struct json *json, const char *tag,
+                   const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag ? tag : "syntax error", details, args);
+    va_end(args);
+
+    if (json) {
+        /* XXX this is much too much information in some cases */
+        error->syntax = json_to_string(json, 0);
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...)
+{
+    va_list args;
+    char *msg;
+
+    va_start(args, details);
+    msg = xvasprintf(details, args);
+    va_end(args);
+
+    if (error->details) {
+        char *new = xasprintf("%s: %s", msg, error->details);
+        free(error->details);
+        error->details = new;
+        free(msg);
+    } else {
+        error->details = msg;
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_internal_error(const char *file, int line, const char *details, ...)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct backtrace backtrace;
+    struct ovsdb_error *error;
+    va_list args;
+
+    ds_put_format(&ds, "%s:%d:", file, line);
+
+    if (details) {
+        ds_put_char(&ds, ' ');
+        va_start(args, details);
+        ds_put_format_valist(&ds, details, args);
+        va_end(args);
+    }
+
+    backtrace_capture(&backtrace);
+    if (backtrace.n_frames) {
+        int i;
+
+        ds_put_cstr(&ds, " (backtrace:");
+        for (i = 0; i < backtrace.n_frames; i++) {
+            ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]);
+        }
+        ds_put_char(&ds, ')');
+    }
+
+    ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
+
+    error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
+
+    ds_destroy(&ds);
+
+    return error;
+}
+
+void
+ovsdb_error_destroy(struct ovsdb_error *error)
+{
+    if (error) {
+        free(error->details);
+        free(error->syntax);
+        free(error);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_error_clone(const struct ovsdb_error *old)
+{
+    if (old) {
+        struct ovsdb_error *new = xmalloc(sizeof *new);
+        new->tag = old->tag;
+        new->details = old->details ? xstrdup(old->details) : NULL;
+        new->syntax = old->syntax ? xstrdup(old->syntax) : NULL;
+        new->errno_ = old->errno_;
+        return new;
+    } else {
+        return NULL;
+    }
+}
+
+static const char *
+ovsdb_errno_string(int error)
+{
+    return error == EOF ? "unexpected end of file" : strerror(error);
+}
+
+struct json *
+ovsdb_error_to_json(const struct ovsdb_error *error)
+{
+    struct json *json = json_object_create();
+    json_object_put_string(json, "error", error->tag);
+    if (error->details) {
+        json_object_put_string(json, "details", error->details);
+    }
+    if (error->syntax) {
+        json_object_put_string(json, "syntax", error->syntax);
+    }
+    if (error->errno_) {
+        json_object_put_string(json, "io-error",
+                               ovsdb_errno_string(error->errno_));
+    }
+    return json;
+}
+
+char *
+ovsdb_error_to_string(const struct ovsdb_error *error)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    if (error->syntax) {
+        ds_put_format(&ds, "syntax \"%s\": ", error->syntax);
+    }
+    ds_put_cstr(&ds, error->tag);
+    if (error->details) {
+        ds_put_format(&ds, ": %s", error->details);
+    }
+    if (error->errno_) {
+        ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_));
+    }
+    return ds_steal_cstr(&ds);
+}
+
+const char *
+ovsdb_error_get_tag(const struct ovsdb_error *error)
+{
+    return error->tag;
+}
diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h
new file mode 100644 (file)
index 0000000..7e2523e
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ERROR_H
+#define OVSDB_ERROR_H 1
+
+#include "compiler.h"
+
+struct json;
+
+struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag,
+                                       const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error,
+                                     const char *details, ...)
+    PRINTF_FORMAT(2, 3);
+
+struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+                                         const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+void ovsdb_error_destroy(struct ovsdb_error *);
+struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
+    WARN_UNUSED_RESULT;
+
+char *ovsdb_error_to_string(const struct ovsdb_error *);
+struct json *ovsdb_error_to_json(const struct ovsdb_error *);
+
+const char *ovsdb_error_get_tag(const struct ovsdb_error *);
+
+#endif /* ovsdb-error.h */
diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c
new file mode 100644 (file)
index 0000000..d923d21
--- /dev/null
@@ -0,0 +1,167 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-parser.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "ovsdb-error.h"
+
+void
+ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json,
+                  const char *name, ...)
+{
+    va_list args;
+
+    va_start(args, name);
+    parser->name = xvasprintf(name, args);
+    va_end(args);
+
+    svec_init(&parser->used);
+    parser->error = NULL;
+
+    parser->json = (json && json->type == JSON_OBJECT ? json : NULL);
+    if (!parser->json) {
+        ovsdb_parser_raise_error(parser, "Object expected.");
+    }
+}
+
+static bool
+is_id(const char *string)
+{
+    unsigned char c;
+
+    c = *string;
+    if (!isalpha(c) && c != '_') {
+        return false;
+    }
+
+    for (;;) {
+        c = *++string;
+        if (c == '\0') {
+            return true;
+        } else if (!isalpha(c) && !isdigit(c) && c != '_') {
+            return false;
+        }
+    }
+}
+
+const struct json *
+ovsdb_parser_member(struct ovsdb_parser *parser, const char *name,
+                    enum ovsdb_parser_types types)
+{
+    struct json *value;
+
+    if (!parser->json) {
+        return NULL;
+    }
+
+    value = shash_find_data(json_object(parser->json), name);
+    if (!value) {
+        if (!(types & OP_OPTIONAL)) {
+            ovsdb_parser_raise_error(parser,
+                                     "Required '%s' member is missing.", name);
+        }
+        return NULL;
+    }
+
+    if (value->type >= 0 && value->type < JSON_N_TYPES
+        && (types & (1u << value->type)
+            || (types & OP_ID
+                && value->type == JSON_STRING
+                && is_id(value->u.string))))
+    {
+        svec_add(&parser->used, name);
+        return value;
+    } else {
+        ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.",
+                                 name);
+        return NULL;
+    }
+}
+
+void
+ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...)
+{
+    if (!parser->error) {
+        struct ovsdb_error *error;
+        va_list args;
+        char *message;
+
+        va_start(args, format);
+        message = xvasprintf(format, args);
+        va_end(args);
+
+        error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s",
+                                   parser->name, message);
+        free(message);
+
+        parser->error = error;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_parser_get_error(const struct ovsdb_parser *parser)
+{
+    return parser->error ? ovsdb_error_clone(parser->error) : NULL;
+}
+
+bool
+ovsdb_parser_has_error(const struct ovsdb_parser *parser)
+{
+    return parser->error != NULL;
+}
+
+struct ovsdb_error *
+ovsdb_parser_finish(struct ovsdb_parser *parser)
+{
+    if (!parser->error) {
+        const struct shash *object = json_object(parser->json);
+        size_t n_unused;
+
+        /* XXX this method of detecting unused members can be made cheaper */
+        svec_sort_unique(&parser->used);
+        n_unused = shash_count(object) - parser->used.n;
+        if (n_unused) {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, object) {
+                if (!svec_contains(&parser->used, node->name)) {
+                    if (n_unused > 1) {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' and %zu other member%s "
+                            "are present but not allowed here.",
+                            node->name, n_unused - 1, n_unused > 2 ? "s" : "");
+                    } else {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' is present but not allowed here.",
+                            node->name);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    free(parser->name);
+    svec_destroy(&parser->used);
+
+    return parser->error;
+}
diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h
new file mode 100644 (file)
index 0000000..f9a2ef7
--- /dev/null
@@ -0,0 +1,74 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_PARSER_H
+#define OVSDB_PARSER_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "json.h"
+#include "svec.h"
+#include "util.h"
+
+struct ovsdb_parser {
+    char *name;                 /* Used only in error messages. */
+    struct svec used;           /* Already-parsed names from 'object'. */
+    const struct json *json;    /* JSON object being parsed. */
+    struct ovsdb_error *error;  /* Error signaled, if any. */
+};
+
+/* Check that the JSON types make the bitwise tricks below work OK. */
+BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10);
+BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10);
+BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10);
+BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10);
+BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10);
+BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10);
+BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10);
+BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10);
+BUILD_ASSERT_DECL(JSON_N_TYPES == 8);
+
+enum ovsdb_parser_types {
+    OP_NULL = 1 << JSON_NULL,             /* null */
+    OP_FALSE = 1 << JSON_FALSE,           /* false */
+    OP_TRUE = 1 << JSON_TRUE,             /* true */
+    OP_OBJECT = 1 << JSON_OBJECT,         /* {"a": b, "c": d, ...} */
+    OP_ARRAY = 1 << JSON_ARRAY,           /* [1, 2, 3, ...] */
+    OP_INTEGER = 1 << JSON_INTEGER,       /* 123. */
+    OP_NONINTEGER = 1 << JSON_REAL,       /* 123.456. */
+    OP_STRING = 1 << JSON_STRING,         /* "..." */
+
+    OP_BOOLEAN = OP_FALSE | OP_TRUE,
+    OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
+
+    OP_ID = 1 << JSON_N_TYPES,            /* "[_a-zA-Z][_a-zA-Z0-9]*" */
+    OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */
+};
+
+void ovsdb_parser_init(struct ovsdb_parser *, const struct json *,
+                       const char *name, ...)
+    PRINTF_FORMAT(3, 4);
+const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name,
+                                     enum ovsdb_parser_types);
+
+void ovsdb_parser_raise_error(struct ovsdb_parser *parser,
+                              const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+bool ovsdb_parser_has_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb-parser.h */
diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c
new file mode 100644 (file)
index 0000000..07982e3
--- /dev/null
@@ -0,0 +1,251 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-types.h"
+
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+
+const struct ovsdb_type ovsdb_type_integer =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER);
+const struct ovsdb_type ovsdb_type_real =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL);
+const struct ovsdb_type ovsdb_type_boolean =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN);
+const struct ovsdb_type ovsdb_type_string =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING);
+const struct ovsdb_type ovsdb_type_uuid =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID);
+
+const char *
+ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        return "void";
+
+    case OVSDB_TYPE_INTEGER:
+        return "integer";
+
+    case OVSDB_TYPE_REAL:
+        return "real";
+
+    case OVSDB_TYPE_BOOLEAN:
+        return "boolean";
+
+    case OVSDB_TYPE_STRING:
+        return "string";
+
+    case OVSDB_TYPE_UUID:
+        return "uuid";
+
+    case OVSDB_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+
+struct json *
+ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
+{
+    return json_string_create(ovsdb_atomic_type_to_string(type));
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+    return (type->key_type != OVSDB_TYPE_VOID
+            && ovsdb_atomic_type_is_valid(type->key_type)
+            && ovsdb_atomic_type_is_valid(type->value_type)
+            && type->n_min <= type->n_max
+            && (type->value_type == OVSDB_TYPE_VOID
+                || ovsdb_atomic_type_is_valid_key(type->key_type)));
+}
+
+bool
+ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
+{
+    if (!strcmp(string, "integer")) {
+        *type = OVSDB_TYPE_INTEGER;
+    } else if (!strcmp(string, "real")) {
+        *type = OVSDB_TYPE_REAL;
+    } else if (!strcmp(string, "boolean")) {
+        *type = OVSDB_TYPE_BOOLEAN;
+    } else if (!strcmp(string, "string")) {
+        *type = OVSDB_TYPE_STRING;
+    } else if (!strcmp(string, "uuid")) {
+        *type = OVSDB_TYPE_UUID;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
+                            const struct json *json)
+{
+    if (json->type == JSON_STRING) {
+        if (ovsdb_atomic_type_from_string(json_string(json), type)) {
+            return NULL;
+        } else {
+            *type = OVSDB_TYPE_VOID;
+            return ovsdb_syntax_error(json, NULL,
+                                      "\"%s\" is not an atomic-type",
+                                      json_string(json));
+        }
+    } else {
+        *type = OVSDB_TYPE_VOID;
+        return ovsdb_syntax_error(json, NULL, "atomic-type expected");
+    }
+}
+
+static struct ovsdb_error *
+n_from_json(const struct json *json, unsigned int *n)
+{
+    if (!json) {
+        return NULL;
+    } else if (json->type == JSON_INTEGER
+               && json->u.integer >= 0 && json->u.integer < UINT_MAX) {
+        *n = json->u.integer;
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "bad min or max value");
+    }
+}
+
+char *
+ovsdb_type_to_english(const struct ovsdb_type *type)
+{
+    const char *key = ovsdb_atomic_type_to_string(type->key_type);
+    const char *value = ovsdb_atomic_type_to_string(type->value_type);
+    if (ovsdb_type_is_scalar(type)) {
+        return xstrdup(key);
+    } else {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map");
+        if (type->n_max == UINT_MAX) {
+            if (type->n_min) {
+                ds_put_format(&s, " of %u or more", type->n_min);
+            } else {
+                ds_put_cstr(&s, " of");
+            }
+        } else if (type->n_min) {
+            ds_put_format(&s, " of %u to %u", type->n_min, type->n_max);
+        } else {
+            ds_put_format(&s, " of up to %u", type->n_max);
+        }
+        if (ovsdb_type_is_set(type)) {
+            ds_put_format(&s, " %ss", key);
+        } else {
+            ds_put_format(&s, " (%s, %s) pairs", key, value);
+        }
+        return ds_cstr(&s);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
+{
+    type->value_type = OVSDB_TYPE_VOID;
+    type->n_min = 1;
+    type->n_max = 1;
+
+    if (json->type == JSON_STRING) {
+        return ovsdb_atomic_type_from_json(&type->key_type, json);
+    } else if (json->type == JSON_OBJECT) {
+        const struct json *key, *value, *min, *max;
+        struct ovsdb_error *error;
+        struct ovsdb_parser parser;
+
+        ovsdb_parser_init(&parser, json, "ovsdb type");
+        key = ovsdb_parser_member(&parser, "key", OP_STRING);
+        value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL);
+        min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "max",
+                                  OP_INTEGER | OP_STRING | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            return error;
+        }
+
+        error = ovsdb_atomic_type_from_json(&type->key_type, key);
+        if (error) {
+            return error;
+        }
+
+        if (value) {
+            error = ovsdb_atomic_type_from_json(&type->value_type, value);
+            if (error) {
+                return error;
+            }
+        }
+
+        error = n_from_json(min, &type->n_min);
+        if (error) {
+            return error;
+        }
+
+        if (max && max->type == JSON_STRING
+            && !strcmp(max->u.string, "unlimited")) {
+            type->n_max = UINT_MAX;
+        } else {
+            error = n_from_json(max, &type->n_max);
+            if (error) {
+                return error;
+            }
+        }
+
+        if (!ovsdb_type_is_valid(type)) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "ovsdb type fails constraint checks");
+        }
+
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "ovsdb type expected");
+    }
+}
+
+struct json *
+ovsdb_type_to_json(const struct ovsdb_type *type)
+{
+    if (ovsdb_type_is_scalar(type)) {
+        return ovsdb_atomic_type_to_json(type->key_type);
+    } else {
+        struct json *json = json_object_create();
+        json_object_put(json, "key",
+                        ovsdb_atomic_type_to_json(type->key_type));
+        if (type->value_type != OVSDB_TYPE_VOID) {
+            json_object_put(json, "value",
+                            ovsdb_atomic_type_to_json(type->value_type));
+        }
+        if (type->n_min != 1) {
+            json_object_put(json, "min", json_integer_create(type->n_min));
+        }
+        if (type->n_max == UINT_MAX) {
+            json_object_put_string(json, "max", "unlimited");
+        } else if (type->n_max != 1) {
+            json_object_put(json, "max", json_integer_create(type->n_max));
+        }
+        return json;
+    }
+}
diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h
new file mode 100644 (file)
index 0000000..78d76c9
--- /dev/null
@@ -0,0 +1,126 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TYPES_H
+#define OVSDB_TYPES_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+#include "uuid.h"
+
+struct json;
+
+/* An atomic type: one that OVSDB regards as a single unit of data. */
+enum ovsdb_atomic_type {
+    OVSDB_TYPE_VOID,            /* No value. */
+    OVSDB_TYPE_INTEGER,         /* Signed 64-bit integer. */
+    OVSDB_TYPE_REAL,            /* IEEE 754 double-precision floating point. */
+    OVSDB_TYPE_BOOLEAN,         /* True or false. */
+    OVSDB_TYPE_STRING,          /* UTF-8 string. */
+    OVSDB_TYPE_UUID,            /* RFC 4122 UUID referencing a table row. */
+    OVSDB_N_TYPES
+};
+
+static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
+static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type);
+bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
+struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
+                                                const struct json *);
+const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
+struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
+\f
+/* An OVSDB type.  One of:
+ *
+ *      - An atomic type.
+ *
+ *      - A set of atomic types.
+ *
+ *      - A map from one atomic type to another.
+ */
+struct ovsdb_type {
+    enum ovsdb_atomic_type key_type;
+    enum ovsdb_atomic_type value_type;
+    unsigned int n_min;
+    unsigned int n_max;         /* UINT_MAX stands in for "unlimited". */
+};
+
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \
+        { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 }
+
+extern const struct ovsdb_type ovsdb_type_integer;
+extern const struct ovsdb_type ovsdb_type_real;
+extern const struct ovsdb_type ovsdb_type_boolean;
+extern const struct ovsdb_type ovsdb_type_string;
+extern const struct ovsdb_type ovsdb_type_uuid;
+
+bool ovsdb_type_is_valid(const struct ovsdb_type *);
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *);
+
+char *ovsdb_type_to_english(const struct ovsdb_type *);
+
+struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *,
+                                         const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_type_to_json(const struct ovsdb_type *);
+\f
+/* Inline function implementations. */
+
+static inline bool
+ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
+{
+    return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
+}
+
+static inline bool
+ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type)
+{
+    /* XXX should we disallow reals or booleans as keys? */
+    return ovsdb_atomic_type_is_valid(atomic_type);
+}
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && type->n_min == 1 && type->n_max == 1);
+}
+
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type)
+{
+    return type->n_min == 0;
+}
+
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
+{
+    return type->n_max > 1;
+}
+
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && (type->n_min != 1 || type->n_max != 1));
+}
+
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
+{
+    return type->value_type != OVSDB_TYPE_VOID;
+}
+
+#endif /* ovsdb-types.h */
diff --git a/lib/sort.c b/lib/sort.c
new file mode 100644 (file)
index 0000000..017b0a9
--- /dev/null
@@ -0,0 +1,70 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "sort.h"
+
+#include "random.h"
+
+static size_t
+partition(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t x = r - 1;
+    size_t i, j;
+
+    i = p;
+    for (j = p; j < x; j++) {
+        if (compare(j, x, aux) <= 0) {
+            swap(i++, j, aux);
+        }
+    }
+    swap(i, x, aux);
+    return i;
+}
+
+static void
+quicksort(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t i, q;
+
+    if (r - p < 2) {
+        return;
+    }
+
+    i = random_range(r - p) + p;
+    if (r - 1 != i) {
+        swap(r - 1, i, aux);
+    }
+
+    q = partition(p, r, compare, swap, aux);
+    quicksort(p, q, compare, swap, aux);
+    quicksort(q, r, compare, swap, aux);
+}
+
+void
+sort(size_t count,
+     int (*compare)(size_t a, size_t b, void *aux),
+     void (*swap)(size_t a, size_t b, void *aux),
+     void *aux)
+{
+    quicksort(0, count, compare, swap, aux);
+}
diff --git a/lib/sort.h b/lib/sort.h
new file mode 100644 (file)
index 0000000..c952f44
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SORT_H
+#define SORT_H 1
+
+#include <stddef.h>
+
+void sort(size_t count,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux);
+
+#endif /* sort.h */
index 684954ea75779371060947e842ed92cce2e4c1c9..d5a59ab0cbd6bddf0bb70f1b3256f8fcedf5e45c 100644 (file)
@@ -54,6 +54,11 @@ VLOG_MODULE(ofctl)
 VLOG_MODULE(ovs_discover)
 VLOG_MODULE(ofproto)
 VLOG_MODULE(openflowd)
+VLOG_MODULE(ovsdb)
+VLOG_MODULE(ovsdb_file)
+VLOG_MODULE(ovsdb_jsonrpc_server)
+VLOG_MODULE(ovsdb_server)
+VLOG_MODULE(ovsdb_tool)
 VLOG_MODULE(pktbuf)
 VLOG_MODULE(pcap)
 VLOG_MODULE(poll_loop)
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644 (file)
index 0000000..12d9768
--- /dev/null
@@ -0,0 +1,628 @@
+         ===================================================
+          Open vSwitch Configuration Database Specification
+         ===================================================
+
+Basic Notation
+--------------
+
+The descriptions below use the following shorthand notations for JSON
+values.  Additional notation is presented later.
+
+<string>
+
+    A JSON string.
+
+<id>
+
+    A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*.
+
+    <id>s that begin with _ are reserved to the implementation and may
+    not be used by the user.
+
+<boolean>
+
+    A JSON true or false value.
+
+<number>
+
+    A JSON number.
+
+<integer>
+
+    A JSON number with an integer value, within a certain range
+    (currently -2**63...+2**63-1).
+
+Schema Format
+-------------
+
+An Open vSwitch configuration database consists of a set of tables,
+each of which has a number of columns and zero or more rows.  A schema
+is represented by <database-schema>, as described below.
+
+<database-schema>
+
+    A JSON object with the following members:
+
+        "name": <id>                            required
+        "comment": <string>                     optional
+        "tables": {<id>: <table-schema>, ...}   required
+
+    The "name" identifies the database as a whole.  The "comment"
+    optionally provides more information about the database.  The
+    value of "tables" is a JSON object whose names are table names and
+    whose values are <table-schema>s.
+
+<table-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "columns": {<id>: <column-schema>, ...}   required
+
+    The "comment" optionally provides information about this table for
+    a human reader.  The value of "tables" is a JSON object whose
+    names are table names and whose values are <column-schema>s.
+
+    Every table has the following columns whose definitions are not
+    included in the schema:
+
+        "_uuid": This column, which contains exactly one UUID value,
+        is initialized to a random value by the database engine when
+        it creates a row.  It is read-only, and its value never
+        changes during the lifetime of a row.
+
+        "_version": Like "_uuid", this column contains exactly one
+        UUID value, initialized to a random value by the database
+        engine when it creates a row, and it is read-only.  However,
+        its value changes to a new random value whenever any other
+        field in the row changes.  Furthermore, its value is
+        ephemeral: when the database is closed and reopened, or when
+        the database process is stopped and then started again, each
+        "_version" also changes to a new random value.
+
+<column-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "type": <type>                            required
+        "ephemeral": <boolean>                    optional
+
+    The "comment" optionally provides information about this column
+    for a human reader.  The "type" specifies the type of data stored
+    in this column.  If "ephemeral" is specified as true, then this
+    column's values are not guaranteed to be durable; they may be lost
+    when the database restarts.
+
+<type>
+
+    The type of a database column.  Either an <atomic-type> or a JSON
+    object that describes the type of a database column, with the
+    following members:
+
+        "key": <atomic-type>               required
+        "value": <atomic-type>             optional
+        "min": <integer>                   optional
+        "max": <integer> or "unlimited"    optional
+
+    If "min" or "max" is not specified, each defaults to 1.  If "max"
+    is specified as "unlimited", then there is no specified maximum
+    number of elements, although the implementation will enforce some
+    limit.  After considering defaults, "min" must be at least 0,
+    "max" must be at least 1, and "max" must be greater than or equal
+    to "min".
+
+    If "min" and "max" are both 1 and "value" is not specified, the
+    type is the scalar type specified by "key".
+
+    If "min" is not 1 or "max" is not 1, or both, and "value" is not
+    specified, the type is a set of scalar type "key".
+
+    If "value" is specified, the type is a map from type "key" to type
+    "value".
+
+<atomic-type>
+
+    One of the strings "integer", "real", "boolean", "string", or
+    "uuid", representing the specified scalar type.
+
+Wire Protocol
+-------------
+
+The database wire protocol is implemented in JSON-RPC 1.0.  It
+consists of the following JSON-RPC methods:
+
+get_schema
+..........
+
+Request object members:
+
+    "method": "get_schema"            required
+    "params": []                      required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": <database-schema>
+    "error": null
+    "id": same "id" as request
+
+This operation retrieves a <database-schema> that describes the
+hosted database.
+
+transact
+........
+
+Request object members:
+
+    "method": "transact"              required
+    "params": [<operation>*]          required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": [<object>*]
+    "error": null
+    "id": same "id" as request
+
+The "params" array for this method consists of zero or more JSON
+objects, each of which represents a single database operation.  The
+"Operations" section below describes the valid operations.
+
+The value of "id" must be unique among all in-flight transactions
+within the current JSON-RPC session.  Otherwise, the server may return
+a JSON-RPC error.
+
+The database server executes each of the specified operations in the
+specified order, except that if an operation fails, then the remaining
+operations are not executed.
+
+The set of operations is executed as a single atomic, consistent,
+isolated transaction.  The transaction is committed only if every
+operation succeeds.  Durability of the commit is not guaranteed unless
+the "commit" operation, with "durable" set to true, is included in the
+operation set (see below).
+
+Regardless of whether errors occur, the response is always a JSON-RPC
+response with null "error" and a "result" member that is an array with
+the same number of elements as "params".  Each element of the "result"
+array corresponds to the same element of the "params" array.  The
+"result" array elements may be interpreted as follows:
+
+    - A JSON object that does not contain an "error" member indicates
+      that the operation completed successfully.  The specific members
+      of the object are specified below in the descriptions of
+      individual operations.  Some operations do not produce any
+      results, in which case the object will have no members.
+
+    - A JSON object that contains a "error" member indicates that the
+      operation completed with an error.  The value of the "error"
+      member is a short string, specified in this document, that
+      broadly indicates the class of the error.  Besides the ones
+      listed for a specific operation, any operation may result in one
+      the following "error"s:
+
+      "error": "resources exhausted"
+
+          The operation or the transaction requires more resources
+          (memory, disk, CPU, etc.) than are currently available to
+          the database server.
+
+      "error": "syntax error"
+
+          The operation is not specified correctly: a required request
+          object member is missing, an unknown or unsupported request
+          object member is present, the operation attempts to act on a
+          table that does not exist, the operation modifies a
+          read-only table column, etc.
+
+      Database implementations may use "error" strings not specified
+      in this document to indicate errors that do not fit into any of
+      the specified categories.
+
+      Optionally, the object may include a "details" member, whose
+      value is a string that describes the error in more detail for
+      the benefit of a human user or administrator.  The object may
+      also have other members that describe the error in more detail.
+      This document does not specify the names or values of these
+      members.
+
+    - A JSON null value indicates that the operation was not attempted
+      because a prior operation failed.
+
+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.
+
+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
+replying to other transactions and other JSON-RPC requests while a
+transaction or transactions containing "wait" operations are
+outstanding on the same or different JSON-RPC sessions.
+
+The section "Notation for the Wire Protocol" below describes
+additional notation for use with the wire protocol.  After that, the
+"Operations" section describes each operation.
+
+cancel
+......
+
+Request object members:
+
+    "method": "cancel"                              required
+    "params": the "id" for an outstanding request   required
+    "id": null                                      required
+
+Response object members:
+
+    <no response>
+
+This JSON-RPC notification instructs the database server to
+immediately complete or cancel the "transact" request whose "id" is
+the same as the notification's "params" value.  
+
+If the "transact" request can be completed immediately, then the
+server sends a response in the form described for "transact", above.
+Otherwise, the server sends a JSON-RPC error response of the following
+form:
+
+    "result": null
+    "error": "canceled"
+    "id": the request "id" member
+
+The "cancel" notification itself has no reply.
+
+Notation for the Wire Protocol
+------------------------------
+
+<table>
+
+    An <id> that names a table.
+
+<column>
+
+    An <id> that names a table column.
+
+<row>
+
+    A JSON object that describes a table row or a subset of a table
+    row.  Each member is the name of a table column paired with the
+    <value> of that column.
+
+<value>
+
+    A JSON value that represents the value of a column in a table row,
+    one of <atom>, a <set>, or a <map>.
+
+<atom>
+
+    A JSON value that represents a scalar value for a column, one of
+    <string>, <number>, <boolean>, <uuid>, <named-uuid>.
+
+<set>
+
+    A 2-element JSON array that represents a database set value.  The
+    first element of the array must be the string "set" and the second
+    element must be an array of zero or more <atom>s giving the values
+    in the set.  All of the <atom>s must have the same type.
+
+<map>
+
+    A 2-element JSON array that represents a database map value.  The
+    first element of the array must be the string "map" and the second
+    element must be an array of zero or more <pair>s giving the values
+    in the map.  All of the <pair>s must have the same key and value
+    types.
+
+    (JSON objects are not used to represent <map> because JSON only
+    allows string names in an object.)
+
+<pair>
+
+    A 2-element JSON array that represents a pair within a database
+    map.  The first element is an <atom> that represents the key, the
+    second element is an <atom> that represents the value.
+
+<uuid>
+
+    A 2-element JSON array that represents a UUID.  The first element
+    of the array must be the string "uuid" and the second element must
+    be a 36-character string giving the UUID in the format described
+    by RFC 4122.  For example, the following <uuid> represents the
+    UUID 550e8400-e29b-41d4-a716-446655440000:
+
+        ["uuid", "550e8400-e29b-41d4-a716-446655440000"]
+
+<named-uuid>
+
+    A 2-element JSON array that represents the UUID of a row inserted
+    in a previous "insert" operation within the same transaction.  The
+    first element of the array must be the string "named-uuid" and the
+    second element must be the string specified on a previous "insert"
+    operation's "uuid-name".  For example, if a previous "insert"
+    operation specified a "uuid-name" of "myrow", the following
+    <named-uuid> represents the UUID created by that operation:
+
+        ["named-uuid", "myrow"]
+
+<condition>
+
+    A 3-element JSON array of the form [<column>, <function>,
+    <value>] that represents a test on a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <function> must be "<", "<=", "==", "!=", ">=", ">",
+            "includes", or "excludes".
+
+            The test is true if the column's value satisfies the
+            relation <function> <value>, e.g. if the column has value
+            1 and <value> is 2, the test is true if <function> is "<",
+            "<=" or "!=", but not otherwise.
+
+            "includes" is equivalent to "=="; "excludes" is equivalent
+            to "!=".
+
+        boolean
+        string
+        uuid
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==" or "includes", the test is true if
+            the column's value equals <value>.  If <function> is "!="
+            or "excludes", the test is inverted.
+
+        set
+        map
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==", the test is true if the column's
+            value contains exactly the same values (for sets) or pairs
+            (for maps).  If <function> is "!=", the test is inverted.
+
+            If <function> is "includes", the test is true if the
+            column's value contains all of the values (for sets) or
+            pairs (for maps) in <value>.  The column's value may also
+            contain other values or pairs.
+
+            If <function> is "excludes", the test is true if the
+            column's value does not contain any of the values (for
+            sets) or pairs (for maps) in <value>.  The column's value
+            may contain other values or pairs not in <value>.
+
+            If <function> is "includes" or "excludes", then the
+            required type of <value> is slightly relaxed, in that it
+            may have fewer than the minimum number of elements
+            specified by the column's type.  If <function> is
+            "excludes", then the required type is additionally relaxed
+            in that <value> may have more than the maximum number of
+            elements specified by the column's type.
+
+<function>
+
+    One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+    "op": "insert"          required
+    "table": <table>        required
+    "row": <row>            required
+    "uuid-name": <string>   optional
+
+Result object members:
+
+    "uuid": <uuid>
+
+Semantics:
+
+    Inserts "row" into "table".  If "row" does not specify values
+    for all the columns in "table", those columns receive default
+    values.
+
+    The new row receives a new, randomly generated UUID, which is
+    returned as the "_uuid" member of the result.  If "uuid-name"
+    is supplied, then the UUID is made available under that name
+    to later operations within the same transaction.
+
+select
+......
+
+Request object members:
+
+    "op": "select"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "columns": [<column>*]        optional
+
+Result object members:
+
+    "rows": [<row>*]
+
+Semantics:
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  If "where" is an empty array, every row in "table" is
+    selected.
+
+    The "rows" member of the result is an array of objects.  Each
+    object corresponds to a matching row, with each column
+    specified in "columns" as a member, the column's name as the
+    member name and its value as the member value.  If "columns"
+    is not specified, all the table's columns are included.  If
+    two rows of the result have the same values for all included
+    columns, only one copy of that row is included in "rows".
+    Specifying "_uuid" within "columns" will avoid dropping
+    duplicates, since every row has a unique UUID.
+
+    The ordering of rows within "rows" is unspecified.
+
+update
+......
+
+Request object members:
+
+    "op": "update"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "row": <row>                  required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Updates rows in a table.
+
+    Searches "table" for rows that match all the conditions
+    specified in "where".  For each matching row, changes the
+    value of each column specified in "row" to the value for that
+    column specified in "row".
+
+    The "_uuid" and "_version" columns of a table may not be updated.
+    Columns designated read-only in the schema also may not be
+    updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+delete
+......
+
+Request object members:
+
+    "op": "delete"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Deletes all the rows from "table" that match all the conditions
+    specified in "where".
+
+    The "count" member of the result specifies the number of deleted
+    rows.
+
+wait
+....
+
+Request object members:
+
+    "op": "wait"                        required
+    "timeout": <integer>                optional
+    "table": <table>                    required
+    "where": [<condition>*]             required
+    "columns": [<column>*]              required
+    "until": "==" or "!="               required
+    "rows": [<row>*]                    required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Waits until a condition becomes true.
+
+    If "until" is "==", checks whether the query on "table" specified
+    by "where" and "columns", which is evaluated in the same way as
+    specified for "select", returns the result set specified by
+    "rows".  If it does, then the operation completes successfully.
+    Otherwise, the entire transaction rolls back.  It is automatically
+    restarted later, after a change in the database makes it possible
+    for the operation to succeed.  The client will not receive a
+    response until the operation permanently succeeds or fails.
+    
+    If "until" is "!=", the sense of the test is negated.  That is, as
+    long as the query on "table" specified by "where" and "columns"
+    returns "rows", the transaction will be rolled back and restarted
+    later.
+
+    If "timeout" is specified, then the transaction aborts after the
+    specified number of milliseconds.  The transaction is guaranteed
+    to be attempted at least once before it aborts.  A "timeout" of 0
+    will abort the transaction on the first mismatch.
+
+Errors:
+
+    "error": "not supported"
+
+        One or more of the columns in this table do not support
+        triggers.  This error will not occur if "timeout" is 0.
+
+    "error": "timed out"
+
+        The "timeout" was reached before the transaction was able to
+        complete.
+
+commit
+......
+
+Request object members:
+
+    "op": "commit"                      required
+    "durable": <boolean>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    If "durable" is specified as true, then the transaction, if it
+    commits, will be stored durably (to disk) before the reply is sent
+    to the client.
+
+Errors:
+
+    "error": "not supported"
+
+        When "durable" is true, this database implementation does not
+        support durable commits.
+
+abort
+.....
+
+Request object members:
+
+    "op": "abort"                      required
+
+Result object members:
+
+    (never succeeds)
+
+Semantics:
+
+    Aborts the transaction with an error.  This may be useful for
+    testing.
+
+Errors:
+
+    "error": "aborted"
+
+        This operation always fails with this error.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644 (file)
index 0000000..d2a3e04
--- /dev/null
@@ -0,0 +1,44 @@
+# libovsdb
+noinst_LIBRARIES += ovsdb/libovsdb.a
+ovsdb_libovsdb_a_SOURCES = \
+       ovsdb/column.c \
+       ovsdb/column.h \
+       ovsdb/condition.c \
+       ovsdb/condition.h \
+       ovsdb/execution.c \
+       ovsdb/file.c \
+       ovsdb/file.h \
+       ovsdb/jsonrpc-server.c \
+       ovsdb/jsonrpc-server.h \
+       ovsdb/ovsdb-server.c \
+       ovsdb/ovsdb.c \
+       ovsdb/ovsdb.h \
+       ovsdb/query.c \
+       ovsdb/query.h \
+       ovsdb/row.c \
+       ovsdb/row.h \
+       ovsdb/table.c \
+       ovsdb/table.h \
+       ovsdb/trigger.c \
+       ovsdb/trigger.h \
+       ovsdb/transaction.c \
+       ovsdb/transaction.h
+
+# ovsdb-tool
+bin_PROGRAMS += ovsdb/ovsdb-tool
+ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+
+## ovsdb-tool.8
+#man_MANS += ovsdb/ovsdb-tool.8
+#DISTCLEANFILES += ovsdb/ovsdb-tool.8
+#EXTRA_DIST += ovsdb/ovsdb-tool.8.in
+
+# ovsdb-server
+sbin_PROGRAMS += ovsdb/ovsdb-server
+ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(FAULT_LIBS)
+## ovsdb-server.8
+#man_MANS += ovsdb/ovsdb-server.8
+#DISTCLEANFILES += ovsdb/ovsdb-server.8
+#EXTRA_DIST += ovsdb/ovsdb-server.8.in
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644 (file)
index 0000000..1e8a2d0
--- /dev/null
@@ -0,0 +1,232 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb/column.h"
+
+#include <stdlib.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "util.h"
+
+struct ovsdb_column *
+ovsdb_column_create(const char *name, const char *comment,
+                    bool mutable, bool persistent,
+                    const struct ovsdb_type *type)
+{
+    struct ovsdb_column *ts;
+
+    ts = xzalloc(sizeof *ts);
+    ts->name = xstrdup(name);
+    ts->comment = comment ? xstrdup(comment) : NULL;
+    ts->mutable = mutable;
+    ts->persistent = persistent;
+    ts->type = *type;
+
+    return ts;
+}
+
+void
+ovsdb_column_destroy(struct ovsdb_column *column)
+{
+    free(column->name);
+    free(column->comment);
+    free(column);
+}
+
+struct ovsdb_error *
+ovsdb_column_from_json(const struct json *json, const char *name,
+                       struct ovsdb_column **columnp)
+{
+    const struct json *comment, *mutable, *ephemeral, *type_json;
+    struct ovsdb_error *error;
+    struct ovsdb_type type;
+    struct ovsdb_parser parser;
+    bool persistent;
+
+    *columnp = NULL;
+
+    ovsdb_parser_init(&parser, json, "schema for column %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    ephemeral = ovsdb_parser_member(&parser, "ephemeral",
+                                    OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_type_from_json(&type, type_json);
+    if (error) {
+        return error;
+    }
+
+    persistent = ephemeral ? !json_boolean(ephemeral) : true;
+    *columnp = ovsdb_column_create(name,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true,
+                                   persistent, &type);
+    return NULL;
+}
+
+struct json *
+ovsdb_column_to_json(const struct ovsdb_column *column)
+{
+    struct json *json = json_object_create();
+    if (column->comment) {
+        json_object_put_string(json, "comment", column->comment);
+    }
+    if (!column->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+    if (!column->persistent) {
+        json_object_put(json, "ephemeral", json_boolean_create(true));
+    }
+    json_object_put(json, "type", ovsdb_type_to_json(&column->type));
+    return json;
+}
+\f
+void
+ovsdb_column_set_init(struct ovsdb_column_set *set)
+{
+    set->columns = NULL;
+    set->n_columns = set->allocated_columns = 0;
+}
+
+void
+ovsdb_column_set_destroy(struct ovsdb_column_set *set)
+{
+    free(set->columns);
+}
+
+void
+ovsdb_column_set_clone(struct ovsdb_column_set *new,
+                       const struct ovsdb_column_set *old)
+{
+    new->columns = xmemdup(old->columns,
+                           old->n_columns * sizeof *old->columns);
+    new->n_columns = new->allocated_columns = old->n_columns;
+}
+
+struct ovsdb_error *
+ovsdb_column_set_from_json(const struct json *json,
+                           const struct ovsdb_table *table,
+                           struct ovsdb_column_set *set)
+{
+    ovsdb_column_set_init(set);
+    if (!json) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    } else {
+        size_t i;
+
+        if (json->type != JSON_ARRAY) {
+            goto error;
+        }
+
+        /* XXX this is O(n**2) */
+        for (i = 0; i < json->u.array.n; i++) {
+            struct ovsdb_column *column;
+
+            if (json->u.array.elems[i]->type != JSON_STRING) {
+                goto error;
+            }
+
+            column = shash_find_data(&table->schema->columns,
+                                     json->u.array.elems[i]->u.string);
+            if (ovsdb_column_set_contains(set, column->index)) {
+                goto error;
+            }
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    }
+
+error:
+    ovsdb_column_set_destroy(set);
+    return ovsdb_syntax_error(json, NULL,
+                              "array of distinct column names expected");
+}
+
+void
+ovsdb_column_set_add(struct ovsdb_column_set *set,
+                     const struct ovsdb_column *column)
+{
+    if (set->n_columns >= set->allocated_columns) {
+        set->columns = x2nrealloc(set->columns, &set->allocated_columns,
+                                  sizeof *set->columns);
+    }
+    set->columns[set->n_columns++] = column;
+}
+
+void
+ovsdb_column_set_add_all(struct ovsdb_column_set *set,
+                         const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_column_set_add(set, column);
+    }
+}
+
+bool
+ovsdb_column_set_contains(const struct ovsdb_column_set *set,
+                          unsigned int column_index)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_columns; i++) {
+        if (set->columns[i]->index == column_index) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* This comparison is sensitive to ordering of columns within a set, but that's
+ * good: the only existing caller wants to make sure that hash values are
+ * comparable, which is only true if column ordering is the same. */
+bool
+ovsdb_column_set_equals(const struct ovsdb_column_set *a,
+                        const struct ovsdb_column_set *b)
+{
+    size_t i;
+
+    if (a->n_columns != b->n_columns) {
+        return false;
+    }
+    for (i = 0; i < a->n_columns; i++) {
+        if (a->columns[i] != b->columns[i]) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/ovsdb/column.h b/ovsdb/column.h
new file mode 100644 (file)
index 0000000..5942151
--- /dev/null
@@ -0,0 +1,84 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_COLUMN_H
+#define OVSDB_COLUMN_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_table;
+
+/* A column or a column schema (currently there is no distinction). */
+struct ovsdb_column {
+    unsigned int index;
+    char *name;
+
+    char *comment;
+    bool mutable;
+    bool persistent;
+    struct ovsdb_type type;
+};
+
+/* A few columns appear in every table with standardized column indexes.
+ * These macros define those columns' indexes.
+ *
+ * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID
+ * having value 0. */
+enum {
+    OVSDB_COL_UUID = 0,         /* UUID for the row. */
+    OVSDB_COL_VERSION = 1,      /* Version number for the row. */
+    OVSDB_N_STD_COLUMNS
+};
+
+struct ovsdb_column *ovsdb_column_create(
+    const char *name, const char *comment, bool mutable, bool persistent,
+    const struct ovsdb_type *);
+void ovsdb_column_destroy(struct ovsdb_column *);
+
+struct ovsdb_error *ovsdb_column_from_json(const struct json *,
+                                           const char *name,
+                                           struct ovsdb_column **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_column_to_json(const struct ovsdb_column *);
+\f
+/* An unordered set of distinct columns. */
+
+struct ovsdb_column_set {
+    const struct ovsdb_column **columns;
+    size_t n_columns, allocated_columns;
+};
+
+#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_column_set_init(struct ovsdb_column_set *);
+void ovsdb_column_set_destroy(struct ovsdb_column_set *);
+void ovsdb_column_set_clone(struct ovsdb_column_set *,
+                            const struct ovsdb_column_set *);
+struct ovsdb_error *ovsdb_column_set_from_json(const struct json *,
+                                               const struct ovsdb_table *,
+                                               struct ovsdb_column_set *);
+
+void ovsdb_column_set_add(struct ovsdb_column_set *,
+                          const struct ovsdb_column *);
+void ovsdb_column_set_add_all(struct ovsdb_column_set *,
+                              const struct ovsdb_table *);
+bool ovsdb_column_set_contains(const struct ovsdb_column_set *,
+                               unsigned int column_index);
+bool ovsdb_column_set_equals(const struct ovsdb_column_set *,
+                             const struct ovsdb_column_set *);
+
+#endif /* column.h */
diff --git a/ovsdb/condition.c b/ovsdb/condition.c
new file mode 100644 (file)
index 0000000..0342b8e
--- /dev/null
@@ -0,0 +1,284 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "condition.h"
+
+#include <limits.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_function_from_string(const char *name, enum ovsdb_function *function)
+{
+#define OVSDB_FUNCTION(ENUM, NAME)              \
+    if (!strcmp(name, NAME)) {                  \
+        *function = ENUM;                       \
+        return NULL;                            \
+    }
+    OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+
+    return ovsdb_syntax_error(NULL, "unknown function",
+                              "No function named %s.", name);
+}
+
+const char *
+ovsdb_function_to_string(enum ovsdb_function function)
+{
+    switch (function) {
+#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+    }
+
+    return NULL;
+}
+
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
+                       const struct json *json,
+                       const struct ovsdb_symbol_table *symtab,
+                       struct ovsdb_clause *clause)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *function_name;
+    const char *column_name;
+    struct ovsdb_type type;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in condition.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    clause->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!clause->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    type = clause->column->type;
+
+    function_name = json_string(array->elems[1]);
+    error = ovsdb_function_from_string(function_name, &clause->function);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (clause->function) {
+    case OVSDB_F_LT:
+    case OVSDB_F_LE:
+    case OVSDB_F_GT:
+    case OVSDB_F_GE:
+        /* XXX should we also allow these operators for types with n_min == 0,
+         * n_max == 1?  (They would always be "false" if the value was
+         * missing.) */
+        if (!ovsdb_type_is_scalar(&type)
+            || (type.key_type != OVSDB_TYPE_INTEGER
+                && type.key_type != OVSDB_TYPE_REAL)) {
+            char *s = ovsdb_type_to_english(&type);
+            error = ovsdb_syntax_error(
+                json, NULL, "Type mismatch: \"%s\" operator may not be "
+                "applied to column %s of type %s.",
+                ovsdb_function_to_string(clause->function),
+                clause->column->name, s);
+            free(s);
+            return error;
+        }
+        break;
+
+    case OVSDB_F_EQ:
+    case OVSDB_F_NE:
+        break;
+
+    case OVSDB_F_EXCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+            type.n_max = UINT_MAX;
+        }
+        break;
+
+    case OVSDB_F_INCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+        }
+        break;
+    }
+    return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab);
+}
+
+static void
+ovsdb_clause_free(struct ovsdb_clause *clause)
+{
+    ovsdb_datum_destroy(&clause->arg, &clause->column->type);
+}
+
+static int
+compare_clauses_3way(const void *a_, const void *b_)
+{
+    const struct ovsdb_clause *a = a_;
+    const struct ovsdb_clause *b = b_;
+
+    if (a->function != b->function) {
+        /* Bring functions to the front based on the fraction of table rows
+         * that they are (heuristically) expected to leave in the query
+         * results.  Note that "enum ovsdb_function" is intentionally ordered
+         * to make this trivial. */
+        return a->function < b->function ? -1 : 1;
+    } else if (a->column->index != b->column->index) {
+        if (a->column->index < OVSDB_N_STD_COLUMNS
+            || b->column->index < OVSDB_N_STD_COLUMNS) {
+            /* Bring the standard columns and in particular the UUID column
+             * (since OVSDB_COL_UUID has value 0) to the front.  We have an
+             * index on the UUID column, so that makes our queries cheaper. */
+            return a->column->index < b->column->index ? -1 : 1;
+        } else {
+            /* Order clauses predictably to make testing easier. */
+            return strcmp(a->column->name, b->column->name);
+        }
+    } else {
+        return 0;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_condition_from_json(const struct ovsdb_table_schema *ts,
+                          const struct json *json,
+                          const struct ovsdb_symbol_table *symtab,
+                          struct ovsdb_condition *cnd)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses);
+    cnd->n_clauses = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_clause_from_json(ts, array->elems[i], symtab,
+                                       &cnd->clauses[i]);
+        if (error) {
+            ovsdb_condition_destroy(cnd);
+            cnd->clauses = NULL;
+            cnd->n_clauses = 0;
+            return error;
+        }
+        cnd->n_clauses++;
+    }
+
+    /* A real database would have a query optimizer here. */
+    qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses,
+          compare_clauses_3way);
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_clause_to_json(const struct ovsdb_clause *clause)
+{
+    return json_array_create_3(
+        json_string_create(clause->column->name),
+        json_string_create(ovsdb_function_to_string(clause->function)),
+        ovsdb_datum_to_json(&clause->arg, &clause->column->type));
+}
+
+struct json *
+ovsdb_condition_to_json(const struct ovsdb_condition *cnd)
+{
+    struct json **clauses;
+    size_t i;
+
+    clauses = xmalloc(cnd->n_clauses * sizeof *clauses);
+    for (i = 0; i < cnd->n_clauses; i++) {
+        clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]);
+    }
+    return json_array_create(clauses, cnd->n_clauses);
+}
+
+bool
+ovsdb_condition_evaluate(const struct ovsdb_row *row,
+                         const struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        const struct ovsdb_clause *c = &cnd->clauses[i];
+        const struct ovsdb_datum *field = &row->fields[c->column->index];
+        const struct ovsdb_datum *arg = &cnd->clauses[i].arg;
+        const struct ovsdb_type *type = &c->column->type;
+
+        if (ovsdb_type_is_scalar(type)) {
+            int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
+                                              type->key_type);
+            switch (c->function) {
+            case OVSDB_F_LT:
+                return cmp < 0;
+            case OVSDB_F_LE:
+                return cmp <= 0;
+            case OVSDB_F_EQ:
+            case OVSDB_F_INCLUDES:
+                return cmp == 0;
+            case OVSDB_F_NE:
+            case OVSDB_F_EXCLUDES:
+                return cmp != 0;
+            case OVSDB_F_GE:
+                return cmp >= 0;
+            case OVSDB_F_GT:
+                return cmp > 0;
+            }
+        } else {
+            switch (c->function) {
+            case OVSDB_F_EQ:
+                return ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_NE:
+                return !ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_INCLUDES:
+                return ovsdb_datum_includes_all(arg, field, type);
+            case OVSDB_F_EXCLUDES:
+                return ovsdb_datum_excludes_all(arg, field, type);
+            case OVSDB_F_LT:
+            case OVSDB_F_LE:
+            case OVSDB_F_GE:
+            case OVSDB_F_GT:
+                NOT_REACHED();
+            }
+        }
+        NOT_REACHED();
+    }
+
+    return true;
+}
+
+void
+ovsdb_condition_destroy(struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        ovsdb_clause_free(&cnd->clauses[i]);
+    }
+    free(cnd->clauses);
+}
diff --git a/ovsdb/condition.h b/ovsdb/condition.h
new file mode 100644 (file)
index 0000000..8c422b9
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_CONDITION_H
+#define OVSDB_CONDITION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_FUNCTIONS                         \
+    OVSDB_FUNCTION(OVSDB_F_EQ, "==")                  \
+    OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes")      \
+    OVSDB_FUNCTION(OVSDB_F_LE, "<=")                  \
+    OVSDB_FUNCTION(OVSDB_F_LT, "<")                   \
+    OVSDB_FUNCTION(OVSDB_F_GE, ">=")                  \
+    OVSDB_FUNCTION(OVSDB_F_GT, ">")                   \
+    OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes")      \
+    OVSDB_FUNCTION(OVSDB_F_NE, "!=")
+
+enum ovsdb_function {
+#define OVSDB_FUNCTION(ENUM, NAME) ENUM,
+    OVSDB_FUNCTIONS
+#undef OVSDB_FUNCTION
+};
+
+struct ovsdb_error *ovsdb_function_from_string(const char *,
+                                               enum ovsdb_function *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_function_to_string(enum ovsdb_function);
+
+struct ovsdb_clause {
+    enum ovsdb_function function;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+};
+
+struct ovsdb_condition {
+    struct ovsdb_clause *clauses;
+    size_t n_clauses;
+};
+
+#define OVSDB_CONDITION_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_condition_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, const struct ovsdb_symbol_table *,
+    struct ovsdb_condition *) WARN_UNUSED_RESULT;
+struct json *ovsdb_condition_to_json(const struct ovsdb_condition *);
+void ovsdb_condition_destroy(struct ovsdb_condition *);
+bool ovsdb_condition_evaluate(const struct ovsdb_row *,
+                              const struct ovsdb_condition *);
+
+#endif /* ovsdb/condition.h */
diff --git a/ovsdb/execution.c b/ovsdb/execution.c
new file mode 100644 (file)
index 0000000..25b34b1
--- /dev/null
@@ -0,0 +1,613 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+    struct ovsdb *db;
+    struct ovsdb_txn *txn;
+    struct ovsdb_symbol_table *symtab;
+    bool durable;
+
+    /* Triggers. */
+    long long int elapsed_msec;
+    long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+                                                     struct ovsdb_parser *,
+                                                     struct json *result);
+
+static struct ovsdb_error *do_commit(struct ovsdb_execution *);
+static ovsdb_operation_executor ovsdb_execute_insert;
+static ovsdb_operation_executor ovsdb_execute_select;
+static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+    struct ovsdb_operation {
+        const char *name;
+        ovsdb_operation_executor *executor;
+    };
+
+    static const struct ovsdb_operation operations[] = {
+        { "insert", ovsdb_execute_insert },
+        { "select", ovsdb_execute_select },
+        { "update", ovsdb_execute_update },
+        { "delete", ovsdb_execute_delete },
+        { "wait", ovsdb_execute_wait },
+        { "commit", ovsdb_execute_commit },
+        { "abort", ovsdb_execute_abort },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(operations); i++) {
+        const struct ovsdb_operation *c = &operations[i];
+        if (!strcmp(c->name, name)) {
+            return c->executor;
+        }
+    }
+    return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+              long long int elapsed_msec, long long int *timeout_msec)
+{
+    struct ovsdb_execution x;
+    struct ovsdb_error *error;
+    struct json *results;
+    size_t n_operations;
+    size_t i;
+
+    if (params->type != JSON_ARRAY) {
+        struct ovsdb_error *error;
+
+        error = ovsdb_syntax_error(params, NULL, "array expected");
+        results = ovsdb_error_to_json(error);
+        ovsdb_error_destroy(error);
+        return results;
+    }
+
+    x.db = db;
+    x.txn = ovsdb_txn_create(db);
+    x.symtab = ovsdb_symbol_table_create();
+    x.durable = false;
+    x.elapsed_msec = elapsed_msec;
+    x.timeout_msec = LLONG_MAX;
+    results = NULL;
+
+    results = json_array_create_empty();
+    n_operations = params->u.array.n;
+    error = NULL;
+    for (i = 0; i < n_operations; i++) {
+        struct json *operation = params->u.array.elems[i];
+        struct ovsdb_error *parse_error;
+        struct ovsdb_parser parser;
+        struct json *result;
+        const struct json *op;
+
+        /* Parse and execute operation. */
+        ovsdb_parser_init(&parser, operation,
+                          "ovsdb operation %zu of %zu", i + 1, n_operations);
+        op = ovsdb_parser_member(&parser, "op", OP_ID);
+        result = json_object_create();
+        if (op) {
+            const char *op_name = json_string(op);
+            ovsdb_operation_executor *executor = lookup_executor(op_name);
+            if (executor) {
+                error = executor(&x, &parser, result);
+            } else {
+                error = ovsdb_syntax_error(operation, "unknown operation",
+                                           "No operation \"%s\"", op_name);
+            }
+        } else {
+            assert(ovsdb_parser_has_error(&parser));
+        }
+
+        /* A parse error overrides any other error.
+         * An error overrides any other result. */
+        parse_error = ovsdb_parser_finish(&parser);
+        if (parse_error) {
+            ovsdb_error_destroy(error);
+            error = parse_error;
+        }
+        if (error) {
+            json_destroy(result);
+            result = ovsdb_error_to_json(error);
+        }
+        if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+            && timeout_msec) {
+            ovsdb_txn_abort(x.txn);
+            *timeout_msec = x.timeout_msec;
+            ovsdb_error_destroy(error);
+            json_destroy(results);
+            return NULL;
+        }
+
+        /* Add result to array. */
+        json_array_add(results, result);
+        if (error) {
+            break;
+        }
+    }
+
+    if (!error) {
+        /* Commit transaction.  Bail if commit encounters error.  */
+        error = do_commit(&x);
+        if (error) {
+            json_array_add(results, ovsdb_error_to_json(error));
+        }
+    } else {
+        ovsdb_txn_abort(x.txn);
+    }
+
+    while (json_array(results)->n < n_operations) {
+        json_array_add(results, json_null_create());
+    }
+
+    ovsdb_error_destroy(error);
+    ovsdb_symbol_table_destroy(x.symtab);
+
+    return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result UNUSED)
+{
+    const struct json *durable;
+
+    durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+    if (durable && json_boolean(durable)) {
+        x->durable = true;
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_abort(struct ovsdb_execution *x UNUSED,
+                    struct ovsdb_parser *parser UNUSED,
+                    struct json *result UNUSED)
+{
+    return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_error *
+do_commit(struct ovsdb_execution *x)
+{
+    if (x->db->file) {
+        struct ovsdb_error *error;
+        struct json *json;
+
+        json = ovsdb_txn_to_json(x->txn);
+        if (!json) {
+            /* Nothing to commit. */
+            return NULL;
+        }
+
+        error = ovsdb_file_write(x->db->file, json);
+        json_destroy(json);
+        if (error) {
+            return ovsdb_wrap_error(error, "writing transaction failed");
+        }
+
+        if (x->durable) {
+            error = ovsdb_file_commit(x->db->file);
+            if (error) {
+                return ovsdb_wrap_error(error,
+                                        "committing transaction failed");
+            }
+        }
+    }
+
+    ovsdb_txn_commit(x->txn);
+    return NULL;
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+            struct ovsdb_parser *parser, const char *member)
+{
+    struct ovsdb_table *table;
+    const char *table_name;
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_ID);
+    if (!json) {
+        return NULL;
+    }
+    table_name = json_string(json);
+
+    table = shash_find_data(&x->db->tables, table_name);
+    if (!table) {
+        ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+    }
+    return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+          const struct ovsdb_table *table,
+          const struct ovsdb_symbol_table *symtab,
+          struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+    struct ovsdb_error *error;
+    const struct json *json;
+    struct ovsdb_row *row;
+
+    *rowp = NULL;
+
+    if (!table) {
+        return OVSDB_BUG("null table");
+    }
+    json = ovsdb_parser_member(parser, member, OP_OBJECT);
+    if (!json) {
+        return OVSDB_BUG("null row member");
+    }
+
+    row = ovsdb_row_create(table);
+    error = ovsdb_row_from_json(row, json, symtab, columns);
+    if (error) {
+        ovsdb_row_destroy(row);
+        return error;
+    } else {
+        *rowp = row;
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    struct ovsdb_row *row = NULL;
+    const struct json *uuid_name;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+    }
+    if (!error) {
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        if (uuid_name) {
+            ovsdb_symbol_table_put(x->symtab, json_string(uuid_name),
+                                   ovsdb_row_get_uuid(row));
+        }
+        ovsdb_txn_row_insert(x->txn, row);
+        json_object_put(result, "uuid",
+                        ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+                                            &ovsdb_type_uuid));
+        row = NULL;
+    }
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where, *columns_json, *sort_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(sort_json, table, &sort);
+    }
+    if (!error) {
+        struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+        ovsdb_query_distinct(table, &condition, &columns, &rows);
+        ovsdb_row_set_sort(&rows, &sort);
+        json_object_put(result, "rows",
+                        ovsdb_row_set_to_json(&rows, &columns));
+
+        ovsdb_row_set_destroy(&rows);
+    }
+
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_column_set_destroy(&sort);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct update_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_row *row;
+    const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+    struct update_row_cbdata *ur = ur_;
+
+    ur->n_matches++;
+    if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+        ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+                                 ur->row, ur->columns);
+    }
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct update_row_cbdata ur;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        ur.n_matches = 0;
+        ur.txn = x->txn;
+        ur.row = row;
+        ur.columns = &columns;
+        ovsdb_query(table, &condition, update_row_cb, &ur);
+        json_object_put(result, "count", json_integer_create(ur.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct delete_row_cbdata {
+    size_t n_matches;
+    const struct ovsdb_table *table;
+    struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+    struct delete_row_cbdata *dr = dr_;
+
+    dr->n_matches++;
+    ovsdb_txn_row_delete(dr->txn, row);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_error *error;
+
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        struct delete_row_cbdata dr;
+
+        dr.n_matches = 0;
+        dr.table = table;
+        dr.txn = x->txn;
+        ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+        json_object_put(result, "count", json_integer_create(dr.n_matches));
+    }
+
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct wait_auxdata {
+    struct ovsdb_row_hash *actual;
+    struct ovsdb_row_hash *expected;
+    bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+    struct wait_auxdata *aux = aux_;
+
+    if (ovsdb_row_hash_contains(aux->expected, row)) {
+        ovsdb_row_hash_insert(aux->actual, row);
+        return true;
+    } else {
+        /* The query row isn't in the expected result set, so the actual and
+         * expected results sets definitely differ and we can short-circuit the
+         * rest of the query. */
+        *aux->equal = false;
+        return false;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                   struct json *result UNUSED)
+{
+    struct ovsdb_table *table;
+    const struct json *timeout, *where, *columns_json, *until, *rows;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+    struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+    struct ovsdb_error *error;
+    struct wait_auxdata aux;
+    long long int timeout_msec = 0;
+    size_t i;
+
+    timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    until = ovsdb_parser_member(parser, "until", OP_STRING);
+    rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        if (timeout) {
+            timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+            if (timeout_msec < 0) {
+                error = ovsdb_syntax_error(timeout, NULL,
+                                           "timeout must be nonnegative");
+            } else if (timeout_msec < x->timeout_msec) {
+                x->timeout_msec = timeout_msec;
+            }
+        } else {
+            timeout_msec = LLONG_MAX;
+        }
+        if (strcmp(json_string(until), "==")
+            && strcmp(json_string(until), "!=")) {
+            error = ovsdb_syntax_error(until, NULL,
+                                       "\"until\" must be \"==\" or \"!=\"");
+        }
+    }
+    if (!error) {
+        /* Parse "rows" into 'expected'. */
+        ovsdb_row_hash_init(&expected, &columns);
+        for (i = 0; i < rows->u.array.n; i++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_create(table);
+            error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+                                        NULL);
+            if (error) {
+                break;
+            }
+
+            if (!ovsdb_row_hash_insert(&expected, row)) {
+                /* XXX Perhaps we should abort with an error or log a
+                 * warning. */
+                ovsdb_row_destroy(row);
+            }
+        }
+    }
+    if (!error) {
+        /* Execute query. */
+        bool equal = true;
+        ovsdb_row_hash_init(&actual, &columns);
+        aux.actual = &actual;
+        aux.expected = &expected;
+        aux.equal = &equal;
+        ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+        if (equal) {
+            /* We know that every row in 'actual' is also in 'expected'.  We
+             * also know that all of the rows in 'actual' are distinct and that
+             * all of the rows in 'expected' are distinct.  Therefore, if
+             * 'actual' and 'expected' have the same number of rows, then they
+             * have the same content. */
+            size_t n_actual = ovsdb_row_hash_count(&actual);
+            size_t n_expected = ovsdb_row_hash_count(&expected);
+            equal = n_actual == n_expected;
+        }
+        if (!strcmp(json_string(until), "==") != equal) {
+            if (timeout && x->elapsed_msec >= timeout_msec) {
+                if (x->elapsed_msec) {
+                    error = ovsdb_error("timed out",
+                                        "\"wait\" timed out after %lld ms",
+                                        x->elapsed_msec);
+                } else {
+                    error = ovsdb_error("timed out", "\"wait\" timed out");
+                }
+            } else {
+                /* ovsdb_execute() will change this, if triggers really are
+                 * supported. */
+                error = ovsdb_error("not supported", "triggers not supported");
+            }
+        }
+    }
+
+
+    ovsdb_row_hash_destroy(&expected, true);
+    ovsdb_row_hash_destroy(&actual, false);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644 (file)
index 0000000..4883d76
--- /dev/null
@@ -0,0 +1,360 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "file.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb-error.h"
+#include "sha1.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+enum ovsdb_file_mode {
+    OVSDB_FILE_READ,
+    OVSDB_FILE_WRITE
+};
+
+struct ovsdb_file {
+    off_t offset;
+    char *name;
+    struct lockfile *lockfile;
+    FILE *stream;
+    struct ovsdb_error *read_error;
+    struct ovsdb_error *write_error;
+    enum ovsdb_file_mode mode;
+};
+
+struct ovsdb_error *
+ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep)
+{
+    struct lockfile *lockfile;
+    struct ovsdb_error *error;
+    struct ovsdb_file *file;
+    struct stat s;
+    FILE *stream;
+    int accmode;
+    int fd;
+
+    *filep = NULL;
+
+    accmode = flags & O_ACCMODE;
+    if (accmode == O_RDWR || accmode == O_WRONLY) {
+        int retval = lockfile_lock(name, 0, &lockfile);
+        if (retval) {
+            error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
+                                   name);
+            goto error;
+        }
+    } else {
+        lockfile = NULL;
+    }
+
+    fd = open(name, flags, 0666);
+    if (fd < 0) {
+        const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open";
+        error = ovsdb_io_error(errno, "%s: %s failed", op, name);
+        goto error_unlock;
+    }
+
+    if (!fstat(fd, &s) && s.st_size == 0) {
+        /* It's (probably) a new file so fsync() its parent directory to ensure
+         * that its directory entry is committed to disk. */
+        char *dir = dir_name(name);
+        int dirfd = open(dir, O_RDONLY);
+        if (dirfd >= 0) {
+            if (fsync(dirfd) && errno != EINVAL) {
+                VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno));
+            }
+            close(dirfd);
+        } else {
+            VLOG_ERR("%s: open failed (%s)", dir, strerror(errno));
+        }
+        free(dir);
+    }
+
+    stream = fdopen(fd, (accmode == O_RDONLY ? "rb"
+                         : accmode == O_WRONLY ? "wb"
+                         : "w+b"));
+    if (!stream) {
+        error = ovsdb_io_error(errno, "%s: fdopen failed", name);
+        goto error_close;
+    }
+
+    file = xmalloc(sizeof *file);
+    file->name = xstrdup(name);
+    file->lockfile = lockfile;
+    file->stream = stream;
+    file->offset = 0;
+    file->read_error = NULL;
+    file->write_error = NULL;
+    file->mode = OVSDB_FILE_READ;
+    *filep = file;
+    return NULL;
+
+error_close:
+    close(fd);
+error_unlock:
+    lockfile_unlock(lockfile);
+error:
+    return error;
+}
+
+void
+ovsdb_file_close(struct ovsdb_file *file)
+{
+    if (file) {
+        free(file->name);
+        fclose(file->stream);
+        lockfile_unlock(file->lockfile);
+        ovsdb_error_destroy(file->read_error);
+        ovsdb_error_destroy(file->write_error);
+        free(file);
+    }
+}
+
+static const char magic[] = "OVSDB JSON ";
+
+static bool
+parse_header(char *header, unsigned long int *length,
+             uint8_t sha1[SHA1_DIGEST_SIZE])
+{
+    char *p;
+
+    /* 'header' must consist of a magic string... */
+    if (strncmp(header, magic, strlen(magic))) {
+        return false;
+    }
+
+    /* ...followed by a length in bytes... */
+    *length = strtoul(header + strlen(magic), &p, 10);
+    if (!*length || *length == ULONG_MAX || *p != ' ') {
+        return false;
+    }
+    p++;
+
+    /* ...followed by a SHA-1 hash... */
+    if (!sha1_from_hex(sha1, p)) {
+        return false;
+    }
+    p += SHA1_HEX_DIGEST_LEN;
+
+    /* ...and ended by a new-line. */
+    if (*p != '\n') {
+        return false;
+    }
+
+    return true;
+}
+
+struct ovsdb_file_read_cbdata {
+    char input[4096];
+    struct ovsdb_file *file;
+    int error;
+    unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_file *file, off_t offset, unsigned long int length,
+           uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
+{
+    unsigned long int bytes_left;
+    struct json_parser *parser;
+    struct sha1_ctx ctx;
+
+    sha1_init(&ctx);
+    parser = json_parser_create(JSPF_TRAILER);
+
+    bytes_left = length;
+    while (length > 0) {
+        char input[BUFSIZ];
+        int chunk;
+
+        chunk = MIN(length, sizeof input);
+        if (fread(input, 1, chunk, file->stream) != chunk) {
+            json_parser_abort(parser);
+            return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+                                  "%s: error reading %lu bytes "
+                                  "starting at offset %lld", file->name,
+                                  length, (long long int) offset);
+        }
+        sha1_update(&ctx, input, chunk);
+        json_parser_feed(parser, input, chunk);
+        length -= chunk;
+    }
+
+    sha1_final(&ctx, sha1);
+    *jsonp = json_parser_finish(parser);
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_file_read(struct ovsdb_file *file, struct json **jsonp)
+{
+    uint8_t expected_sha1[SHA1_DIGEST_SIZE];
+    uint8_t actual_sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    off_t data_offset;
+    unsigned long data_length;
+    struct json *json;
+    char header[128];
+
+    *jsonp = json = NULL;
+
+    if (file->read_error) {
+        return ovsdb_error_clone(file->read_error);
+    } else if (file->mode == OVSDB_FILE_WRITE) {
+        return OVSDB_BUG("reading file in write mode");
+    }
+
+    if (!fgets(header, sizeof header, file->stream)) {
+        if (feof(file->stream)) {
+            error = NULL;
+        } else {
+            error = ovsdb_io_error(errno, "%s: read failed", file->name);
+        }
+        goto error;
+    }
+
+    if (!parse_header(header, &data_length, expected_sha1)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
+                                   "%lld in header line \"%.*s\"",
+                                   file->name, (long long int) file->offset,
+                                   (int) strcspn(header, "\n"), header);
+        goto error;
+    }
+
+    data_offset = file->offset + strlen(header);
+    error = parse_body(file, data_offset, data_length, actual_sha1, &json);
+    if (error) {
+        goto error;
+    }
+
+    if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld have SHA-1 hash "SHA1_FMT" "
+                                   "but should have hash "SHA1_FMT,
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   SHA1_ARGS(actual_sha1),
+                                   SHA1_ARGS(expected_sha1));
+        goto error;
+    }
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld are not valid JSON (%s)",
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   json->u.string);
+        goto error;
+    }
+
+    file->offset = data_offset + data_length;
+    *jsonp = json;
+    return 0;
+
+error:
+    file->read_error = ovsdb_error_clone(error);
+    json_destroy(json);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_file_write(struct ovsdb_file *file, struct json *json)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    char *json_string;
+    char header[128];
+    size_t length;
+
+    json_string = NULL;
+
+    if (file->write_error) {
+        return ovsdb_error_clone(file->write_error);
+    } else if (file->mode == OVSDB_FILE_READ) {
+        file->mode = OVSDB_FILE_WRITE;
+        if (fseeko(file->stream, file->offset, SEEK_SET)) {
+            error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+        if (ftruncate(fileno(file->stream), file->offset)) {
+            error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+    }
+
+    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+        error = OVSDB_BUG("bad JSON type");
+        goto error;
+    }
+
+    /* Compose content.  Add a new-line (replacing the null terminator) to make
+     * the file easier to read, even though it has no semantic value.  */
+    json_string = json_to_string(json, 0);
+    length = strlen(json_string) + 1;
+    json_string[length - 1] = '\n';
+
+    /* Compose header. */
+    sha1_bytes(json_string, length, sha1);
+    snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n",
+             magic, length, SHA1_ARGS(sha1));
+
+    /* Write. */
+    if (fwrite(header, strlen(header), 1, file->stream) != 1
+        || fwrite(json_string, length, 1, file->stream) != 1
+        || fflush(file->stream))
+    {
+        error = ovsdb_io_error(errno, "%s: write failed", file->name);
+
+        /* Remove any partially written data, ignoring errors since there is
+         * nothing further we can do. */
+        ftruncate(fileno(file->stream), file->offset);
+
+        goto error;
+    }
+
+    file->offset += strlen(header) + length;
+    free(json_string);
+    return 0;
+
+error:
+    file->write_error = ovsdb_error_clone(error);
+    free(json_string);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_file_commit(struct ovsdb_file *file)
+{
+    if (fsync(fileno(file->stream))) {
+        return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+    }
+    return 0;
+}
diff --git a/ovsdb/file.h b/ovsdb/file.h
new file mode 100644 (file)
index 0000000..5178140
--- /dev/null
@@ -0,0 +1,36 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_FILE_H
+#define OVSDB_FILE_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_file;
+
+struct ovsdb_error *ovsdb_file_open(const char *name, int flags,
+                                    struct ovsdb_file **) WARN_UNUSED_RESULT;
+void ovsdb_file_close(struct ovsdb_file *);
+
+struct ovsdb_error *ovsdb_file_read(struct ovsdb_file *, struct json **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_write(struct ovsdb_file *, struct json *)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644 (file)
index 0000000..36c9a7a
--- /dev/null
@@ -0,0 +1,362 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc-server.h"
+
+#include <errno.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_trigger {
+    struct ovsdb_trigger trigger;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node hmap_node; /* Element in session's trigger table. */
+    struct json *id;
+};
+
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+    struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+
+struct ovsdb_jsonrpc_session {
+    struct ovsdb_jsonrpc_server *server;
+    struct list node;           /* Element in server's sessions list. */
+    struct jsonrpc *rpc;
+    struct hmap triggers;
+    struct list completions;    /* Completed triggers. */
+};
+
+static void ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *,
+                                       struct stream *);
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+
+struct ovsdb_jsonrpc_server {
+    struct ovsdb *db;
+
+    struct list sessions;       /* List of "struct ovsdb_jsonrpc_session"s. */
+    unsigned int n_sessions, max_sessions;
+    unsigned int max_triggers;
+
+    struct pstream **listeners;
+    size_t n_listeners, allocated_listeners;
+};
+
+static void ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *,
+                                        struct pstream *);
+
+int
+ovsdb_jsonrpc_server_create(struct ovsdb *db, const struct svec *active,
+                            const struct svec *passive,
+                            struct ovsdb_jsonrpc_server **serverp)
+{
+    struct ovsdb_jsonrpc_server *server;
+    const char *name;
+    int retval = 0;
+    size_t i;
+
+    server = xzalloc(sizeof *server);
+    server->db = db;
+    server->max_sessions = 64;
+    server->max_triggers = 64;
+    list_init(&server->sessions);
+
+    SVEC_FOR_EACH (i, name, active) {
+        struct stream *stream;
+        int error;
+
+        error = stream_open(name, &stream);
+        if (!error) {
+            ovsdb_jsonrpc_session_open(server, stream);
+        } else {
+            ovs_error(error, "%s: connection failed", name);
+            retval = error;
+        }
+    }
+
+    SVEC_FOR_EACH (i, name, passive) {
+        struct pstream *pstream;
+        int error;
+
+        error = pstream_open(name, &pstream);
+        if (!error) {
+            ovsdb_jsonrpc_server_listen(server, pstream);
+        } else {
+            ovs_error(error, "failed to listen on %s", name);
+            retval = error;
+        }
+    }
+
+    *serverp = server;
+    return retval;
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+    size_t i;
+
+    /* Accept new connections. */
+    for (i = 0; i < svr->n_listeners && svr->n_sessions < svr->max_sessions;) {
+        struct pstream *listener = svr->listeners[i];
+        struct stream *stream;
+        int error;
+
+        error = pstream_accept(listener, &stream);
+        if (!error) {
+            ovsdb_jsonrpc_session_open(svr, stream);
+        } else if (error == EAGAIN) {
+            i++;
+        } else if (error) {
+            VLOG_WARN("%s: accept failed: %s",
+                      pstream_get_name(listener), strerror(error));
+            pstream_close(listener);
+            svr->listeners[i] = svr->listeners[--svr->n_listeners];
+        }
+    }
+
+    /* Handle each session. */
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &svr->sessions) {
+        struct jsonrpc_msg *msg;
+        int error;
+
+        jsonrpc_run(s->rpc);
+
+        while (!list_is_empty(&s->completions)) {
+            struct ovsdb_jsonrpc_trigger *t
+                = CONTAINER_OF(s->completions.next,
+                               struct ovsdb_jsonrpc_trigger, trigger.node);
+            ovsdb_jsonrpc_trigger_complete(t);
+        }
+
+        if (!jsonrpc_get_backlog(s->rpc) && !jsonrpc_recv(s->rpc, &msg)) {
+            if (msg->type == JSONRPC_REQUEST) {
+                ovsdb_jsonrpc_session_got_request(s, msg);
+            } else if (msg->type == JSONRPC_NOTIFY) {
+                ovsdb_jsonrpc_session_got_notify(s, msg);
+            } else {
+                VLOG_WARN("%s: received unexpected %s message",
+                          jsonrpc_get_name(s->rpc),
+                          jsonrpc_msg_type_to_string(msg->type));
+                jsonrpc_error(s->rpc, EPROTO);
+                jsonrpc_msg_destroy(msg);
+            }
+        }
+
+        error = jsonrpc_get_status(s->rpc);
+        if (error) {
+            ovsdb_jsonrpc_session_close(s);
+        }
+    }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    if (svr->n_sessions < svr->max_sessions) {
+        size_t i;
+
+        for (i = 0; i < svr->n_sessions; i++) {
+            pstream_wait(svr->listeners[i]);
+        }
+    }
+
+    LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &svr->sessions) {
+        jsonrpc_wait(s->rpc);
+        if (!jsonrpc_get_backlog(s->rpc)) {
+            jsonrpc_recv_wait(s->rpc);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *svr,
+                            struct pstream *pstream)
+{
+    if (svr->n_listeners >= svr->allocated_listeners) {
+        svr->listeners = x2nrealloc(svr->listeners, &svr->allocated_listeners,
+                                    sizeof *svr->listeners);
+    }
+    svr->listeners[svr->n_listeners++] = pstream;
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *id, size_t hash)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+
+    HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+                             &s->triggers) {
+        if (json_equal(t->id, id)) {
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+    struct ovsdb_jsonrpc_session *s = t->session;
+
+    if (!jsonrpc_get_status(s->rpc)) {
+        struct jsonrpc_msg *reply;
+        struct json *result;
+
+        result = ovsdb_trigger_steal_result(&t->trigger);
+        if (result) {
+            reply = jsonrpc_create_reply(result, t->id);
+        } else {
+            reply = jsonrpc_create_error(json_string_create("canceled"),
+                                         t->id);
+        }
+        jsonrpc_send(s->rpc, reply);
+    }
+
+    json_destroy(t->id);
+    ovsdb_trigger_destroy(&t->trigger);
+    hmap_remove(&s->triggers, &t->hmap_node);
+    free(t);
+}
+
+static void
+ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *svr,
+                           struct stream *stream)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    s = xzalloc(sizeof *s);
+    s->server = svr;
+    list_push_back(&svr->sessions, &s->node);
+    s->rpc = jsonrpc_open(stream);
+    hmap_init(&s->triggers);
+    list_init(&s->completions);
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_trigger *t, *next;
+
+    jsonrpc_error(s->rpc, EOF);
+    HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+                        &s->triggers) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+
+    jsonrpc_close(s->rpc);
+
+    list_remove(&s->node);
+    s->server->n_sessions--;
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+                    struct jsonrpc_msg *request)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+    size_t hash;
+
+    /* Check for duplicate ID. */
+    hash = json_hash(request->id, 0);
+    t = ovsdb_jsonrpc_trigger_find(s, request->id, hash);
+    if (t) {
+        return jsonrpc_create_error(
+            json_string_create("duplicate request ID"), request->id);
+    }
+
+    /* Insert into trigger table. */
+    t = xmalloc(sizeof *t);
+    ovsdb_trigger_init(s->server->db,
+                       &t->trigger, request->params, &s->completions,
+                       time_msec());
+    t->session = s;
+    t->id = request->id;
+    hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+    request->id = NULL;
+    request->params = NULL;
+
+    /* Complete early if possible. */
+    if (ovsdb_trigger_is_complete(&t->trigger)) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+                                  struct jsonrpc_msg *request)
+{
+    struct jsonrpc_msg *reply;
+
+    if (!strcmp(request->method, "transact")) {
+        reply = execute_transaction(s, request);
+    } else if (!strcmp(request->method, "get_schema")) {
+        reply = jsonrpc_create_reply(
+            ovsdb_schema_to_json(s->server->db->schema), request->id);
+    } else {
+        reply = jsonrpc_create_error(json_string_create("unknown method"),
+                                     request->id);
+    }
+
+    if (reply) {
+        jsonrpc_msg_destroy(request);
+        jsonrpc_send(s->rpc, reply);
+    }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+    size_t hash = json_hash(request->id, 0);
+    struct ovsdb_jsonrpc_trigger *t;
+
+    t = ovsdb_jsonrpc_trigger_find(s, request->params, hash);
+    if (t) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+                                 struct jsonrpc_msg *request)
+{
+    if (!strcmp(request->method, "cancel")) {
+        execute_cancel(s, request);
+    }
+    jsonrpc_msg_destroy(request);
+}
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644 (file)
index 0000000..49b5f8a
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+struct ovsdb_jsonrpc_server;
+struct svec;
+
+int ovsdb_jsonrpc_server_create(struct ovsdb *, const struct svec *active,
+                                const struct svec *passive,
+                                struct ovsdb_jsonrpc_server **);
+void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
+
+#endif /* ovsdb/jsonrpc-server.h */
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644 (file)
index 0000000..17a9970
--- /dev/null
@@ -0,0 +1,223 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "fault.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static const struct jsonrpc_server_cbs ovsdb_jsonrpc_cbs;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+                          struct svec *active, struct svec *passive);
+static void usage(void) NO_RETURN;
+
+static void ovsdb_transact(struct unixctl_conn *, const char *args, void *db);
+
+int
+main(int argc, char *argv[])
+{
+    struct unixctl_server *unixctl;
+    struct ovsdb_jsonrpc_server *jsonrpc;
+    struct svec active, passive;
+    struct ovsdb_error *error;
+    struct ovsdb *db;
+    char *file_name;
+    int retval;
+
+    set_program_name(argv[0]);
+    register_fault_handlers();
+    time_init();
+    vlog_init();
+    signal(SIGPIPE, SIG_IGN);
+    process_init();
+
+    parse_options(argc, argv, &file_name, &active, &passive);
+
+    error = ovsdb_open(file_name, false, &db);
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+
+    retval = ovsdb_jsonrpc_server_create(db, &active, &passive, &jsonrpc);
+    if (retval) {
+        ovs_fatal(retval, "failed to initialize JSON-RPC server for OVSDB");
+    }
+    svec_destroy(&active);
+    svec_destroy(&passive);
+
+    die_if_already_running();
+    daemonize();
+
+    retval = unixctl_server_create(NULL, &unixctl);
+    if (retval) {
+        ovs_fatal(retval, "could not listen for control connections");
+    }
+
+    unixctl_command_register("ovsdb/transact", ovsdb_transact, db);
+
+    for (;;) {
+        ovsdb_jsonrpc_server_run(jsonrpc);
+        unixctl_server_run(unixctl);
+        ovsdb_trigger_run(db, time_msec());
+
+        ovsdb_jsonrpc_server_wait(jsonrpc);
+        unixctl_server_wait(unixctl);
+        ovsdb_trigger_wait(db, time_msec());
+        poll_block();
+    }
+
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+              struct svec *active, struct svec *passive)
+{
+    enum {
+        OPT_DUMMY = UCHAR_MAX + 1,
+        OPT_CONNECT,
+        OPT_LISTEN,
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"connect",     required_argument, 0, OPT_CONNECT},
+        {"listen",      required_argument, 0, OPT_LISTEN},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+        VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    svec_init(active);
+    svec_init(passive);
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_CONNECT:
+            svec_add(active, optarg);
+            break;
+
+        case OPT_LISTEN:
+            svec_add(passive, optarg);
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        VLOG_OPTION_HANDLERS
+        DAEMON_OPTION_HANDLERS
+        LEAK_CHECKER_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 1) {
+        ovs_fatal(0, "database file is only non-option argument; "
+                "use --help for usage");
+    }
+
+    *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database server\n"
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a database file in ovsdb format.\n",
+           program_name, program_name);
+    printf("\nJSON-RPC options (may be specified any number of times):\n"
+           "  --connect=REMOTE        make active connection to REMOTE\n"
+           "  --listen=LOCAL          passively listen on LOCAL\n");
+    stream_usage("JSON-RPC", true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    leak_checker_usage();
+    exit(EXIT_SUCCESS);
+}
+\f
+static void
+ovsdb_transact(struct unixctl_conn *conn, const char *args, void *db_)
+{
+    struct ovsdb *db = db_;
+    struct json *request, *reply;
+    char *reply_string;
+
+    /* Parse JSON. */
+    request = json_from_string(args);
+    if (request->type == JSON_STRING) {
+        unixctl_command_reply(conn, 501, request->u.string);
+        json_destroy(request);
+        return;
+    }
+
+    /* Execute command. */
+    reply = ovsdb_execute(db, request, 0, NULL);
+    reply_string = json_to_string(reply, 0);
+    unixctl_command_reply(conn, 200, reply_string);
+    free(reply_string);
+    json_destroy(reply);
+}
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644 (file)
index 0000000..5169653
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database management utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  create DB SCHEMA   create DB with the given SCHEMA\n"
+           "  compact DB [DST]   compact DB in-place (or to DST)\n"
+           "  extract-schema DB  print DB's schema on stdout\n"
+           "  query DB TRNS      execute read-only transaction on DB\n"
+           "  transact DB TRNS   execute read/write transaction on DB\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+static void
+do_create(int argc UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    const char *schema_file_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct ovsdb_file *db_file;
+    struct json *json;
+
+    /* Read schema from file and convert to JSON. */
+    check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+    json = ovsdb_schema_to_json(schema);
+
+    /* Create database file. */
+    check_ovsdb_error(ovsdb_file_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+                                      &db_file));
+    check_ovsdb_error(ovsdb_file_write(db_file, json));
+    check_ovsdb_error(ovsdb_file_commit(db_file));
+    ovsdb_file_close(db_file);
+
+    json_destroy(json);
+}
+
+static void
+transact(int flags, const char *db_file_name, const char *transaction)
+{
+    struct json *request, *result;
+    struct ovsdb *db;
+
+    check_ovsdb_error(ovsdb_open(db_file_name, flags, &db));
+
+    request = parse_json(transaction);
+    result = ovsdb_execute(db, request, 0, NULL);
+    json_destroy(request);
+
+    print_and_free_json(result);
+    ovsdb_destroy(db);
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    transact(O_RDONLY, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+    transact(O_RDWR, argv[1], argv[2]);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "create", 2, 2, do_create },
+    { "query", 2, 2, do_query },
+    { "transact", 2, 2, do_transact },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644 (file)
index 0000000..e653758
--- /dev/null
@@ -0,0 +1,262 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <fcntl.h>
+
+#include "file.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "transaction.h"
+
+#define THIS_MODULE VLM_ovsdb
+#include "vlog.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name, const char *comment)
+{
+    struct ovsdb_schema *schema;
+
+    schema = xzalloc(sizeof *schema);
+    schema->name = xstrdup(name);
+    schema->comment = comment ? xstrdup(comment) : NULL;
+    shash_init(&schema->tables);
+
+    return schema;
+}
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        ovsdb_table_schema_destroy(node->data);
+    }
+    shash_destroy(&schema->tables);
+    free(schema->comment);
+    free(schema->name);
+    free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct json *json;
+
+    *schemap = NULL;
+    json = json_from_file(file_name);
+    if (json->type == JSON_STRING) {
+        error = ovsdb_error("failed to read schema",
+                           "\"%s\" could not be read as JSON (%s)",
+                           file_name, json_string(json));
+        json_destroy(json);
+        return error;
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    if (error) {
+        json_destroy(json);
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+
+    *schemap = schema;
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    const struct json *name, *comment, *tables;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+
+    *schemap = NULL;
+
+    ovsdb_parser_init(&parser, json, "database schema");
+    name = ovsdb_parser_member(&parser, "name", OP_ID);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    schema = ovsdb_schema_create(json_string(name),
+                                 comment ? json_string(comment) : NULL);
+    SHASH_FOR_EACH (node, json_object(tables)) {
+        struct ovsdb_table_schema *table;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else {
+            error = ovsdb_table_schema_from_json(node->data, node->name,
+                                                 &table);
+        }
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            return error;
+        }
+
+        shash_add(&schema->tables, table->name, table);
+    }
+    *schemap = schema;
+    return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+    struct json *json, *tables;
+    struct shash_node *node;
+
+    json = json_object_create();
+    json_object_put_string(json, "name", schema->name);
+    if (schema->comment) {
+        json_object_put_string(json, "comment", schema->comment);
+    }
+
+    tables = json_object_create();
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        json_object_put(tables, table->name,
+                        ovsdb_table_schema_to_json(table));
+    }
+    json_object_put(json, "tables", tables);
+
+    return json;
+}
+\f
+struct ovsdb *
+ovsdb_create(struct ovsdb_file *file, struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+    struct ovsdb *db;
+
+    db = xmalloc(sizeof *db);
+    db->schema = schema;
+    db->file = file;
+    list_init(&db->triggers);
+    db->run_triggers = false;
+
+    shash_init(&db->tables);
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+        shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+    }
+
+    return db;
+}
+
+struct ovsdb_error *
+ovsdb_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct ovsdb_file *file;
+    struct json *json;
+    struct ovsdb *db;
+
+    error = ovsdb_file_open(file_name, read_only ? O_RDONLY : O_RDWR, &file);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_file_read(file, &json);
+    if (error) {
+        return error;
+    } else if (!json) {
+        return ovsdb_io_error(EOF, "%s: database file contains no schema",
+                              file_name);
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    if (error) {
+        json_destroy(json);
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+    json_destroy(json);
+
+    db = ovsdb_create(read_only ? file : NULL, schema);
+    while ((error = ovsdb_file_read(file, &json)) == NULL && json) {
+        struct ovsdb_txn *txn;
+
+        error = ovsdb_txn_from_json(db, json, &txn);
+        json_destroy(json);
+        if (error) {
+            break;
+        }
+
+        ovsdb_txn_commit(txn);
+    }
+    if (error) {
+        char *msg = ovsdb_error_to_string(error);
+        VLOG_WARN("%s", msg);
+        free(msg);
+
+        ovsdb_error_destroy(error);
+    }
+
+    if (read_only) {
+        ovsdb_file_close(file);
+    }
+
+    *dbp = db;
+    return NULL;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+    if (db) {
+        struct shash_node *node;
+
+        /* Delete all the tables.  This also deletes their schemas. */
+        SHASH_FOR_EACH (node, &db->tables) {
+            struct ovsdb_table *table = node->data;
+            ovsdb_table_destroy(table);
+        }
+        shash_destroy(&db->tables);
+
+        /* Clear the schema's hash of table schemas.  The schemas, but not the
+         * table that points to them, were deleted in the previous step. */
+        shash_destroy(&db->schema->tables);
+
+        ovsdb_schema_destroy(db->schema);
+        ovsdb_file_close(db->file);
+        free(db);
+    }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+    return shash_find_data(&db->tables, name);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644 (file)
index 0000000..3f62966
--- /dev/null
@@ -0,0 +1,73 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_OVSDB_H
+#define OVSDB_OVSDB_H 1
+
+#include "compiler.h"
+#include "hmap.h"
+#include "list.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Database schema. */
+struct ovsdb_schema {
+    char *name;
+    char *comment;
+    struct shash tables;        /* Contains "struct ovsdb_table_schema *"s. */
+};
+
+struct ovsdb_schema *ovsdb_schema_create(const char *name,
+                                         const char *comment);
+void ovsdb_schema_destroy(struct ovsdb_schema *);
+
+struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_schema_from_json(struct json *,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
+\f
+/* Database. */
+struct ovsdb {
+    struct ovsdb_schema *schema;
+    struct ovsdb_file *file;    /* Disk file (null for in-memory db). */
+    struct shash tables;        /* Contains "struct ovsdb_table *"s. */
+
+    /* Triggers. */
+    struct list triggers;       /* Contains "struct ovsdb_trigger"s. */
+    bool run_triggers;
+};
+
+struct ovsdb *ovsdb_create(struct ovsdb_file *, struct ovsdb_schema *);
+struct ovsdb_error *ovsdb_open(const char *file_name, bool read_only,
+                               struct ovsdb **)
+    WARN_UNUSED_RESULT;
+void ovsdb_destroy(struct ovsdb *);
+
+struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_to_json(const struct ovsdb *);
+
+struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
+
+struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+                           long long int elapsed_msec,
+                           long long int *timeout_msec);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644 (file)
index 0000000..878ac5b
--- /dev/null
@@ -0,0 +1,99 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "query.h"
+
+#include "column.h"
+#include "condition.h"
+#include "row.h"
+#include "table.h"
+
+void
+ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd,
+            bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux)
+{
+    if (cnd->n_clauses > 0
+        && cnd->clauses[0].column->index == OVSDB_COL_UUID
+        && cnd->clauses[0].function == OVSDB_F_EQ) {
+        /* Optimize the case where the query has a clause of the form "uuid ==
+         * <some-uuid>", since we have an index on UUID. */
+        const struct ovsdb_row *row;
+
+        row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid);
+        if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) {
+            output_row(row, aux);
+        }
+    } else {
+        /* Linear scan. */
+        const struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) {
+                break;
+            }
+        }
+    }
+}
+
+static bool
+query_row_set_cb(const struct ovsdb_row *row, void *results_)
+{
+    struct ovsdb_row_set *results = results_;
+    ovsdb_row_set_add_row(results, row);
+    return true;
+}
+
+void
+ovsdb_query_row_set(struct ovsdb_table *table,
+                    const struct ovsdb_condition *condition,
+                    struct ovsdb_row_set *results)
+{
+    ovsdb_query(table, condition, query_row_set_cb, results);
+}
+
+static bool
+query_distinct_cb(const struct ovsdb_row *row, void *hash_)
+{
+    struct ovsdb_row_hash *hash = hash_;
+    ovsdb_row_hash_insert(hash, row);
+    return true;
+}
+
+void
+ovsdb_query_distinct(struct ovsdb_table *table,
+                     const struct ovsdb_condition *condition,
+                     const struct ovsdb_column_set *columns,
+                     struct ovsdb_row_set *results)
+{
+    if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) {
+        /* All the result rows are guaranteed to be distinct anyway. */
+        return ovsdb_query_row_set(table, condition, results);
+    } else {
+        /* Use hash table to drop duplicates. */
+        struct ovsdb_row_hash_node *node;
+        struct ovsdb_row_hash hash;
+
+        ovsdb_row_hash_init(&hash, columns);
+        ovsdb_query(table, condition, query_distinct_cb, &hash);
+        HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node,
+                       &hash.rows) {
+            ovsdb_row_set_add_row(results, node->row);
+        }
+        ovsdb_row_hash_destroy(&hash, false);
+    }
+}
diff --git a/ovsdb/query.h b/ovsdb/query.h
new file mode 100644 (file)
index 0000000..f5cfe2e
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_QUERY_H
+#define OVSDB_QUERY_H 1
+
+#include <stdbool.h>
+
+struct ovsdb_column_set;
+struct ovsdb_condition;
+struct ovsdb_row;
+struct ovsdb_row_set;
+struct ovsdb_table;
+struct ovsdb_txn;
+
+void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *,
+                 bool (*output_row)(const struct ovsdb_row *, void *aux),
+                 void *aux);
+void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *,
+                         struct ovsdb_row_set *);
+void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *,
+                          const struct ovsdb_column_set *,
+                          struct ovsdb_row_set *);
+
+#endif /* ovsdb/query.h */
diff --git a/ovsdb/row.c b/ovsdb/row.c
new file mode 100644 (file)
index 0000000..1b81942
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "row.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "shash.h"
+#include "sort.h"
+#include "table.h"
+
+static struct ovsdb_row *
+allocate_row(const struct ovsdb_table *table)
+{
+    size_t n_fields = shash_count(&table->schema->columns);
+    size_t row_size = (offsetof(struct ovsdb_row, fields)
+                       + sizeof(struct ovsdb_datum) * n_fields);
+    struct ovsdb_row *row = xmalloc(row_size);
+    row->table = (struct ovsdb_table *) table;
+    row->txn_row = NULL;
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_create(const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+    struct ovsdb_row *row;
+
+    row = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_init_default(&row->fields[column->index], &column->type);
+    }
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_clone(const struct ovsdb_row *old)
+{
+    const struct ovsdb_table *table = old->table;
+    const struct shash_node *node;
+    struct ovsdb_row *new;
+
+    new = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_clone(&new->fields[column->index],
+                          &old->fields[column->index],
+                          &column->type);
+    }
+    return new;
+}
+
+/* The caller is responsible for ensuring that 'row' has been removed from its
+ * table and that it is not participating in a transaction. */
+void
+ovsdb_row_destroy(struct ovsdb_row *row)
+{
+    if (row) {
+        const struct ovsdb_table *table = row->table;
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_datum_destroy(&row->fields[column->index], &column->type);
+        }
+        free(row);
+    }
+}
+
+uint32_t
+ovsdb_row_hash_columns(const struct ovsdb_row *row,
+                       const struct ovsdb_column_set *columns,
+                       uint32_t basis)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        basis = ovsdb_datum_hash(&row->fields[column->index], &column->type,
+                                 basis);
+    }
+
+    return basis;
+}
+
+int
+ovsdb_row_compare_columns_3way(const struct ovsdb_row *a,
+                               const struct ovsdb_row *b,
+                               const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        int cmp = ovsdb_datum_compare_3way(&a->fields[column->index],
+                                           &b->fields[column->index],
+                                           &column->type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_row_equal_columns(const struct ovsdb_row *a,
+                        const struct ovsdb_row *b,
+                        const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        if (!ovsdb_datum_equals(&a->fields[column->index],
+                                &b->fields[column->index],
+                                &column->type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void
+ovsdb_row_update_columns(struct ovsdb_row *dst,
+                         const struct ovsdb_row *src,
+                         const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        ovsdb_datum_destroy(&dst->fields[column->index], &column->type);
+        ovsdb_datum_clone(&dst->fields[column->index],
+                          &src->fields[column->index],
+                          &column->type);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json,
+                    const struct ovsdb_symbol_table *symtab,
+                    struct ovsdb_column_set *included)
+{
+    struct ovsdb_table_schema *schema = row->table->schema;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+    }
+
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
+
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data,
+                                      symtab);
+        if (error) {
+            return error;
+        }
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
+        if (included) {
+            ovsdb_column_set_add(included, column);
+        }
+    }
+
+    return NULL;
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+                const struct ovsdb_column *column)
+{
+    json_object_put(object, column->name,
+                    ovsdb_datum_to_json(&row->fields[column->index],
+                                        &column->type));
+}
+
+struct json *
+ovsdb_row_to_json(const struct ovsdb_row *row,
+                  const struct ovsdb_column_set *columns)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_object_create();
+    for (i = 0; i < columns->n_columns; i++) {
+        put_json_column(json, row, columns->columns[i]);
+    }
+    return json;
+}
+\f
+void
+ovsdb_row_set_init(struct ovsdb_row_set *set)
+{
+    set->rows = NULL;
+    set->n_rows = set->allocated_rows = 0;
+}
+
+void
+ovsdb_row_set_destroy(struct ovsdb_row_set *set)
+{
+    free(set->rows);
+}
+
+void
+ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row)
+{
+    if (set->n_rows >= set->allocated_rows) {
+        set->rows = x2nrealloc(set->rows, &set->allocated_rows,
+                               sizeof *set->rows);
+    }
+    set->rows[set->n_rows++] = row;
+}
+
+struct json *
+ovsdb_row_set_to_json(const struct ovsdb_row_set *rows,
+                      const struct ovsdb_column_set *columns)
+{
+    struct json **json_rows;
+    size_t i;
+
+    json_rows = xmalloc(rows->n_rows * sizeof *json_rows);
+    for (i = 0; i < rows->n_rows; i++) {
+        json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns);
+    }
+    return json_array_create(json_rows, rows->n_rows);
+}
+
+struct ovsdb_row_set_sort_cbdata {
+    struct ovsdb_row_set *set;
+    const struct ovsdb_column_set *columns;
+};
+
+static int
+ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    return ovsdb_row_compare_columns_3way(cbdata->set->rows[a],
+                                          cbdata->set->rows[b],
+                                          cbdata->columns);
+}
+
+static void
+ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    const struct ovsdb_row *tmp = cbdata->set->rows[a];
+    cbdata->set->rows[a] = cbdata->set->rows[b];
+    cbdata->set->rows[b] = tmp;
+}
+
+void
+ovsdb_row_set_sort(struct ovsdb_row_set *set,
+                   const struct ovsdb_column_set *columns)
+{
+    if (columns && columns->n_columns && set->n_rows > 1) {
+        struct ovsdb_row_set_sort_cbdata cbdata;
+        cbdata.set = set;
+        cbdata.columns = columns;
+        sort(set->n_rows,
+             ovsdb_row_set_sort_compare_cb,
+             ovsdb_row_set_sort_swap_cb,
+             &cbdata);
+    }
+}
+\f
+void
+ovsdb_row_hash_init(struct ovsdb_row_hash *rh,
+                    const struct ovsdb_column_set *columns)
+{
+    hmap_init(&rh->rows);
+    ovsdb_column_set_clone(&rh->columns, columns);
+}
+
+void
+ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows)
+{
+    struct ovsdb_row_hash_node *node, *next;
+
+    HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node,
+                        &rh->rows) {
+        hmap_remove(&rh->rows, &node->hmap_node);
+        if (destroy_rows) {
+            ovsdb_row_destroy((struct ovsdb_row *) node->row);
+        }
+        free(node);
+    }
+    hmap_destroy(&rh->rows);
+    ovsdb_column_set_destroy(&rh->columns);
+}
+
+size_t
+ovsdb_row_hash_count(const struct ovsdb_row_hash *rh)
+{
+    return hmap_count(&rh->rows);
+}
+
+bool
+ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh,
+                        const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_contains__(rh, row, hash);
+}
+
+/* Returns true if every row in 'b' has an equal row in 'a'. */
+bool
+ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a,
+                            const struct ovsdb_row_hash *b)
+{
+    struct ovsdb_row_hash_node *node;
+
+    assert(ovsdb_column_set_equals(&a->columns, &b->columns));
+    HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) {
+        if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_insert__(rh, row, hash);
+}
+
+bool
+ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh,
+                          const struct ovsdb_row *row, size_t hash)
+{
+    struct ovsdb_row_hash_node *node;
+    HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node,
+                             hash, &rh->rows) {
+        if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row,
+                        size_t hash)
+{
+    if (!ovsdb_row_hash_contains__(rh, row, hash)) {
+        struct ovsdb_row_hash_node *node = xmalloc(sizeof *node);
+        node->row = row;
+        hmap_insert(&rh->rows, &node->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/row.h b/ovsdb/row.h
new file mode 100644 (file)
index 0000000..55c4f14
--- /dev/null
@@ -0,0 +1,139 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ROW_H
+#define OVSDB_ROW_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "column.h"
+#include "hmap.h"
+#include "ovsdb-data.h"
+
+struct ovsdb_column_set;
+
+/* A row in a database table. */
+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. */
+    struct ovsdb_datum fields[];
+};
+
+struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *);
+struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *);
+void ovsdb_row_destroy(struct ovsdb_row *);
+
+uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *,
+                                const struct ovsdb_column_set *,
+                                uint32_t basis);
+bool ovsdb_row_equal_columns(const struct ovsdb_row *,
+                             const struct ovsdb_row *,
+                             const struct ovsdb_column_set *);
+int ovsdb_row_compare_columns_3way(const struct ovsdb_row *,
+                                   const struct ovsdb_row *,
+                                   const struct ovsdb_column_set *);
+void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *,
+                              const struct ovsdb_column_set *);
+
+struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *,
+                                        const struct json *,
+                                        const struct ovsdb_symbol_table *,
+                                        struct ovsdb_column_set *included)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_row_to_json(const struct ovsdb_row *,
+                               const struct ovsdb_column_set *include);
+
+static inline const struct uuid *
+ovsdb_row_get_uuid(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_uuid_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline const struct uuid *
+ovsdb_row_get_version(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_version_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline uint32_t
+ovsdb_row_hash(const struct ovsdb_row *row)
+{
+    return uuid_hash(ovsdb_row_get_uuid(row));
+}
+\f
+/* An unordered collection of rows. */
+struct ovsdb_row_set {
+    const struct ovsdb_row **rows;
+    size_t n_rows, allocated_rows;
+};
+
+#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_row_set_init(struct ovsdb_row_set *);
+void ovsdb_row_set_destroy(struct ovsdb_row_set *);
+void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *);
+
+struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *,
+                                   const struct ovsdb_column_set *);
+
+void ovsdb_row_set_sort(struct ovsdb_row_set *,
+                        const struct ovsdb_column_set *);
+\f
+/* A hash table of rows.  A specified set of columns is used for hashing and
+ * comparing rows.
+ *
+ * The row hash doesn't necessarily own its rows.  They may be owned by, for
+ * example, an ovsdb_table. */
+struct ovsdb_row_hash {
+    struct hmap rows;
+    struct ovsdb_column_set columns;
+};
+
+#define OVSDB_ROW_HASH_INITIALIZER(RH) \
+    { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER }
+
+struct ovsdb_row_hash_node {
+    struct hmap_node hmap_node;
+    const struct ovsdb_row *row;
+};
+
+void ovsdb_row_hash_init(struct ovsdb_row_hash *,
+                         const struct ovsdb_column_set *);
+void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows);
+size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *,
+                             const struct ovsdb_row *);
+bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *,
+                                 const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *);
+bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *,
+                               const struct ovsdb_row *, size_t hash);
+bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *,
+                             const struct ovsdb_row *, size_t hash);
+
+#endif /* ovsdb/row.h */
diff --git a/ovsdb/table.c b/ovsdb/table.c
new file mode 100644 (file)
index 0000000..d017a6b
--- /dev/null
@@ -0,0 +1,228 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "table.h"
+
+#include <assert.h>
+
+#include "json.h"
+#include "column.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "row.h"
+
+static void
+add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
+{
+    assert(!shash_find(&ts->columns, column->name));
+    column->index = shash_count(&ts->columns);
+    shash_add(&ts->columns, column->name, column);
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_create(const char *name, const char *comment, bool mutable)
+{
+    struct ovsdb_column *uuid, *version;
+    struct ovsdb_table_schema *ts;
+
+    ts = xzalloc(sizeof *ts);
+    ts->name = xstrdup(name);
+    ts->comment = comment ? xstrdup(comment) : NULL;
+    ts->mutable = mutable;
+    shash_init(&ts->columns);
+
+    uuid = ovsdb_column_create(
+        "_uuid", "Unique identifier for this row.",
+        false, true, &ovsdb_type_uuid);
+    add_column(ts, uuid);
+    assert(uuid->index == OVSDB_COL_UUID);
+
+    version = ovsdb_column_create(
+        "_version", "Unique identifier for this version of this row.",
+        false, false, &ovsdb_type_uuid);
+    add_column(ts, version);
+    assert(version->index == OVSDB_COL_VERSION);
+
+    return ts;
+}
+
+void
+ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        ovsdb_column_destroy(node->data);
+    }
+    shash_destroy(&ts->columns);
+    free(ts->comment);
+    free(ts->name);
+    free(ts);
+}
+
+struct ovsdb_error *
+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 *comment, *columns, *mutable;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+
+    *tsp = NULL;
+
+    ovsdb_parser_init(&parser, json, "table schema for table %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                  OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    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,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true);
+    SHASH_FOR_EACH (node, json_object(columns)) {
+        struct ovsdb_column *column;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else {
+            error = ovsdb_column_from_json(node->data, node->name, &column);
+        }
+        if (error) {
+            ovsdb_table_schema_destroy(ts);
+            return error;
+        }
+
+        add_column(ts, column);
+    }
+    *tsp = ts;
+    return 0;
+}
+
+struct json *
+ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
+{
+    struct json *json, *columns;
+    struct shash_node *node;
+
+    json = json_object_create();
+    if (ts->comment) {
+        json_object_put_string(json, "comment", ts->comment);
+    }
+    if (!ts->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+
+    columns = json_object_create();
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        struct ovsdb_column *column = node->data;
+        if (node->name[0] != '_') {
+            json_object_put(columns, column->name,
+                            ovsdb_column_to_json(column));
+        }
+    }
+    json_object_put(json, "columns", columns);
+
+    return json;
+}
+
+const struct ovsdb_column *
+ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts,
+                              const char *name)
+{
+    return shash_find_data(&ts->columns, name);
+}
+\f
+struct ovsdb_table *
+ovsdb_table_create(struct ovsdb_table_schema *ts)
+{
+    struct ovsdb_table *table;
+
+    table = xmalloc(sizeof *table);
+    table->schema = ts;
+    hmap_init(&table->rows);
+
+    return table;
+}
+
+void
+ovsdb_table_destroy(struct ovsdb_table *table)
+{
+    if (table) {
+        struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            ovsdb_row_destroy(row);
+        }
+        hmap_destroy(&table->rows);
+
+        ovsdb_table_schema_destroy(table->schema);
+        free(table);
+    }
+}
+
+static const struct ovsdb_row *
+ovsdb_table_get_row__(const struct ovsdb_table *table, const struct uuid *uuid,
+                      size_t hash)
+{
+    struct ovsdb_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, hash,
+                             &table->rows) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) {
+            return row;
+        }
+    }
+
+    return NULL;
+}
+
+const struct ovsdb_row *
+ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    return ovsdb_table_get_row__(table, uuid, uuid_hash(uuid));
+}
+
+/* This is probably not the function you want.  Use ovsdb_txn_row_modify()
+ * instead. */
+bool
+ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row)
+{
+    const struct uuid *uuid = ovsdb_row_get_uuid(row);
+    size_t hash = uuid_hash(uuid);
+
+    if (!ovsdb_table_get_row__(table, uuid, hash)) {
+        hmap_insert(&table->rows, &row->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/table.h b/ovsdb/table.h
new file mode 100644 (file)
index 0000000..9e36c91
--- /dev/null
@@ -0,0 +1,63 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TABLE_H
+#define OVSDB_TABLE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "hmap.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Schema for a database table. */
+struct ovsdb_table_schema {
+    char *name;
+    char *comment;
+    bool mutable;
+    struct shash columns;       /* Contains "struct ovsdb_column *"s. */
+};
+
+struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
+                                                     const char *comment,
+                                                     bool mutable);
+void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
+
+struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *,
+                                                 const char *name,
+                                                 struct ovsdb_table_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *);
+
+const struct ovsdb_column *ovsdb_table_schema_get_column(
+    const struct ovsdb_table_schema *, const char *name);
+\f
+/* Database table. */
+
+struct ovsdb_table {
+    struct ovsdb_table_schema *schema;
+    struct hmap rows;           /* Contains "struct ovsdb_row"s. */
+};
+
+struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *);
+void ovsdb_table_destroy(struct ovsdb_table *);
+
+const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *,
+                                            const struct uuid *);
+bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *);
+
+#endif /* ovsdb/table.h */
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
new file mode 100644 (file)
index 0000000..21a46ec
--- /dev/null
@@ -0,0 +1,444 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "transaction.h"
+
+#include <assert.h>
+
+#include "hash.h"
+#include "hmap.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb.h"
+#include "row.h"
+#include "table.h"
+#include "uuid.h"
+
+struct ovsdb_txn {
+    struct ovsdb *db;
+    struct hmap txn_tables;     /* Contains "struct ovsdb_txn_table"s. */
+};
+
+/* A table modified by a transaction. */
+struct ovsdb_txn_table {
+    struct hmap_node hmap_node; /* Element in ovsdb_txn's txn_tables hmap. */
+    struct ovsdb_table *table;
+    struct hmap txn_rows;       /* Contains "struct ovsdb_txn_row"s. */
+};
+
+/* A row modified by the transaction:
+ *
+ *      - A row added by a transaction will have null 'old' and non-null 'new'.
+ *
+ *      - A row deleted by a transaction will have non-null 'old' and null
+ *        'new'.
+ *
+ *      - A row modified by a transaction will have non-null 'old' and 'new'.
+ *
+ *      - 'old' and 'new' both null is invalid.  It would indicate that a row
+ *        was added then deleted within a single transaction, but we instead
+ *        handle that case by deleting the txn_row entirely.
+ */
+struct ovsdb_txn_row {
+    struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */
+    struct ovsdb_row *old;      /* The old row. */
+    struct ovsdb_row *new;      /* The new row. */
+};
+
+static const struct uuid *
+ovsdb_txn_row_get_uuid(const struct ovsdb_txn_row *txn_row)
+{
+    const struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new;
+    return ovsdb_row_get_uuid(row);
+}
+
+struct ovsdb_txn *
+ovsdb_txn_create(struct ovsdb *db)
+{
+    struct ovsdb_txn *txn = xmalloc(sizeof *txn);
+    txn->db = db;
+    hmap_init(&txn->txn_tables);
+    return txn;
+}
+
+static void
+ovsdb_txn_destroy(struct ovsdb_txn *txn, void (*cb)(struct ovsdb_txn_row *))
+{
+    struct ovsdb_txn_table *txn_table, *next_txn_table;
+
+    HMAP_FOR_EACH_SAFE (txn_table, next_txn_table,
+                        struct ovsdb_txn_table, hmap_node, &txn->txn_tables)
+    {
+        struct ovsdb_txn_row *txn_row, *next_txn_row;
+
+        HMAP_FOR_EACH_SAFE (txn_row, next_txn_row,
+                            struct ovsdb_txn_row, hmap_node,
+                            &txn_table->txn_rows)
+        {
+            if (txn_row->new) {
+                txn_row->new->txn_row = NULL;
+            }
+            cb(txn_row);
+            free(txn_row);
+        }
+
+        hmap_destroy(&txn_table->txn_rows);
+        free(txn_table);
+    }
+    hmap_destroy(&txn->txn_tables);
+    free(txn);
+}
+
+static void
+ovsdb_txn_row_abort(struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_row *old = txn_row->old;
+    struct ovsdb_row *new = txn_row->new;
+
+    if (!old) {
+        hmap_remove(&new->table->rows, &new->hmap_node);
+    } else if (!new) {
+        hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old));
+    } else {
+        hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node);
+    }
+    ovsdb_row_destroy(new);
+}
+
+void
+ovsdb_txn_abort(struct ovsdb_txn *txn)
+{
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_abort);
+}
+
+static void
+ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
+{
+    ovsdb_row_destroy(txn_row->old);
+}
+
+void
+ovsdb_txn_commit(struct ovsdb_txn *txn)
+{
+    txn->db->run_triggers = true;
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_commit);
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+                const struct ovsdb_column *column)
+{
+    json_object_put(object, column->name,
+                    ovsdb_datum_to_json(&row->fields[column->index],
+                                        &column->type));
+}
+
+static struct json *
+ovsdb_txn_row_to_json(const struct ovsdb_txn_row *txn_row)
+{
+    const struct ovsdb_row *old = txn_row->old;
+    const struct ovsdb_row *new = txn_row->new;
+    struct shash_node *node;
+    struct json *json;
+
+    if (!new) {
+        return json_null_create();
+    }
+
+    json = NULL;
+    SHASH_FOR_EACH (node, &new->table->schema->columns) {
+        struct ovsdb_column *column = node->data;
+        unsigned int index = column->index;
+
+        if (index != OVSDB_COL_UUID && column->persistent
+            && (!old || !ovsdb_datum_equals(&old->fields[index],
+                                            &new->fields[index],
+                                            &column->type)))
+        {
+            if (!json) {
+                json = json_object_create();
+            }
+            put_json_column(json, new, column);
+        }
+    }
+    return json;
+}
+
+static struct json *
+ovsdb_txn_table_to_json(const struct ovsdb_txn_table *txn_table)
+{
+    struct ovsdb_txn_row *txn_row;
+    struct json *txn_table_json;
+
+    txn_table_json = NULL;
+    HMAP_FOR_EACH (txn_row, struct ovsdb_txn_row, hmap_node,
+                   &txn_table->txn_rows) {
+        struct json *txn_row_json = ovsdb_txn_row_to_json(txn_row);
+        if (txn_row_json) {
+            char uuid[UUID_LEN + 1];
+
+            if (!txn_table_json) {
+                txn_table_json = json_object_create();
+            }
+
+            snprintf(uuid, sizeof uuid,
+                     UUID_FMT, UUID_ARGS(ovsdb_txn_row_get_uuid(txn_row)));
+            json_object_put(txn_table_json, uuid, txn_row_json);
+        }
+    }
+    return txn_table_json;
+}
+
+struct json *
+ovsdb_txn_to_json(const struct ovsdb_txn *txn)
+{
+    struct ovsdb_txn_table *txn_table;
+    struct json *txn_json;
+
+    txn_json = NULL;
+    HMAP_FOR_EACH (txn_table, struct ovsdb_txn_table, hmap_node,
+                   &txn->txn_tables) {
+        struct json *txn_table_json = ovsdb_txn_table_to_json(txn_table);
+        if (!txn_json) {
+            txn_json = json_object_create();
+        }
+        json_object_put(txn_json, txn_table->table->schema->name,
+                        txn_table_json);
+    }
+    return txn_json;
+}
+
+static struct ovsdb_error *
+ovsdb_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                        const struct uuid *row_uuid, struct json *json)
+{
+    const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
+    if (json->type == JSON_NULL) {
+        if (!row) {
+            return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
+                                      "row "UUID_FMT" that does not exist",
+                                      UUID_ARGS(row_uuid));
+        }
+        ovsdb_txn_row_delete(txn, row);
+        return NULL;
+    } else if (row) {
+        return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row),
+                                   json, NULL, NULL);
+    } else {
+        struct ovsdb_error *error;
+        struct ovsdb_row *new;
+
+        new = ovsdb_row_create(table);
+        *ovsdb_row_get_uuid_rw(new) = *row_uuid;
+        error = ovsdb_row_from_json(new, json, NULL, NULL);
+        if (error) {
+            ovsdb_row_destroy(new);
+        }
+
+        ovsdb_txn_row_insert(txn, new);
+
+        return error;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_txn_table_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                          struct json *json)
+{
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *uuid_string = node->name;
+        struct json *txn_row_json = node->data;
+        struct ovsdb_error *error;
+        struct uuid row_uuid;
+
+        if (!uuid_from_string(&row_uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+
+        error = ovsdb_txn_row_from_json(txn, table, &row_uuid, txn_row_json);
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_txn_from_json(struct ovsdb *db, const struct json *json,
+                    struct ovsdb_txn **txnp)
+{
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_txn *txn;
+
+    *txnp = NULL;
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    txn = ovsdb_txn_create(db);
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *table_name = node->name;
+        struct json *txn_table_json = node->data;
+        struct ovsdb_table *table;
+
+        table = shash_find_data(&db->tables, table_name);
+        if (!table) {
+            error = ovsdb_syntax_error(json, "unknown table",
+                                       "No table named %s.", table_name);
+            goto error;
+        }
+
+        error = ovsdb_txn_table_from_json(txn, table, txn_table_json);
+        if (error) {
+            goto error;
+        }
+    }
+    *txnp = txn;
+    return NULL;
+
+error:
+    ovsdb_txn_abort(txn);
+    return error;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table__(struct ovsdb_txn *txn,
+                          const struct ovsdb_table *table,
+                          uint32_t hash)
+{
+    struct ovsdb_txn_table *txn_table;
+
+    HMAP_FOR_EACH_IN_BUCKET (txn_table, struct ovsdb_txn_table, hmap_node,
+                             hash, &txn->txn_tables) {
+        if (txn_table->table == table) {
+            return txn_table;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table(struct ovsdb_txn *txn, const struct ovsdb_table *table)
+{
+    return ovsdb_txn_get_txn_table__(txn, table, hash_pointer(table, 0));
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_create_txn_table(struct ovsdb_txn *txn,
+                           struct ovsdb_table *table)
+{
+    uint32_t hash = hash_pointer(table, 0);
+    struct ovsdb_txn_table *txn_table;
+
+    txn_table = ovsdb_txn_get_txn_table__(txn, table, hash);
+    if (!txn_table) {
+        txn_table = xmalloc(sizeof *txn_table);
+        txn_table->table = table;
+        hmap_init(&txn_table->txn_rows);
+        hmap_insert(&txn->txn_tables, &txn_table->hmap_node, hash);
+    }
+    return txn_table;
+}
+
+static struct ovsdb_txn_row *
+ovsdb_txn_row_create(struct ovsdb_txn_table *txn_table,
+                     const struct ovsdb_row *old, struct ovsdb_row *new)
+{
+    uint32_t hash = ovsdb_row_hash(old ? old : new);
+    struct ovsdb_txn_row *txn_row;
+
+    txn_row = xmalloc(sizeof *txn_row);
+    txn_row->old = (struct ovsdb_row *) old;
+    txn_row->new = new;
+    hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, hash);
+
+    return txn_row;
+}
+
+struct ovsdb_row *
+ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
+{
+    struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_;
+
+    if (ro_row->txn_row) {
+        assert(ro_row == ro_row->txn_row->new);
+        return ro_row;
+    } else {
+        struct ovsdb_table *table = ro_row->table;
+        struct ovsdb_txn_table *txn_table;
+        struct ovsdb_row *rw_row;
+
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        rw_row = ovsdb_row_clone(ro_row);
+        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);
+
+        return rw_row;
+    }
+}
+
+void
+ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row)
+{
+    uint32_t hash = ovsdb_row_hash(row);
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_table *txn_table;
+
+    uuid_generate(ovsdb_row_get_version_rw(row));
+
+    txn_table = ovsdb_txn_create_txn_table(txn, table);
+    row->txn_row = ovsdb_txn_row_create(txn_table, NULL, row);
+    hmap_insert(&table->rows, &row->hmap_node, hash);
+}
+
+/* 'row' must be assumed destroyed upon return; the caller must not reference
+ * it again. */
+void
+ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_)
+{
+    struct ovsdb_row *row = (struct ovsdb_row *) row_;
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_row *txn_row = row->txn_row;
+    struct ovsdb_txn_table *txn_table;
+
+    hmap_remove(&table->rows, &row->hmap_node);
+
+    if (!txn_row) {
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        row->txn_row = ovsdb_txn_row_create(txn_table, row, NULL);
+    } else {
+        assert(txn_row->new == row);
+        if (txn_row->old) {
+            txn_row->new = NULL;
+        } else {
+            txn_table = ovsdb_txn_get_txn_table(txn, table);
+            hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+        }
+        ovsdb_row_destroy(row);
+    }
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644 (file)
index 0000000..293eaf4
--- /dev/null
@@ -0,0 +1,41 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRANSACTION_H
+#define OVSDB_TRANSACTION_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+void ovsdb_txn_commit(struct ovsdb_txn *);
+
+struct json *ovsdb_txn_to_json(const struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_from_json(struct ovsdb *, const struct json *,
+                                        struct ovsdb_txn **)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *,
+                                       const struct ovsdb_row *);
+
+void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *);
+void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644 (file)
index 0000000..1ecfdca
--- /dev/null
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "trigger.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "poll-loop.h"
+
+static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *,
+                              long long int now);
+static void ovsdb_trigger_complete(struct ovsdb_trigger *);
+
+void
+ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger,
+                   struct json *request, struct list *completion,
+                   long long int now)
+{
+    list_push_back(&db->triggers, &trigger->node);
+    trigger->completion = completion;
+    trigger->request = request;
+    trigger->result = NULL;
+    trigger->created = now;
+    trigger->timeout_msec = LLONG_MAX;
+    ovsdb_trigger_try(db, trigger, now);
+}
+
+void
+ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
+{
+    list_remove(&trigger->node);
+    json_destroy(trigger->request);
+    json_destroy(trigger->result);
+}
+
+bool
+ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)
+{
+    return trigger->result != NULL;
+}
+
+struct json *
+ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)
+{
+    struct json *result = trigger->result;
+    trigger->result = NULL;
+    return result;
+}
+
+void
+ovsdb_trigger_run(struct ovsdb *db, long long int now)
+{
+    struct ovsdb_trigger *t, *next;
+    bool run_triggers;
+
+    run_triggers = db->run_triggers;
+    db->run_triggers = false;
+    LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) {
+        if (run_triggers || now - t->created >= t->timeout_msec) {
+            ovsdb_trigger_try(db, t, now);
+        }
+    }
+}
+
+void
+ovsdb_trigger_wait(struct ovsdb *db, long long int now)
+{
+    if (db->run_triggers) {
+        poll_immediate_wake();
+    } else {
+        long long int deadline = LLONG_MAX;
+        struct ovsdb_trigger *t;
+
+        LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) {
+            if (t->created < LLONG_MAX - t->timeout_msec) {
+                long long int t_deadline = t->created + t->timeout_msec;
+                if (deadline > t_deadline) {
+                    deadline = t_deadline;
+                    if (now >= deadline) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (deadline < LLONG_MAX) {
+            poll_timer_wait(MIN(deadline - now, INT_MAX));
+        }
+    }
+}
+
+static bool
+ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now)
+{
+    t->result = ovsdb_execute(db, t->request, now - t->created,
+                              &t->timeout_msec);
+    if (t->result) {
+        ovsdb_trigger_complete(t);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void
+ovsdb_trigger_complete(struct ovsdb_trigger *t)
+{
+    assert(t->result != NULL);
+    list_remove(&t->node);
+    list_push_back(t->completion, &t->node);
+}
diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h
new file mode 100644 (file)
index 0000000..521b150
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRIGGER_H
+#define OVSDB_TRIGGER_H 1
+
+#include "list.h"
+
+struct ovsdb;
+
+struct ovsdb_trigger {
+    struct list node;           /* !result: in struct ovsdb "triggers" list;
+                                 * result: in completion list. */
+    struct list *completion;    /* Completion list. */
+    struct json *request;       /* Database request. */
+    struct json *result;        /* Result (null if none yet). */
+    long long int created;      /* Time created. */
+    long long int timeout_msec; /* Max wait duration. */
+};
+
+void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *,
+                        struct json *request, struct list *completion,
+                        long long int now);
+void ovsdb_trigger_destroy(struct ovsdb_trigger *);
+
+bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
+struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
+
+void ovsdb_trigger_run(struct ovsdb *, long long int now);
+void ovsdb_trigger_wait(struct ovsdb *, long long int now);
+
+#endif /* ovsdb/trigger.h */
index 6284511250629b7cc6bcbb9afd888f6e0cbd1f61..e8a429fa10e45337e3e54c6e9705a46585c29c75 100644 (file)
@@ -15,6 +15,18 @@ TESTSUITE_AT = \
        tests/jsonrpc.at \
        tests/timeval.at \
        tests/lockfile.at \
+       tests/ovsdb.at \
+       tests/ovsdb-file.at \
+       tests/ovsdb-types.at \
+       tests/ovsdb-data.at \
+       tests/ovsdb-column.at \
+       tests/ovsdb-table.at \
+       tests/ovsdb-row.at \
+       tests/ovsdb-condition.at \
+       tests/ovsdb-query.at \
+       tests/ovsdb-transaction.at \
+       tests/ovsdb-execution.at \
+       tests/ovsdb-trigger.at \
        tests/stp.at \
        tests/ovs-vsctl.at \
        tests/lcov-post.at
@@ -89,6 +101,11 @@ noinst_PROGRAMS += tests/test-lockfile
 tests_test_lockfile_SOURCES = tests/test-lockfile.c
 tests_test_lockfile_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-ovsdb
+tests_test_ovsdb_SOURCES = tests/test-ovsdb.c
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+EXTRA_DIST += tests/uuidfilt.pl
+
 noinst_PROGRAMS += tests/test-sha1
 tests_test_sha1_SOURCES = tests/test-sha1.c
 tests_test_sha1_LDADD = lib/libopenvswitch.a
index c48828e9d00830b1778b5e91b55dadb67a217458..ffcd4b835759d4ff5c352d91e9e08cd56e6b19e3 100644 (file)
@@ -11,6 +11,7 @@ OVS_CHECK_LCOV([test-csum], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test flow classifier])
+AT_KEYWORDS([slow])
 OVS_CHECK_LCOV([test-classifier], [0], [ignore])
 AT_CLEANUP
 
diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at
new file mode 100644 (file)
index 0000000..03cd8dc
--- /dev/null
@@ -0,0 +1,18 @@
+AT_BANNER([OVSDB -- columns])
+
+OVSDB_CHECK_POSITIVE([ordinary column],
+  [[parse-column mycol '{"type": "integer"}']],
+  [[{"type":"integer"}]])
+
+OVSDB_CHECK_POSITIVE([immutable column],
+  [[parse-column mycol '{"type": "real", "mutable": false}']],
+  [[{"mutable":false,"type":"real"}]])
+
+OVSDB_CHECK_POSITIVE([ephemeral column],
+  [[parse-column mycol '{"type": "uuid", "ephemeral": true}']],
+  [[{"ephemeral":true,"type":"uuid"}]])
+
+OVSDB_CHECK_POSITIVE([column with comment],
+  [[parse-column mycol '{"type": "boolean",
+                         "comment": "extra information about this column"}']],
+  [[{"comment":"extra information about this column","type":"boolean"}]])
diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at
new file mode 100644 (file)
index 0000000..3715b6d
--- /dev/null
@@ -0,0 +1,558 @@
+AT_BANNER([OVSDB -- conditions])
+
+OVSDB_CHECK_POSITIVE([null condition],
+  [[parse-conditions \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([conditions on scalars],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "==", 0]]' \
+    '[["i", "!=", 1]]' \
+    '[["i", "<", 2]]' \
+    '[["i", "<=", 3]]' \
+    '[["i", ">", 4]]' \
+    '[["i", ">=", 5]]' \
+    '[["i", "includes", 6]]' \
+    '[["i", "excludes", 7]]' \
+    '[["r", "==", 0.5]]' \
+    '[["r", "!=", 1.5]]' \
+    '[["r", "<", 2.5]]' \
+    '[["r", "<=", 3.5]]' \
+    '[["r", ">", 4.5]]' \
+    '[["r", ">=", 5.5]]' \
+    '[["r", "includes", 6.5]]' \
+    '[["r", "excludes", 7.5]]' \
+    '[["b", "==", true]]' \
+    '[["b", "!=", false]]' \
+    '[["b", "includes", false]]' \
+    '[["b", "excludes", true]]' \
+    '[["s", "==", "a"]]' \
+    '[["s", "!=", "b"]]' \
+    '[["s", "includes", "c"]]' \
+    '[["s", "excludes", "d"]]' \
+    '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [[[["i","==",0]]
+[["i","!=",1]]
+[["i","<",2]]
+[["i","<=",3]]
+[["i",">",4]]
+[["i",">=",5]]
+[["i","includes",6]]
+[["i","excludes",7]]
+[["r","==",0.5]]
+[["r","!=",1.5]]
+[["r","<",2.5]]
+[["r","<=",3.5]]
+[["r",">",4.5]]
+[["r",">=",5.5]]
+[["r","includes",6.5]]
+[["r","excludes",7.5]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",true]]
+[["s","==","a"]]
+[["s","!=","b"]]
+[["s","includes","c"]]
+[["s","excludes","d"]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]]
+[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]
+[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]],
+  [condition])
+
+AT_SETUP([disallowed conditions on scalars])
+AT_KEYWORDS([ovsdb negative condition])
+OVS_CHECK_LCOV([[test-ovsdb parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["b", ">", true]]' \
+    '[["b", ">=", false]]' \
+    '[["b", "<", false]]' \
+    '[["b", "<=", false]]' \
+    '[["s", ">", "a"]]' \
+    '[["s", ">=", "b"]]' \
+    '[["s", "<", "c"]]' \
+    '[["s", "<=", "d"]]' \
+    '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [1], [],
+  [[test-ovsdb: syntax "["b",">",true]": syntax error: Type mismatch: ">" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b",">=",false]": syntax error: Type mismatch: ">=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<",false]": syntax error: Type mismatch: "<" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<=",false]": syntax error: Type mismatch: "<=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s",">","a"]": syntax error: Type mismatch: ">" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s",">=","b"]": syntax error: Type mismatch: ">=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<","c"]": syntax error: Type mismatch: "<" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<=","d"]": syntax error: Type mismatch: "<=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": syntax error: Type mismatch: ">" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u",">=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: ">=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([conditions on sets],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "==", ["set", []]]]' \
+    '[["i", "!=", ["set", [1]]]]' \
+    '[["i", "includes", ["set", [1, 2]]]]' \
+    '[["i", "excludes", ["set", [1, 2, 3]]]]' \
+    '[["r", "==", ["set", []]]]' \
+    '[["r", "!=", ["set", [1.5]]]]' \
+    '[["r", "includes", ["set", [1.5, 2.5]]]]' \
+    '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \
+    '[["b", "==", ["set", [true]]]]' \
+    '[["b", "!=", ["set", [false]]]]' \
+    '[["b", "includes", ["set", [false]]]]' \
+    '[["b", "excludes", ["set", [true, false]]]]' \
+    '[["s", "==", ["set", ["a"]]]]' \
+    '[["s", "!=", ["set", ["a", "b"]]]]' \
+    '[["s", "includes", ["set", ["c"]]]]' \
+    '[["s", "excludes", ["set", ["c", "d"]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+    '[["u", "includes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \
+    '[["u", "excludes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],
+                ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \
+]],
+  [[[["i","==",["set",[]]]]
+[["i","!=",["set",[1]]]]
+[["i","includes",["set",[1,2]]]]
+[["i","excludes",["set",[1,2,3]]]]
+[["r","==",["set",[]]]]
+[["r","!=",["set",[1.5]]]]
+[["r","includes",["set",[1.5,2.5]]]]
+[["r","excludes",["set",[1.5,2.5,3.5]]]]
+[["b","==",["set",[true]]]]
+[["b","!=",["set",[false]]]]
+[["b","includes",["set",[false]]]]
+[["b","excludes",["set",[false,true]]]]
+[["s","==",["set",["a"]]]]
+[["s","!=",["set",["a","b"]]]]
+[["s","includes",["set",["c"]]]]
+[["s","excludes",["set",["c","d"]]]]
+[["u","==",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [condition])
+
+OVSDB_CHECK_POSITIVE([condition sorting],
+  [[parse-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[["i", "excludes", 7],
+      ["i", "!=", 8],
+      ["i", "==", 1],
+      ["i", "includes", 2],
+      ["i", "<=", 3],
+      ["i", "<", 4],
+      ["i", ">", 6],
+      ["i", ">=", 5],
+      ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']],
+  [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]])
+
+OVSDB_CHECK_POSITIVE([evaluating null condition],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: TTT])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on integers],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "<", 1]],
+      [["i", "<=", 1]],
+      [["i", "==", 1]],
+      [["i", "!=", 1]],
+      [["i", ">=", 1]],
+      [["i", ">", 1]],
+      [["i", "includes", 1]],
+      [["i", "excludes", 1]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on reals],
+  [[evaluate-conditions \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "<", 5.0]],
+      [["r", "<=", 5.0]],
+      [["r", "==", 5.0]],
+      [["r", "!=", 5.0]],
+      [["r", ">=", 5.0]],
+      [["r", ">", 5.0]],
+      [["r", "includes", 5.0]],
+      [["r", "excludes", 5.0]]]' \
+    '[{"r": 0},
+      {"r": 5.0},
+      {"r": 5.1}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on booleans],
+  [[evaluate-conditions \
+    '{"columns": {"b": {"type": "boolean"}}}' \
+    '[[["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", true]],
+      [["b", "excludes", true]],
+      [["b", "==", false]],
+      [["b", "!=", false]],
+      [["b", "includes", false]],
+      [["b", "excludes", false]]]' \
+    '[{"b": true},
+      {"b": false}']]],
+  [condition  0: T-
+condition  1: -T
+condition  2: T-
+condition  3: -T
+condition  4: -T
+condition  5: T-
+condition  6: -T
+condition  7: T-], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on strings],
+  [[evaluate-conditions \
+    '{"columns": {"s": {"type": "string"}}}' \
+    '[[["s", "==", ""]],
+      [["s", "!=", ""]],
+      [["s", "includes", ""]],
+      [["s", "excludes", ""]],
+      [["s", "==", "foo"]],
+      [["s", "!=", "foo"]],
+      [["s", "includes", "foo"]],
+      [["s", "excludes", "foo"]]]' \
+    '[{"s": ""},
+      {"s": "foo"},
+      {"s": "xxx"}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs],
+  [[evaluate-conditions \
+    '{"columns": {"u": {"type": "uuid"}}}' \
+    '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \
+    '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]},
+      {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]},
+      {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on sets],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]']],
+  [dnl
+condition  0: T---- ----- -----
+condition  1: -T--- ----- -----
+condition  2: ---T- ----- -----
+condition  3: ----- T---- -----
+condition  4: ----- --T-- -----
+condition  5: ----- ----T -----
+condition  6: ----- ----- -T---
+condition  7: ----- ----- ---T-
+condition  8: -TTTT TTTTT TTTTT
+condition  9: T-TTT TTTTT TTTTT
+condition 10: TTT-T TTTTT TTTTT
+condition 11: TTTTT -TTTT TTTTT
+condition 12: TTTTT TT-TT TTTTT
+condition 13: TTTTT TTTT- TTTTT
+condition 14: TTTTT TTTTT T-TTT
+condition 15: TTTTT TTTTT TTT-T
+condition 16: TTTTT TTTTT TTTTT
+condition 17: -T--- TT--T T--T-
+condition 18: ---T- T---- -T-TT
+condition 19: ----- T---- ---T-
+condition 20: ----- --T-T -TTTT
+condition 21: ----- ----T ---T-
+condition 22: ----- ----- -T-TT
+condition 23: ----- ----- ---T-
+condition 24: TTTTT TTTTT TTTTT
+condition 25: T-TTT --TT- -TT-T
+condition 26: TTT-T -TTTT T-T--
+condition 27: T-T-T --TT- --T--
+condition 28: TTTTT TT-T- T----
+condition 29: T-TTT ---T- -----
+condition 30: TTT-T -T-T- T----
+condition 31: T-T-T ---T- -----], [condition])
diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
new file mode 100644 (file)
index 0000000..bedaf70
--- /dev/null
@@ -0,0 +1,259 @@
+AT_BANNER([OVSDB -- atoms])
+
+OVSDB_CHECK_POSITIVE([integer atom], 
+  [[parse-atoms '["integer"]' \
+    '[0]' \
+    '[-1]' \
+    '[1e3]' \
+    '[9223372036854775807]' \
+    '[-9223372036854775808]' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([real atom], 
+  [[parse-atoms '["real"]' \
+    '[0]' \
+    '[0.0]' \
+    '[-0.0]' \
+    '[-1.25]' \
+    '[1e3]' \
+    '[1e37]' \
+    '[0.00390625]' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([boolean atom],
+  [[parse-atoms '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string atom],
+  [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([uuid atom],
+  [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']],
+  [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]])
+
+OVSDB_CHECK_POSITIVE([integer atom sorting],
+  [[sort-atoms '["integer"]' '[55,0,-1,2,1]']],
+  [[[-1,0,1,2,55]]])
+
+OVSDB_CHECK_POSITIVE([real atom sorting],
+  [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']],
+  [[[-1e+99,0,0,1.23,1.25]]])
+
+OVSDB_CHECK_POSITIVE([boolean atom sorting],
+  [[sort-atoms '["boolean"]' '[true,false,true,false,false]']],
+  [[[false,false,false,true,true]]])
+
+OVSDB_CHECK_POSITIVE([string atom sorting],
+  [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']],
+  [[["\b","abc","abd","xxx"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom sorting],
+  [[sort-atoms '["uuid"]' '[
+    ["uuid", "00000000-0000-0000-0000-000000000001"],
+    ["uuid", "00000000-1000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-1000-0000-000000000000"],
+    ["uuid", "00010000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000100"],
+    ["uuid", "00000000-0000-0000-0000-000100000000"],
+    ["uuid", "00000000-0000-0010-0000-000000000000"],
+    ["uuid", "00000100-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0001-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000001000000"],
+    ["uuid", "01000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000001000"],
+    ["uuid", "00000000-0000-0000-0000-000010000000"],
+    ["uuid", "00000000-0000-0000-0000-010000000000"],
+    ["uuid", "00000000-0000-0100-0000-000000000000"],
+    ["uuid", "10000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000010"],
+    ["uuid", "00000000-0100-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0100-000000000000"],
+    ["uuid", "00000000-0000-0000-0001-000000000000"],
+    ["uuid", "00000010-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0010-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000010000"],
+    ["uuid", "00000000-0000-0000-1000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-100000000000"],
+    ["uuid", "00000000-0000-0000-0000-001000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000100000"],
+    ["uuid", "00000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0010-0000-0000-000000000000"],
+    ["uuid", "00100000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0001-0000-0000-000000000000"],
+    ["uuid", "00000001-0000-0000-0000-000000000000"],
+    ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
+  [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer atom],
+  [[parse-atoms '["integer"]' '[0.5]' ]],
+  [expected integer])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean atom],
+  [[parse-atoms '["boolean"]' '["true"]' ]],
+  [expected boolean])
+
+OVSDB_CHECK_NEGATIVE([integer not acceptable string atom],
+  [[parse-atoms '["string"]' '[1]']],
+  [expected string])
+
+OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as array],
+  [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
+  [[expected ["uuid", <string>]]])
+
+AT_BANNER([OSVDB -- simple data])
+
+OVSDB_CHECK_POSITIVE([integer datum],
+  [[parse-data '["integer"]' '[0]' '[1]' '[-1]']],
+  [0
+1
+-1])
+
+OVSDB_CHECK_POSITIVE([real datum], 
+  [[parse-data '["real"]' '[0]' '[1.0]' '[-1.25]']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([boolean datum],
+  [[parse-data '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string datum],
+  [[parse-data '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+AT_BANNER([OVSDB -- set data])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-data '{"key": "boolean", "min": 0}' \
+    '["set", [true]]' \
+    '["set", [false]]' \
+    '["set", []]']], 
+  [[["set",[true]]
+["set",[false]]
+["set",[]]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '["set", [0]]' \
+    '["set", [0, 1]]' \
+    '["set", [0, 1, 2]]' \
+    '["set", [0, 1, 2, 3, 4, 5]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']],
+  [[["set",[0]]
+["set",[0,1]]
+["set",[0,1,2]]
+["set",[0,1,2,3,4,5]]
+["set",[0,1,2,3,4,5,6,7,8]]
+["set",[0,1,2,3,4,5,6,7,8,9,10]]]])
+
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \
+    '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \
+    '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"],
+              ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"],
+              ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']],
+  [[["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"]]]
+["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]])
+
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-data '{"key": "string", "min": 0, "max": 3}' \
+    '["set", []]' \
+    '["set", ["a relatively long string"]]' \
+    '["set", ["short string", "a relatively long string"]]' \
+    '["set", ["zzz", "short string", "a relatively long string"]]']],
+  [[["set",[]]
+["set",["a relatively long string"]]
+["set",["a relatively long string","short string"]]
+["set",["a relatively long string","short string","zzz"]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in set],
+  [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in set],
+  [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in set],
+  [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in set],
+  [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in set],
+  [[parse-data '{"key": "uuid", "max": 5}' \
+    '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"],
+              ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"],
+              ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']],
+  [ovsdb error: set contains duplicate])
+
+AT_BANNER([OVSDB -- map data])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean"}' \
+    '["map", [[1, true]]]']],
+  [[["map",[[1,true]]]]])
+
+OVSDB_CHECK_POSITIVE([map of at least 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '["map", [[1, true]]]' \
+    '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \
+    '["map", [[3, false], [0, true], [4, false]]]']],
+  [[["map",[[1,true]]]
+["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]]
+["map",[[0,true],[3,false],[4,false]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer],
+ [[parse-data '{"key": "boolean", "value": "integer"}' \
+   '["map", [[true, 1]]]']],
+ [[["map",[[true,1]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 5 uuid to real],
+  [[parse-data '{"key": "uuid", "value": "real", "min": 5, "max": 5}' \
+    '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0],
+             [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0],
+             [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0],
+             [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0],
+             [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']],
+  [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]])
+
+OVSDB_CHECK_POSITIVE([map of 10 string to string],
+  [[parse-data '{"key": "string", "value": "string", "min": 10, "max": 10}' \
+    '["map", [["2 gills", "1 chopin"],
+             ["2 chopins", "1 pint"],
+             ["2 pints", "1 quart"],
+             ["2 quarts", "1 pottle"],
+             ["2 pottles", "1 gallon"],
+             ["2 gallons", "1 peck"],
+             ["2 pecks", "1 demibushel"],
+             ["2 demibushel", "1 firkin"],
+             ["2 firkins", "1 kilderkin"],
+             ["2 kilderkins", "1 barrel"]]]']],
+   [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in map],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \
+    '["map", [[1, true], [2, false], [1, false]]]']],
+  [ovsdb error: map contains duplicate key])
diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at
new file mode 100644 (file)
index 0000000..e629298
--- /dev/null
@@ -0,0 +1,321 @@
+AT_BANNER([OVSDB -- execution])
+
+m4_define([ORDINAL_SCHEMA],
+  [['{"name": "mydb",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}}}}}']])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution positive $4])
+   OVS_CHECK_LCOV([test-ovsdb execute $2], [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+   AT_CLEANUP])
+
+OVSDB_CHECK_EXECUTION([insert row, query table],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by value],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]'\ 
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "one"]]}]']],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "second"]]]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]' \
+    '[{"op": "update",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "row": {"name": "nought"}}]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]']],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete rows by value],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]' \
+    '[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]' \
+    '[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "nought"]]}]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]']],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":0}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete all],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": []},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": false}]']],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit durably],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": true}]']],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with correct rows],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with extra row],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with missing row],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "one", "number": 1}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with correct rows],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with extra row],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with missing row],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "one", "number": 1}]}]']],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
diff --git a/tests/ovsdb-file.at b/tests/ovsdb-file.at
new file mode 100644 (file)
index 0000000..c85b29e
--- /dev/null
@@ -0,0 +1,282 @@
+AT_BANNER([OVSDB -- file I/O])
+
+AT_SETUP([create empty, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_CREAT|O_RDWR'], [0], 
+  [file: open successful
+], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read], [0], 
+  [file: open successful
+file: read: end of file
+], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([check that O_EXCL works])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[1]']], [0], 
+  [[file: open successful
+file: write:[1] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read], [0], 
+  [[file: open successful
+file: read: [1]
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_CREAT|O_RDWR|O_EXCL' read], [1], 
+  [], [test-ovsdb: I/O error: create: file failed (File exists)
+])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread, append])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["append"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: write:["append"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["append"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, reread one, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read 'write:["more data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: write:["more data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: ["more data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:[3]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+file: write:[3] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: [3]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, corrupt some data, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '\[3]' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, truncate file, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '^2$' file]], [0], [1
+])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file)
+file: write:["longer data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write bad JSON, read, overwrite])
+AT_KEYWORDS([ovsdb file])
+AT_CAPTURE_FILE([file])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
+OVS_CHECK_LCOV(
+  [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:["replacement data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (syntax error at beginning of input)
+file: write:["replacement data"] successful
+]], [ignore])
+OVS_CHECK_LCOV(
+  [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["replacement data"]
+file: read: end of file
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at
new file mode 100644 (file)
index 0000000..c39aa12
--- /dev/null
@@ -0,0 +1,535 @@
+AT_BANNER([OVSDB -- queries])
+
+OVSDB_CHECK_POSITIVE([queries on scalars],
+  [[query \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']],
+  [dnl
+query  0: 11111
+query  1: 1----
+query  2: 1-111
+query  3: 11---
+query  4: 1111-
+query  5: ---11
+query  6: ----1
+query  7: ---1-
+query  8: 11-11
+query  9: 1----
+query 10: 1-111
+query 11: 11---
+query 12: 1111-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: 11111
+query 17: 1-1-1
+query 18: -1-1-
+query 19: -1-1-
+query 20: -1-1-
+query 21: 1----
+query 22: 1-111
+query 23: --1--
+query 24: 111-1
+query 25: 1----
+query 26: 1-111
+query 27: --1--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([queries on sets],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]' \
+    '[[],
+      [["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([queries on maps (1)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([queries on maps (2)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 11111 11111
+query  1: 1---- ----- -----
+query  2: -1--- ----- -----
+query  3: ---1- ----- -----
+query  4: ----- 1---- -----
+query  5: ----- --1-- -----
+query  6: ----- ----1 -----
+query  7: ----- ----- -1---
+query  8: ----- ----- ---1-
+query  9: -1111 11111 11111
+query 10: 1-111 11111 11111
+query 11: 111-1 11111 11111
+query 12: 11111 -1111 11111
+query 13: 11111 11-11 11111
+query 14: 11111 1111- 11111
+query 15: 11111 11111 1-111
+query 16: 11111 11111 111-1
+query 17: 11111 11111 11111
+query 18: -1--- 11--1 1--1-
+query 19: ---1- 1---- -1-11
+query 20: ----- 1---- ---1-
+query 21: ----- --1-1 -1111
+query 22: ----- ----1 ---1-
+query 23: ----- ----- -1-11
+query 24: ----- ----- ---1-
+query 25: 11111 11111 11111
+query 26: 1-111 --11- -11-1
+query 27: 111-1 -1111 1-1--
+query 28: 1-1-1 --11- --1--
+query 29: 11111 11-1- 1----
+query 30: 1-111 ---1- -----
+query 31: 111-1 -1-1- 1----
+query 32: 1-1-1 ---1- -----], [query])
+
+OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["_uuid"]']],
+  [dnl
+query  0: abcde
+query  1: a----
+query  2: a-cde
+query  3: ab---
+query  4: abcd-
+query  5: ---de
+query  6: ----e
+query  7: ---d-
+query  8: ab-de
+query  9: a----
+query 10: a-cde
+query 11: ab---
+query 12: abcd-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: abcde
+query 17: a-c-e
+query 18: -b-d-
+query 19: -b-d-
+query 20: -b-d-
+query 21: a----
+query 22: a-cde
+query 23: --c--
+query 24: abc-e
+query 25: a----
+query 26: a-cde
+query 27: --c--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["b"]']],
+  [dnl
+query  0: ababa
+query  1: a-a-a
+query  2: ababa
+query  3: ababa
+query  4: ababa
+query  5: ababa
+query  6: a-a-a
+query  7: -b-b-
+query  8: ababa
+query  9: a-a-a
+query 10: ababa
+query 11: ababa
+query 12: ababa
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: ababa
+query 17: a-a-a
+query 18: -b-b-
+query 19: -b-b-
+query 20: -b-b-
+query 21: a-a-a
+query 22: ababa
+query 23: a-a-a
+query 24: ababa
+query 25: a-a-a
+query 26: ababa
+query 27: a-a-a],
+  [query])
diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at
new file mode 100644 (file)
index 0000000..e631f6f
--- /dev/null
@@ -0,0 +1,277 @@
+AT_BANNER([OVSDB -- rows])
+
+# Autoconf 2.63 has a bug that causes the double-quotes below to be
+# lost, so that the following tests fail, so we mark them as XFAIL for
+# Autoconf < 2.64.
+
+m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]])
+
+OVSDB_CHECK_POSITIVE([row with one string column],
+  [[parse-rows \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '{"name": "value"}' \
+    '{"name": ""}' \
+    '{"name": "longer string with spaces"}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"name":"value"}
+name
+{RESERVED_COLUMNS,"name":""}
+name
+{RESERVED_COLUMNS,"name":"longer string with spaces"}
+name
+{RESERVED_COLUMNS,"name":""}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one integer column],
+  [[parse-rows \
+    '{"columns": {"count": {"type": "integer"}}}' \
+    '{"count": 1}' \
+    '{"count": -1}' \
+    '{"count": 2e10}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"count":1}
+count
+{RESERVED_COLUMNS,"count":-1}
+count
+{RESERVED_COLUMNS,"count":20000000000}
+count
+{RESERVED_COLUMNS,"count":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one real column],
+  [[parse-rows \
+    '{"columns": {"cost": {"type": "real"}}}' \
+    '{"cost": 1.0}' \
+    '{"cost": -2.0}' \
+    '{"cost": 123000}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"cost":1}
+cost
+{RESERVED_COLUMNS,"cost":-2}
+cost
+{RESERVED_COLUMNS,"cost":123000}
+cost
+{RESERVED_COLUMNS,"cost":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one boolean column],
+  [[parse-rows \
+    '{"columns": {"feasible": {"type": "boolean"}}}' \
+    '{"feasible": true}' \
+    '{"feasible": false}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"feasible":true}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one uuid column],
+  [[parse-rows \
+    '{"columns": {"ref": {"type": "uuid"}}}' \
+    '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \
+    '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["myset":["set",[0]]]}
+<none>])
+
+OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with several columns],
+  [[parse-rows \
+    '{"columns":
+        {"vswitch": {"type": "uuid"},
+         "name": {"type": "string"},
+         "datapath_id": {"type": {"key": "string", "min": 0}},
+         "hwaddr": {"type": "string"},
+         "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "controller": {"type": {"key": "uuid", "min": 0}},
+         "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"],
+      "name": "br0",
+      "datapath_id": ["set", ["000ae4256bb0"]],
+      "hwaddr": "00:0a:e4:25:6b:b0"}' \
+    '{}']],
+ [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",["000ae4256bb0"]],"hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]}
+datapath_id, hwaddr, name, vswitch
+{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row hashing (scalars)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+     '["null", {}]' \
+     '["i1", {"i": 1}]' \
+     '["i2", {"i": 2}]' \
+     '["i4", {"i": 4}]' \
+     '["i8", {"i": 8}]' \
+     '["i16", {"i": 16}]' \
+     '["i32", {"i": 32}]' \
+     '["i64", {"i": 64}]' \
+     '["i128", {"i": 128}]' \
+     '["i256", {"i": 256}]' \
+     '["null2", {"r": -0}]' \
+     '["r123", {"r": 123}]' \
+     '["r0.0625", {"r": 0.0625}]' \
+     '["r0.125", {"r": 0.125}]' \
+     '["r0.25", {"r": 0.25}]' \
+     '["r0.5", {"r": 0.5}]' \
+     '["r1", {"r": 1}]' \
+     '["r2", {"r": 2}]' \
+     '["r4", {"r": 4}]' \
+     '["r8", {"r": 8}]' \
+     '["r16", {"r": 16}]' \
+     '["r32", {"r": 32}]' \
+     '["null3", {"b": false}]' \
+     '["b1", {"b": true}]' \
+     '["null4", {"s": ""}]' \
+     '["s0", {"s": "a"}]' \
+     '["s1", {"s": "b"}]' \
+     '["s2", {"s": "c"}]' \
+     '["s3", {"s": "d"}]' \
+     '["s4", {"s": "e"}]' \
+     '["s5", {"s": "f"}]' \
+     '["s6", {"s": "g"}]' \
+     '["s7", {"s": "h"}]' \
+     '["s8", {"s": "i"}]' \
+     '["s9", {"s": "j"}]' \
+     '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \
+     '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \
+     '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \
+     '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \
+     '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \
+     '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \
+     '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \
+     '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \
+     '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \
+     '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']],
+  [[null == null2
+null == null3
+null == null4
+null == null5
+hash(null) == hash(null6)
+null2 == null3
+null2 == null4
+null2 == null5
+hash(null2) == hash(null6)
+null3 == null4
+null3 == null5
+hash(null3) == hash(null6)
+null4 == null5
+hash(null4) == hash(null6)
+hash(null5) == hash(null6)]])
+
+OVSDB_CHECK_POSITIVE([row hashing (sets)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '["null0", {"i": ["set", []]}]' \
+    '["i0", {"i": ["set", [0]]}]' \
+    '["i01", {"i": ["set", [0, 1]]}]' \
+    '["i012", {"i": ["set", [0, 1, 2]]}]' \
+    '["i021", {"i": ["set", [0, 2, 1]]}]' \
+    '["i201", {"i": ["set", [2, 0, 1]]}]' \
+    '["i102", {"i": ["set", [1, 0, 2]]}]' \
+    '["i120", {"i": ["set", [1, 2, 0]]}]' \
+    '["i210", {"i": ["set", [2, 1, 0]]}]' \
+    '["r0", {"r": ["set", [0]]}]' \
+    '["r01", {"r": ["set", [0, 1]]}]' \
+    '["r012", {"r": ["set", [0, 1, 2]]}]' \
+    '["r201", {"r": ["set", [2, 0, 1]]}]' \
+    '["null1", {"b": ["set", []]}]' \
+    '["b0", {"b": ["set", [false]]}]' \
+    '["b1", {"b": ["set", [true]]}]' \
+    '["b01", {"b": ["set", [false, true]]}]' \
+    '["b10", {"b": ["set", [true, false]]}]' \
+    '["null2", {"s": ["set", []]}]' \
+    '["sa", {"s": ["set", ["a"]]}]' \
+    '["sb", {"s": ["set", ["b"]]}]' \
+    '["sab", {"s": ["set", ["a", "b"]]}]' \
+    '["sba", {"s": ["set", ["b", "a"]]}]']],
+  [[null0 == null1
+null0 == null2
+i012 == i021
+i012 == i201
+i012 == i102
+i012 == i120
+i012 == i210
+i021 == i201
+i021 == i102
+i021 == i120
+i021 == i210
+i201 == i102
+i201 == i120
+i201 == i210
+i102 == i120
+i102 == i210
+i120 == i210
+r012 == r201
+null1 == null2
+b01 == b10
+sab == sba]])
+
+OVSDB_CHECK_POSITIVE([row hashing (maps)],
+  [[compare-rows \
+    '{"columns":
+        {"ii": {"type": {"key": "integer", "value": "integer", 
+                         "min": 0, "max": "unlimited"}},
+         "rr": {"type": {"key": "real", "value": "real",
+                         "min": 0, "max": "unlimited"}},
+         "bb": {"type": {"key": "boolean", "value": "boolean",
+                         "min": 0, "max": "unlimited"}},
+        "ss": {"type": {"key": "string", "value": "string",
+                         "min": 0, "max": "unlimited"}}}}' \
+    '["null", {}]' \
+    '["ii0", {"ii": ["map", [[0, 0]]]}]' \
+    '["ii1", {"ii": ["map", [[0, 1]]]}]' \
+    '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \
+    '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \
+    '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \
+    '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 0]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 1]]]}]' \
+    '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \
+    '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \
+    '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \
+    '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \
+    '["bb0", {"bb": ["map", [[false, false]]]}]' \
+    '["bb1", {"bb": ["map", [[false, true]]]}]' \
+    '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \
+    '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \
+    '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \
+    '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \
+    '["ss0", {"ss": ["map", [["a", "a"]]]}]' \
+    '["ss1", {"ss": ["map", [["a", "b"]]]}]' \
+    '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \
+    '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \
+    '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \
+    '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo
+]], [[]])
diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at
new file mode 100644 (file)
index 0000000..ebc5992
--- /dev/null
@@ -0,0 +1,31 @@
+AT_BANNER([OVSDB -- tables])
+
+OVSDB_CHECK_POSITIVE([table with one column],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']],
+  [[{"columns":{"name":{"type":"string"}}}]])
+
+OVSDB_CHECK_POSITIVE([immutable table with one column],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "mutable": false}']],
+  [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
+
+OVSDB_CHECK_POSITIVE([table with comment],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "comment": "description of table"}']],
+  [[{"columns":{"name":{"type":"string"}},"comment":"description of table"}]])
+
+OVSDB_CHECK_NEGATIVE([column names may not begin with _],
+  [[parse-table mytable \
+    '{"columns": {"_column": {"type": "integer"}}}']],
+  [[names beginning with "_" are reserved]],
+  [table])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
+  [[parse-table mytable '{}']],
+  [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
+  [[parse-table mytable '{"columns": {}}']],
+  [[table must have at least one column]])
diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at
new file mode 100644 (file)
index 0000000..f0c29d4
--- /dev/null
@@ -0,0 +1,384 @@
+AT_BANNER([OVSDB -- transactions])
+
+OVSDB_CHECK_POSITIVE([empty table, empty transaction],
+  [[transact \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+print:
+commit:
+print:
+abort:
+print:])
+
+OVSDB_CHECK_POSITIVE([nonempty table, empty transaction],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+print:
+1: i=2, j=3
+2: i=2, j=3
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3])
+
+OVSDB_CHECK_POSITIVE([insert, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+commit:
+print:
+1: i=2, j=4
+2: i=5, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at
new file mode 100644 (file)
index 0000000..5980d85
--- /dev/null
@@ -0,0 +1,173 @@
+AT_BANNER([OVSDB -- triggers])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_TRIGGER], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution trigger positive $4])
+   OVS_CHECK_LCOV([test-ovsdb trigger $2], [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+   AT_CLEANUP])
+
+OVSDB_CHECK_TRIGGER([trigger fires immediately],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger times out],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 10]']],
+  [[t=0: new trigger 0
+t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger fires after delay],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{}]
+]])
+
+OVSDB_CHECK_TRIGGER([delayed trigger modifies database],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{},{"count":2}]
+t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}]
+]])
+
+OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another],
+  [ORDINAL_SCHEMA [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 2]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 3, "name": "three"}}]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=5: new trigger 2
+t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 2 (delayed): [{},{"count":2}]
+t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}]
+t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}]
+]])
+
diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at
new file mode 100644 (file)
index 0000000..9a92a5c
--- /dev/null
@@ -0,0 +1,90 @@
+AT_BANNER([OVSDB -- atomic types])
+
+OVSDB_CHECK_POSITIVE([integer], 
+  [[parse-atomic-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real], 
+  [[parse-atomic-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-atomic-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([string], 
+  [[parse-atomic-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([uuid], 
+  [[parse-atomic-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
+  [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- simple types])
+
+OVSDB_CHECK_POSITIVE([simple integer], 
+  [[parse-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([simple real], 
+  [[parse-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([simple boolean], 
+  [[parse-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([simple string], 
+  [[parse-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([simple uuid], 
+  [[parse-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_POSITIVE([integer in object],
+  [[parse-type '{"key": "integer"}' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real in object with explicit min and max],
+  [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"])
+
+OVSDB_CHECK_NEGATIVE([key type is required],
+  [[parse-type '{}' ]], [Required 'key' member is missing.])
+OVSDB_CHECK_NEGATIVE([void is not a valid type],
+  [[parse-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- set types])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-type '{"key": "boolean", "min": 0}' ]], 
+  [[{"key":"boolean","min":0}]],
+  [set])
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]], 
+  [[{"key":"uuid","max":3}]])
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-type '{"key": "string", "min": 0, "max": 3}' ]], 
+  [[{"key":"string","max":3,"min":0}]])
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']],
+  [[{"key":"integer","max":"unlimited","min":0}]])
+OVSDB_CHECK_POSITIVE([set of 10 or more reals],
+  [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']],
+  [[{"key":"real","max":"unlimited","min":10}]])
+
+OVSDB_CHECK_NEGATIVE([set max cannot be less than min],
+  [[parse-type '{"key": "real", "min": 5, "max": 3}' ]],
+  [ovsdb type fails constraint checks])
+OVSDB_CHECK_NEGATIVE([set max cannot be negative],
+  [[parse-type '{"key": "real", "max": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be negative],
+  [[parse-type '{"key": "real", "min": -1}' ]],
+  [bad min or max value])
+
+AT_BANNER([OVSDB -- map types])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-type '{"key": "integer", "value": "boolean"}' ]],
+ [[{"key":"integer","value":"boolean"}]])
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max],
+ [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]],
+ [[{"key":"boolean","value":"integer"}]])
+OVSDB_CHECK_POSITIVE([map of 2 to 5 uuid to real],
+ [[parse-type '{"key": "uuid", "value": "real", "min": 2, "max": 5}' ]],
+ [[{"key":"uuid","max":5,"min":2,"value":"real"}]])
+OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid],
+ [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]],
+ [[{"key":"string","max":10,"min":0,"value":"uuid"}]])
+OVSDB_CHECK_POSITIVE([map of 10 to 20 real to string],
+ [[parse-type '{"key": "real", "value": "string", "min": 10, "max": 20}' ]],
+ [[{"key":"real","max":20,"min":10,"value":"string"}]])
+OVSDB_CHECK_POSITIVE([map of 20 or more string to real],
+ [[parse-type '{"key": "string", "value": "real", "min": 20, "max": "unlimited"}' ]],
+ [[{"key":"string","max":"unlimited","min":20,"value":"real"}]])
+
+OVSDB_CHECK_NEGATIVE([map key type is required],
+ [[parse-type '{"value": "integer"}' ]],
+ [Required 'key' member is missing.])
diff --git a/tests/ovsdb.at b/tests/ovsdb.at
new file mode 100644 (file)
index 0000000..0e0e63b
--- /dev/null
@@ -0,0 +1,35 @@
+m4_define([OVSDB_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   m4_if([$5], [], [], 
+         [AT_XFAIL_IF([m4_version_prereq([$5], [false], [true])])])
+   AT_KEYWORDS([ovsdb positive $4])
+   OVS_CHECK_LCOV([test-ovsdb $2], [0], [$3
+], [])
+   AT_CLEANUP])
+
+m4_define([OVSDB_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb negative $4])
+   OVS_CHECK_LCOV([test-ovsdb $2], [1], [], [stderr])
+   m4_assert(m4_len([$3]))
+   AT_CHECK(
+     [if grep -F -e "AS_ESCAPE([$3])" stderr
+      then
+        :
+      else
+        exit 99
+      fi], 
+            [0], [ignore], [ignore])
+   AT_CLEANUP])
+
+m4_include([tests/ovsdb-file.at])
+m4_include([tests/ovsdb-types.at])
+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-condition.at])
+m4_include([tests/ovsdb-query.at])
+m4_include([tests/ovsdb-transaction.at])
+m4_include([tests/ovsdb-execution.at])
+m4_include([tests/ovsdb-trigger.at])
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
new file mode 100644 (file)
index 0000000..bee1818
--- /dev/null
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "svec.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+           "  file-io FILE FLAGS COMMAND...\n"
+           "    open FILE with FLAGS, run COMMANDs\n"
+           "  parse-atomic-type TYPE\n"
+           "    parse TYPE as OVSDB atomic type, and re-serialize\n"
+           "  parse-type JSON\n"
+           "    parse JSON as OVSDB type, and re-serialize\n"
+           "  parse-atoms TYPE ATOM...\n"
+           "    parse ATOMs as atoms of given TYPE, and re-serialize\n"
+           "  sort-atoms TYPE ATOM...\n"
+           "    print ATOMs in sorted order, and re-serialize\n"
+           "  parse-data TYPE DATUM...\n"
+           "    parse DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-column NAME OBJECT\n"
+           "    parse column NAME with info OBJECT, and re-serialize\n"
+           "  parse-table NAME OBJECT\n"
+           "    parse table NAME with info OBJECT\n"
+           "  parse-row TABLE ROW..., and re-serialize\n"
+           "    parse each ROW of defined TABLE\n"
+           "  compare-row TABLE ROW...\n"
+           "    mutually compare all of the ROWs, print those that are equal\n"
+           "  parse-conditions TABLE CONDITION...\n"
+           "    parse each CONDITION on TABLE, and re-serialize\n"
+           "  evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
+           "    test CONDITIONS on TABLE against each ROW, print results\n"
+           "  query TABLE [ROW,...] [CONDITION,...]\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION.\n"
+           "  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"
+           "  transact COMMAND\n"
+           "    execute each specified transactional COMMAND:\n"
+           "      commit\n"
+           "      abort\n"
+           "      insert UUID I J\n"
+           "      delete UUID\n"
+           "      modify UUID I J\n"
+           "      print\n"
+           "  execute SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA\n"
+           "  trigger SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA.   A TRANSACTION of the form\n"
+           "    [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+           "    simulated time, for causing triggers to time out.\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+    if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+        struct json *inner = json->u.array.elems[0];
+        json->u.array.elems[0] = NULL;
+        json_destroy(json);
+        return inner;
+    } else {
+        return json;
+    }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+/* Command implementations. */
+
+static void
+do_file_io(int argc, char *argv[])
+{
+    const char *name = argv[1];
+    char *mode = argv[2];
+
+    struct ovsdb_error *error;
+    struct ovsdb_file *file;
+    char *save_ptr = NULL;
+    const char *token;
+    int flags;
+    int i;
+
+    for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL;
+         token = strtok_r(NULL, " |", &save_ptr))
+    {
+        if (!strcmp(token, "O_RDONLY")) {
+            flags |= O_RDONLY;
+        } else if (!strcmp(token, "O_RDWR")) {
+            flags |= O_RDWR;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        } else if (!strcmp(token, "O_CREAT")) {
+            flags |= O_CREAT;
+        } else if (!strcmp(token, "O_EXCL")) {
+            flags |= O_EXCL;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        }
+    }
+
+    check_ovsdb_error(ovsdb_file_open(name, flags, &file));
+    printf("%s: open successful\n", name);
+
+    for (i = 3; i < argc; i++) {
+        const char *command = argv[i];
+        if (!strcmp(command, "read")) {
+            struct json *json;
+
+            error = ovsdb_file_read(file, &json);
+            if (!error) {
+                printf("%s: read: ", name);
+                if (json) {
+                    print_and_free_json(json);
+                } else {
+                    printf("end of file\n");
+                }
+                continue;
+            }
+        } else if (!strncmp(command, "write:", 6)) {
+            struct json *json = parse_json(command + 6);
+            error = ovsdb_file_write(file, json);
+            json_destroy(json);
+        } else if (!strcmp(command, "commit")) {
+            error = ovsdb_file_commit(file);
+        } else {
+            ovs_fatal(0, "unknown file-io command \"%s\"", command);
+        }
+        if (error) {
+            char *s = ovsdb_error_to_string(error);
+            printf("%s: %s failed: %s\n", name, command, s);
+            free(s);
+        } else {
+            printf("%s: %s successful\n", name, command);
+        }
+    }
+
+    ovsdb_file_close(file);
+}
+
+static void
+do_parse_atomic_type(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
+do_parse_type(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_type_to_json(&type));
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        union ovsdb_atom atom;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_atom_to_json(&atom, type));
+
+        ovsdb_atom_destroy(&atom, type);
+    }
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+    const union ovsdb_atom *a = a_;
+    const union ovsdb_atom *b = b_;
+
+    return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
+do_sort_atoms(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    union ovsdb_atom *atoms;
+    struct json *json, **json_atoms;
+    size_t n_atoms;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    json = unbox_json(parse_json(argv[2]));
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "second argument must be array");
+    }
+
+    /* Convert JSON atoms to internal representation. */
+    n_atoms = json->u.array.n;
+    atoms = xmalloc(n_atoms * sizeof *atoms);
+    for (i = 0; i < n_atoms; i++) {
+        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type,
+                                               json->u.array.elems[i], NULL));
+    }
+    json_destroy(json);
+
+    /* Sort atoms. */
+    compare_atoms_atomic_type = type;
+    qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+    /* Convert internal representation back to JSON. */
+    json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+    for (i = 0; i < n_atoms; i++) {
+        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type);
+        ovsdb_atom_destroy(&atoms[i], type);
+    }
+    print_and_free_json(json_array_create(json_atoms, n_atoms));
+}
+
+static void
+do_parse_column(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column *column;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+    json_destroy(json);
+    print_and_free_json(ovsdb_column_to_json(column));
+    ovsdb_column_destroy(column);
+}
+
+static void
+do_parse_table(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+    json_destroy(json);
+    print_and_free_json(ovsdb_table_schema_to_json(ts));
+    ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_column_set columns;
+        struct ovsdb_row *row;
+
+        ovsdb_column_set_init(&columns);
+        row = ovsdb_row_create(table);
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+        if (columns.n_columns) {
+            struct svec names;
+            size_t j;
+            char *s;
+
+            svec_init(&names);
+            for (j = 0; j < columns.n_columns; j++) {
+                svec_add(&names, columns.columns[j]->name);
+            }
+            svec_sort(&names);
+            s = svec_join(&names, ", ", "");
+            puts(s);
+            free(s);
+            svec_destroy(&names);
+        } else {
+            printf("<none>\n");
+        }
+
+        ovsdb_column_set_destroy(&columns);
+        ovsdb_row_destroy(row);
+    }
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_row **rows;
+    struct json *json;
+    char **names;
+    int n_rows;
+    int i, j;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    n_rows = argc - 2;
+    rows = xmalloc(sizeof *rows * n_rows);
+    names = xmalloc(sizeof *names * n_rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+
+        json = parse_json(argv[i + 2]);
+        if (json->type != JSON_ARRAY || json->u.array.n != 2
+            || json->u.array.elems[0]->type != JSON_STRING) {
+            ovs_fatal(0, "\"%s\" does not have expected form "
+                      "[\"name\", {data}]", argv[i]);
+        }
+        names[i] = xstrdup(json->u.array.elems[0]->u.string);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+                                              NULL, NULL));
+        json_destroy(json);
+    }
+    for (i = 0; i < n_rows; i++) {
+        uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+        for (j = i + 1; j < n_rows; j++) {
+            uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+            if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+                printf("%s == %s\n", names[i], names[j]);
+                if (i_hash != j_hash) {
+                    printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+                    abort();
+                }
+            } else if (i_hash == j_hash) {
+                printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+            }
+        }
+    }
+    free(rows);
+    free(names);
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_condition cnd;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+        if (!error) {
+            print_and_free_json(ovsdb_condition_to_json(&cnd));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_evaluate_conditions(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_condition *conditions;
+    size_t n_conditions;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse conditions. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    n_conditions = json->u.array.n;
+    conditions = xmalloc(n_conditions * sizeof *conditions);
+    for (i = 0; i < n_conditions; i++) {
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &conditions[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_conditions; i++) {
+        printf("condition %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            putchar(result ? 'T' : '-');
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_conditions; i++) {
+        ovsdb_condition_destroy(&conditions[i]);
+    }
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+struct do_query_cbdata {
+    struct uuid *row_uuids;
+    int *counts;
+    size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+    struct do_query_cbdata *cbdata = cbdata_;
+    size_t i;
+
+    for (i = 0; i < cbdata->n_rows; i++) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+            cbdata->counts[i]++;
+        }
+    }
+
+    return true;
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    struct do_query_cbdata cbdata;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int exit_code = 0;
+    size_t i;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    cbdata.n_rows = json->u.array.n;
+    cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+    cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+    for (i = 0; i < cbdata.n_rows; i++) {
+        struct ovsdb_row *row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+        ovsdb_table_put_row(table, row);
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_condition cnd;
+        size_t j;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+        ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+        printf("query %2d:", i);
+        for (j = 0; j < cbdata.n_rows; j++) {
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (cbdata.counts[j]) {
+                printf("%d", cbdata.counts[j]);
+                if (cbdata.counts[j] > 1) {
+                    /* Dup! */
+                    exit_code = 1;
+                }
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+struct do_query_distinct_class {
+    struct ovsdb_row *example;
+    int count;
+};
+
+struct do_query_distinct_row {
+    struct uuid uuid;
+    struct do_query_distinct_class *class;
+};
+
+static void
+do_query_distinct(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column_set columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct do_query_distinct_row *rows;
+    size_t n_rows;
+    struct do_query_distinct_class *classes;
+    size_t n_classes;
+    struct json *json;
+    int exit_code = 0;
+    size_t i, j, k;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse column set. */
+    json = parse_json(argv[4]);
+    ovsdb_column_set_from_json(json, table, &columns);
+    json_destroy(json);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    classes = xmalloc(n_rows * sizeof *classes);
+    n_classes = 0;
+    for (i = 0; i < n_rows; i++) {
+        struct ovsdb_row *row;
+        size_t j;
+
+        /* Parse row. */
+        row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+
+        /* Initialize row and find equivalence class. */
+        rows[i].uuid = *ovsdb_row_get_uuid(row);
+        rows[i].class = NULL;
+        for (j = 0; j < n_classes; j++) {
+            if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+                rows[i].class = &classes[j];
+                break;
+            }
+        }
+        if (!rows[i].class) {
+            rows[i].class = &classes[n_classes];
+            classes[n_classes].example = ovsdb_row_clone(row);
+            n_classes++;
+        }
+
+        /* Add row to table. */
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        ovsdb_table_put_row(table, row);
+
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_row_set results;
+        struct ovsdb_condition cnd;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        for (j = 0; j < n_classes; j++) {
+            classes[j].count = 0;
+        }
+        ovsdb_row_set_init(&results);
+        ovsdb_query_distinct(table, &cnd, &columns, &results);
+        for (j = 0; j < results.n_rows; j++) {
+            for (k = 0; k < n_rows; k++) {
+                if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+                                &rows[k].uuid)) {
+                    rows[k].class->count++;
+                }
+            }
+        }
+        ovsdb_row_set_destroy(&results);
+
+        printf("query %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            int count = rows[j].class->count;
+
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (count > 1) {
+                /* Dup! */
+                printf("%d", count);
+                exit_code = 1;
+            } else if (count == 1) {
+                putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+static void
+do_execute(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+    struct ovsdb *db;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(NULL, schema);
+
+    for (i = 2; i < argc; i++) {
+        struct json *params, *result;
+        char *s;
+
+        params = parse_json(argv[i]);
+        result = ovsdb_execute(db, params, 0, NULL);
+        s = json_to_string(result, JSSF_SORT);
+        printf("%s\n", s);
+        json_destroy(params);
+        json_destroy(result);
+    }
+
+    ovsdb_destroy(db);
+}
+
+struct test_trigger {
+    struct ovsdb_trigger trigger;
+    int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+    struct json *result;
+    char *s;
+
+    result = ovsdb_trigger_steal_result(&t->trigger);
+    s = json_to_string(result, JSSF_SORT);
+    printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+    json_destroy(result);
+    ovsdb_trigger_destroy(&t->trigger);
+    free(t);
+}
+
+static void
+do_trigger(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct list completions;
+    struct json *json;
+    struct ovsdb *db;
+    long long int now;
+    int number;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(NULL, schema);
+
+    list_init(&completions);
+    now = 0;
+    number = 0;
+    for (i = 2; i < argc; i++) {
+        struct json *params = parse_json(argv[i]);
+        if (params->type == JSON_ARRAY
+            && json_array(params)->n == 2
+            && json_array(params)->elems[0]->type == JSON_STRING
+            && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+            && json_array(params)->elems[1]->type == JSON_INTEGER) {
+            now += json_integer(json_array(params)->elems[1]);
+            json_destroy(params);
+        } else {
+            struct test_trigger *t = xmalloc(sizeof *t);
+            ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+            t->number = number++;
+            if (ovsdb_trigger_is_complete(&t->trigger)) {
+                do_trigger_dump(t, now, "immediate");
+            } else {
+                printf("t=%lld: new trigger %d\n", now, t->number);
+            }
+        }
+
+        ovsdb_trigger_run(db, now);
+        while (!list_is_empty(&completions)) {
+            do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+                                         struct test_trigger, trigger.node),
+                            now, "delayed");
+        }
+
+        ovsdb_trigger_wait(db, now);
+        poll_immediate_wake();
+        poll_block();
+    }
+
+    ovsdb_destroy(db);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+\f
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
+do_transact_commit(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_commit(do_transact_txn);
+    do_transact_txn = NULL;
+}
+
+static void
+do_transact_abort(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_abort(do_transact_txn);
+    do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+    uuid_zero(uuid);
+    uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+    const struct ovsdb_row *row;
+    struct uuid uuid;
+
+    uuid_from_integer(atoi(uuid_string), &uuid);
+    row = ovsdb_table_get_row(do_transact_table, &uuid);
+    if (!row) {
+        ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+                  UUID_ARGS(&uuid));
+    }
+    return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+                        int integer)
+{
+    if (integer != -1) {
+        const struct ovsdb_column *column;
+
+        column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                               column_name);
+        row->fields[column->index].keys[0].integer = integer;
+    }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+    const struct ovsdb_column *column;
+
+    column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                           column_name);
+    return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+                    const char *i_string, const char *j_string)
+{
+    do_transact_set_integer(row, "i", atoi(i_string));
+    do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
+do_transact_insert(int argc UNUSED, char *argv[] UNUSED)
+{
+    struct ovsdb_row *row;
+    struct uuid *uuid;
+
+    row = ovsdb_row_create(do_transact_table);
+
+    /* Set UUID. */
+    uuid = ovsdb_row_get_uuid_rw(row);
+    uuid_from_integer(atoi(argv[1]), uuid);
+    if (ovsdb_table_get_row(do_transact_table, uuid)) {
+        ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+                  UUID_ARGS(uuid));
+    }
+
+    do_transact_set_i_j(row, argv[2], argv[3]);
+
+    /* Insert row. */
+    ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
+do_transact_delete(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+    ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
+do_transact_modify(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row_ro;
+    struct ovsdb_row *row_rw;
+
+    row_ro = do_transact_find_row(argv[1]);
+    row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+    do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+    struct ovsdb_row *const *ap = a_;
+    struct ovsdb_row *const *bp = b_;
+
+    return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
+do_transact_print(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row **rows;
+    const struct ovsdb_row *row;
+    size_t n_rows;
+    size_t i;
+
+    n_rows = hmap_count(&do_transact_table->rows);
+    rows = xmalloc(n_rows * sizeof *rows);
+    i = 0;
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                   &do_transact_table->rows) {
+        rows[i++] = row;
+    }
+    assert(i == n_rows);
+
+    qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+    for (i = 0; i < n_rows; i++) {
+        printf("\n%"PRId32": i=%d, j=%d",
+               ovsdb_row_get_uuid(rows[i])->parts[3],
+               do_transact_get_integer(rows[i], "i"),
+               do_transact_get_integer(rows[i], "j"));
+    }
+
+    free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+    static const struct command do_transact_commands[] = {
+        { "commit", 0, 0, do_transact_commit },
+        { "abort", 0, 0, do_transact_abort },
+        { "insert", 2, 3, do_transact_insert },
+        { "delete", 1, 1, do_transact_delete },
+        { "modify", 2, 3, do_transact_modify },
+        { "print", 0, 0, do_transact_print },
+        { NULL, 0, 0, NULL },
+    };
+
+    struct ovsdb_schema *schema;
+    struct json *json;
+    int i;
+
+    /* Create table. */
+    json = parse_json("{\"name\": \"testdb\", "
+                      " \"tables\": "
+                      "  {\"mytable\": "
+                      "    {\"columns\": "
+                      "      {\"i\": {\"type\": \"integer\"}, "
+                      "       \"j\": {\"type\": \"integer\"}}}}}");
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    do_transact_db = ovsdb_create(NULL, schema);
+    do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+    assert(do_transact_table != NULL);
+
+    for (i = 1; i < argc; i++) {
+        struct json *command;
+        size_t n_args;
+        char **args;
+        int j;
+
+        command = parse_json(argv[i]);
+        if (command->type != JSON_ARRAY) {
+            ovs_fatal(0, "transaction %d must be JSON array "
+                      "with at least 1 element", i);
+        }
+
+        n_args = command->u.array.n;
+        args = xmalloc((n_args + 1) * sizeof *args);
+        for (j = 0; j < n_args; j++) {
+            struct json *s = command->u.array.elems[j];
+            if (s->type != JSON_STRING) {
+                ovs_fatal(0, "transaction %d argument %d must be JSON string",
+                          i, j);
+            }
+            args[j] = xstrdup(json_string(s));
+        }
+        args[n_args] = NULL;
+
+        if (!do_transact_txn) {
+            do_transact_txn = ovsdb_txn_create(do_transact_db);
+        }
+
+        for (j = 0; j < n_args; j++) {
+            if (j) {
+                putchar(' ');
+            }
+            fputs(args[j], stdout);
+        }
+        fputs(":", stdout);
+        run_command(n_args, args, do_transact_commands);
+        putchar('\n');
+
+        for (j = 0; j < n_args; j++) {
+            free(args[j]);
+        }
+        free(args);
+        json_destroy(command);
+    }
+    ovsdb_txn_abort(do_transact_txn);
+    ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static struct command all_commands[] = {
+    { "file-io", 2, INT_MAX, do_file_io },
+    { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+    { "parse-type", 1, 1, do_parse_type },
+    { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+    { "parse-data", 2, INT_MAX, do_parse_data },
+    { "sort-atoms", 2, 2, do_sort_atoms },
+    { "parse-column", 2, 2, do_parse_column },
+    { "parse-table", 2, 2, do_parse_table },
+    { "parse-rows", 2, INT_MAX, do_parse_rows },
+    { "compare-rows", 2, INT_MAX, do_compare_rows },
+    { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+    { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+    { "query", 3, 3, do_query },
+    { "query-distinct", 4, 4, do_query_distinct },
+    { "transact", 1, INT_MAX, do_transact },
+    { "execute", 2, INT_MAX, do_execute },
+    { "trigger", 2, INT_MAX, do_trigger },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
index 781b6a6ce4b2d0fe538d2dff89f646e0c4f81a34..9b47b8fbfc343cb2477c94e6d691d92dfcb6a902 100644 (file)
@@ -16,6 +16,7 @@ limitations under the License.])
 
 AT_TESTED([ovs-vswitchd])
 AT_TESTED([ovs-vsctl])
+AT_TESTED([perl])
 
 m4_include([tests/lcov-pre.at])
 m4_include([tests/library.at])
@@ -26,6 +27,7 @@ m4_include([tests/json.at])
 m4_include([tests/jsonrpc.at])
 m4_include([tests/timeval.at])
 m4_include([tests/lockfile.at])
+m4_include([tests/ovsdb.at])
 m4_include([tests/stp.at])
 m4_include([tests/ovs-vsctl.at])
 m4_include([tests/lcov-post.at])
diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl
new file mode 100755 (executable)
index 0000000..6f003a5
--- /dev/null
@@ -0,0 +1,21 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+our %uuids;
+our $n_uuids = 0;
+sub lookup_uuid {
+    my ($uuid) = @_;
+    if (!exists($uuids{$uuid})) {
+        $uuids{$uuid} = $n_uuids++;
+    }
+    return "<$uuids{$uuid}>";
+}
+
+my $u = '[0-9a-fA-F]';
+my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+while (<>) {
+    s/($uuid_re)/lookup_uuid($1)/eg;
+    print $_;
+}