/*
 * Copyright (c) 2015, 2016, 2017 Nicira, Inc.
 *
 * 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 <ctype.h>
#include <getopt.h>
#include <unistd.h>

#include "db-ctl-base.h"

#include "command-line.h"
#include "compiler.h"
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
#include "fatal-signal.h"
#include "hash.h"
#include "openvswitch/json.h"
#include "openvswitch/vlog.h"
#include "ovsdb-data.h"
#include "ovsdb-idl.h"
#include "ovsdb-idl-provider.h"
#include "openvswitch/shash.h"
#include "sset.h"
#include "svec.h"
#include "string.h"
#include "table.h"
#include "util.h"

VLOG_DEFINE_THIS_MODULE(db_ctl_base);

/* This array defines the 'show' command output format.  User can check the
 * definition in utilities/ovs-vsctl.c as reference.
 *
 * Particularly, if an element in 'columns[]' represents a reference to
 * another table, the referred table must also be defined as an entry in
 * in 'cmd_show_tables[]'.
 *
 * The definition must end with an all-NULL entry.  It is initalized once
 * when ctl_init() is called.
 *
 * */
static const struct cmd_show_table *cmd_show_tables;

/* ctl_exit() is called by ctl_fatal(). User can optionally supply an exit
 * function ctl_exit_func() via ctl_init. If supplied, this function will
 * be called by ctl_exit()
 */
static void (*ctl_exit_func)(int status) = NULL;
OVS_NO_RETURN static void ctl_exit(int status);

/* IDL class. */
static const struct ovsdb_idl_class *idl_class;

/* Two arrays with 'n_classes' elements, which represent the tables in this
 * database and how the user can refer to their rows. */
static const struct ctl_table_class *ctl_classes;
static const struct ovsdb_idl_table_class *idl_classes;
static size_t n_classes;

static struct shash all_commands = SHASH_INITIALIZER(&all_commands);
static char *get_table(const char *, const struct ovsdb_idl_table_class **);
static char *set_column(const struct ovsdb_idl_table_class *,
                        const struct ovsdb_idl_row *, const char *,
                        struct ovsdb_symbol_table *, bool use_partial_update);


static struct option *
find_option(const char *name, struct option *options, size_t n_options)
{
    size_t i;

    for (i = 0; i < n_options; i++) {
        if (!strcmp(options[i].name, name)) {
            return &options[i];
        }
    }
    return NULL;
}

static struct option *
add_option(struct option **optionsp, size_t *n_optionsp,
           size_t *allocated_optionsp)
{
    if (*n_optionsp >= *allocated_optionsp) {
        *optionsp = x2nrealloc(*optionsp, allocated_optionsp,
                               sizeof **optionsp);
    }
    return &(*optionsp)[(*n_optionsp)++];
}

/* Converts the command arguments into format that can be parsed by
 * bash completion script.
 *
 * Therein, arguments will be attached with following prefixes:
 *
 *    !argument :: The argument is required
 *    ?argument :: The argument is optional
 *    *argument :: The argument may appear any number (0 or more) times
 *    +argument :: The argument may appear one or more times
 *
 */
static void
print_command_arguments(const struct ctl_command_syntax *command)
{
    /*
     * The argument string is parsed in reverse.  We use a stack 'oew_stack' to
     * keep track of nested optionals.  Whenever a ']' is encountered, we push
     * a bit to 'oew_stack'.  The bit is set to 1 if the ']' is not nested.
     * Subsequently, we pop an entry everytime '[' is met.
     *
     * We use 'whole_word_is_optional' value to decide whether or not a ! or +
     * should be added on encountering a space: if the optional surrounds the
     * whole word then it shouldn't be, but if it is only a part of the word
     * (i.e. [key=]value), it should be.
     */
    uint32_t oew_stack = 0;

    const char *arguments = command->arguments;
    int length = strlen(arguments);
    if (!length) {
        return;
    }

    /* Output buffer, written backward from end. */
    char *output = xmalloc(2 * length);
    char *outp = output + 2 * length;
    *--outp = '\0';

    bool in_repeated = false;
    bool whole_word_is_optional = false;

    for (const char *inp = arguments + length; inp > arguments; ) {
        switch (*--inp) {
        case ']':
            oew_stack <<= 1;
            if (inp[1] == '\0' || inp[1] == ' ' || inp[1] == '.') {
                oew_stack |= 1;
            }
            break;
        case '[':
            /* Checks if the whole word is optional, and sets the
             * 'whole_word_is_optional' accordingly. */
            if ((inp == arguments || inp[-1] == ' ') && oew_stack & 1) {
                *--outp = in_repeated ? '*' : '?';
                whole_word_is_optional = true;
            } else {
                *--outp = '?';
                whole_word_is_optional = false;
            }
            oew_stack >>= 1;
            break;
        case ' ':
            if (!whole_word_is_optional) {
                *--outp = in_repeated ? '+' : '!';
            }
            *--outp = ' ';
            in_repeated = false;
            whole_word_is_optional = false;
            break;
        case '.':
            in_repeated = true;
            break;
        default:
            *--outp = *inp;
            break;
        }
    }
    if (arguments[0] != '[' && outp != output + 2 * length - 1) {
        *--outp = in_repeated ? '+' : '!';
    }
    printf("%s", outp);
    free(output);
}

static int
to_lower_and_underscores(unsigned c)
{
    return c == '-' ? '_' : tolower(c);
}

/* Returns a score representing how well 's' matches 'name'.  Higher return
 * values indicate a better match.  The order of the arguments is important:
 * 'name' is the name of an entity such as a table or a column, and 's' is user
 * input. */
static unsigned int
score_partial_match(const char *name, const char *s)
{
    int score;

    if (!strcmp(name, s)) {
        return UINT_MAX;
    }
    for (score = 0; ; score++, name++, s++) {
        if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) {
            break;
        } else if (*name == '\0') {
            return UINT_MAX - 1;
        }
    }
    return *s == '\0' ? score : 0;
}

/* Returns NULL and sets 'symbolp' and 'newp' if symbol was created
 * successfully. Otherwise returns a malloc()'ed error message on failure. */
static char * OVS_WARN_UNUSED_RESULT
create_symbol(struct ovsdb_symbol_table *symtab, const char *id,
              struct ovsdb_symbol **symbolp, bool *newp)
{
    struct ovsdb_symbol *symbol;

    ovs_assert(symbolp);

    if (id[0] != '@') {
        return xasprintf("row id \"%s\" does not begin with \"@\"", id);
    }

    if (newp) {
        *newp = ovsdb_symbol_table_get(symtab, id) == NULL;
    }

    symbol = ovsdb_symbol_table_insert(symtab, id);
    if (symbol->created) {
        return xasprintf("row id \"%s\" may only be specified on one --id "
                         "option", id);
    }
    symbol->created = true;
    *symbolp = symbol;
    return NULL;
}

static bool
record_id_equals(const union ovsdb_atom *name, enum ovsdb_atomic_type type,
                 const char *record_id)
{
    if (type == OVSDB_TYPE_STRING) {
        if (!strcmp(name->s->string, record_id)) {
            return true;
        }

        struct uuid uuid;
        size_t len = strlen(record_id);
        if (len >= 4
            && uuid_from_string(&uuid, name->s->string)
            && !strncmp(name->s->string, record_id, len)) {
            return true;
        }

        return false;
    } else {
        ovs_assert(type == OVSDB_TYPE_INTEGER);
        return name->integer == strtoll(record_id, NULL, 10);
    }
}

static const struct ovsdb_idl_row *
get_row_by_id(struct ctl_context *ctx,
              const struct ovsdb_idl_table_class *table,
              const struct ctl_row_id *id, const char *record_id,
              bool *multiple_matches)
{
    ovs_assert(multiple_matches);
    *multiple_matches = false;

    if (!id->name_column) {
        return NULL;
    }

    const struct ovsdb_idl_row *referrer = NULL;

    /* Figure out the 'key' and 'value' types for the column that we're going
     * to look at.  One of these ('name_type') is the type of the name we're
     * going to compare against 'record_id'.  */
    enum ovsdb_atomic_type key, value, name_type;
    if (!id->key) {
        name_type = key = id->name_column->type.key.type;
        value = OVSDB_TYPE_VOID;
    } else {
        key = OVSDB_TYPE_STRING;
        name_type = value = id->name_column->type.value.type;
    }

    /* We only support integer and string names (so far). */
    if (name_type == OVSDB_TYPE_INTEGER) {
        if (!record_id[0] || record_id[strspn(record_id, "0123456789")]) {
            return NULL;
        }
    } else {
        ovs_assert(name_type == OVSDB_TYPE_STRING);
    }

    const struct ovsdb_idl_class *class = ovsdb_idl_get_class(ctx->idl);
    const struct ovsdb_idl_table_class *id_table
        = ovsdb_idl_table_class_from_column(class, id->name_column);
    for (const struct ovsdb_idl_row *row = ovsdb_idl_first_row(ctx->idl,
                                                               id_table);
         row != NULL;
         row = ovsdb_idl_next_row(row)) {
        /* Pick out the name column's data. */
        const struct ovsdb_datum *datum = ovsdb_idl_get(
            row, id->name_column, key, value);

        /* Extract the name from the column. */
        const union ovsdb_atom *name = NULL;
        if (!id->key) {
            name = datum->n == 1 ? &datum->keys[0] : NULL;
        } else {
            union ovsdb_atom key_atom = {
                .s = ovsdb_atom_string_create(CONST_CAST(char *, id->key)) };
            unsigned int i;

            if (ovsdb_datum_find_key(datum, &key_atom,
                                     OVSDB_TYPE_STRING, &i)) {
                name = &datum->values[i];
            }
            ovsdb_atom_destroy(&key_atom, OVSDB_TYPE_STRING);
        }
        if (!name) {
            continue;
        }

        /* If the name equals 'record_id', take it. */
        if (record_id_equals(name, name_type, record_id)) {
            if (referrer) {
                *multiple_matches = true;
                return NULL;
            }
            referrer = row;
        }
    }
    if (!referrer) {
        return NULL;
    }

    const struct ovsdb_idl_row *final = referrer;
    if (id->uuid_column) {
        const struct ovsdb_datum *uuid;

        ovsdb_idl_txn_verify(referrer, id->uuid_column);
        uuid = ovsdb_idl_get(referrer, id->uuid_column,
                             OVSDB_TYPE_UUID, OVSDB_TYPE_VOID);
        if (uuid->n == 1) {
            final = ovsdb_idl_get_row_for_uuid(ctx->idl, table,
                                               &uuid->keys[0].uuid);
        } else {
            final = NULL;
        }
    }
    return final;
}

char * OVS_WARN_UNUSED_RESULT
ctl_get_row(struct ctl_context *ctx,
            const struct ovsdb_idl_table_class *table, const char *record_id,
            bool must_exist, const struct ovsdb_idl_row **rowp)
{
    const struct ovsdb_idl_row *row = NULL;
    struct uuid uuid;

    ovs_assert(rowp);

    if (uuid_from_string(&uuid, record_id)) {
        row = ovsdb_idl_get_row_for_uuid(ctx->idl, table, &uuid);
    }
    if (!row) {
        if (!strcmp(record_id, ".")) {
            row = ovsdb_idl_first_row(ctx->idl, table);
            if (row && ovsdb_idl_next_row(row)) {
                row = NULL;
            }
        }
    }
    if (!row) {
        const struct ctl_table_class *ctl_class
            = &ctl_classes[table - idl_classes];
        for (int i = 0; i < ARRAY_SIZE(ctl_class->row_ids); i++) {
            const struct ctl_row_id *id = &ctl_class->row_ids[i];
            bool multiple_matches;

            row = get_row_by_id(ctx, table, id, record_id, &multiple_matches);
            if (multiple_matches) {
                const struct ovsdb_idl_class *class =
                    ovsdb_idl_get_class(ctx->idl);
                const struct ovsdb_idl_table_class *table_class =
                    ovsdb_idl_table_class_from_column(class, id->name_column);
                return xasprintf("multiple rows in %s match \"%s\"",
                                 table_class->name, record_id);
            }
            if (row) {
                break;
            }
        }
    }
    if (!row && uuid_is_partial_string(record_id) >= 4) {
        for (const struct ovsdb_idl_row *r = ovsdb_idl_first_row(ctx->idl,
                                                                 table);
             r != NULL;
             r = ovsdb_idl_next_row(r)) {
            if (uuid_is_partial_match(&r->uuid, record_id)) {
                if (!row) {
                    row = r;
                } else {
                    return xasprintf("%s contains 2 or more rows whose UUIDs "
                                     "begin with %s: at least "UUID_FMT" "
                                     "and "UUID_FMT, table->name, record_id,
                                     UUID_ARGS(&row->uuid),
                                     UUID_ARGS(&r->uuid));
                }
            }
        }
    }
    if (must_exist && !row) {
        return xasprintf("no row \"%s\" in table %s", record_id, table->name);
    }

    *rowp = row;
    return NULL;
}

static char *
get_column(const struct ovsdb_idl_table_class *table, const char *column_name,
           const struct ovsdb_idl_column **columnp)
{
    struct sset best_matches = SSET_INITIALIZER(&best_matches);
    const struct ovsdb_idl_column *best_match = NULL;
    unsigned int best_score = 0;

    for (size_t i = 0; i < table->n_columns; i++) {
        const struct ovsdb_idl_column *column = &table->columns[i];
        unsigned int score = score_partial_match(column->name, column_name);
        if (score && score >= best_score) {
            if (score > best_score) {
                sset_clear(&best_matches);
            }
            sset_add(&best_matches, column->name);
            best_match = column;
            best_score = score;
        }
    }

    char *error = NULL;
    *columnp = NULL;
    if (!best_match) {
        error = xasprintf("%s does not contain a column whose name matches "
                          "\"%s\"", table->name, column_name);
    } else if (sset_count(&best_matches) == 1) {
        *columnp = best_match;
    } else {
        char *matches = sset_join(&best_matches, ", ", "");
        error = xasprintf("%s contains more than one column "
                          "whose name matches \"%s\": %s",
                          table->name, column_name, matches);
        free(matches);
    }
    sset_destroy(&best_matches);
    return error;
}

static char * OVS_WARN_UNUSED_RESULT
pre_get_column(struct ctl_context *ctx,
               const struct ovsdb_idl_table_class *table,
               const char *column_name,
               const struct ovsdb_idl_column **columnp)
{
    char *error = get_column(table, column_name, columnp);
    if (error) {
        return error;
    }
    ovsdb_idl_add_column(ctx->idl, *columnp);
    return NULL;
}

static char * OVS_WARN_UNUSED_RESULT
pre_get_table(struct ctl_context *ctx, const char *table_name,
              const struct ovsdb_idl_table_class **tablep)
{
    const struct ovsdb_idl_table_class *table;
    char *error = get_table(table_name, &table);
    if (error) {
        return error;
    }
    ovsdb_idl_add_table(ctx->idl, table);

    const struct ctl_table_class *ctl = &ctl_classes[table - idl_classes];
    for (int i = 0; i < ARRAY_SIZE(ctl->row_ids); i++) {
        const struct ctl_row_id *id = &ctl->row_ids[i];
        if (id->name_column) {
            ovsdb_idl_add_column(ctx->idl, id->name_column);
        }
        if (id->uuid_column) {
            ovsdb_idl_add_column(ctx->idl, id->uuid_column);
        }
    }

    if (tablep) {
        *tablep = table;
    }
    return NULL;
}

static char *
missing_operator_error(const char *arg, const char **allowed_operators,
                       size_t n_allowed)
{
    struct ds s;

    ds_init(&s);
    ds_put_format(&s, "%s: argument does not end in ", arg);
    ds_put_format(&s, "\"%s\"", allowed_operators[0]);
    if (n_allowed == 2) {
        ds_put_format(&s, " or \"%s\"", allowed_operators[1]);
    } else if (n_allowed > 2) {
        size_t i;

        for (i = 1; i < n_allowed - 1; i++) {
            ds_put_format(&s, ", \"%s\"", allowed_operators[i]);
        }
        ds_put_format(&s, ", or \"%s\"", allowed_operators[i]);
    }
    ds_put_format(&s, " followed by a value.");

    return ds_steal_cstr(&s);
}

/* Breaks 'arg' apart into a number of fields in the following order:
 *
 *      - The name of a column in 'table', stored into '*columnp'.  The column
 *        name may be abbreviated.
 *
 *      - Optionally ':' followed by a key string.  The key is stored as a
 *        malloc()'d string into '*keyp', or NULL if no key is present in
 *        'arg'.
 *
 *      - If 'valuep' is nonnull, an operator followed by a value string.  The
 *        allowed operators are the 'n_allowed' string in 'allowed_operators',
 *        or just "=" if 'n_allowed' is 0.  If 'operatorp' is nonnull, then the
 *        index of the operator within 'allowed_operators' is stored into
 *        '*operatorp'.  The value is stored as a malloc()'d string into
 *        '*valuep', or NULL if no value is present in 'arg'.
 *
 * On success, returns NULL.  On failure, returned a malloc()'d string error
 * message and stores NULL into all of the nonnull output arguments. */
static char * OVS_WARN_UNUSED_RESULT
parse_column_key_value(const char *arg,
                       const struct ovsdb_idl_table_class *table,
                       const struct ovsdb_idl_column **columnp, char **keyp,
                       int *operatorp,
                       const char **allowed_operators, size_t n_allowed,
                       char **valuep)
{
    const char *p = arg;
    char *column_name;
    char *error;

    ovs_assert(!(operatorp && !valuep));
    *keyp = NULL;
    if (valuep) {
        *valuep = NULL;
    }

    /* Parse column name. */
    error = ovsdb_token_parse(&p, &column_name);
    if (error) {
        goto error;
    }
    if (column_name[0] == '\0') {
        free(column_name);
        error = xasprintf("%s: missing column name", arg);
        goto error;
    }
    error = get_column(table, column_name, columnp);
    free(column_name);
    if (error) {
        goto error;
    }

    /* Parse key string. */
    if (*p == ':') {
        p++;
        error = ovsdb_token_parse(&p, keyp);
        if (error) {
            goto error;
        }
    }

    /* Parse value string. */
    if (valuep) {
        size_t best_len;
        size_t i;
        int best;

        if (!allowed_operators) {
            static const char *equals = "=";
            allowed_operators = &equals;
            n_allowed = 1;
        }

        best = -1;
        best_len = 0;
        for (i = 0; i < n_allowed; i++) {
            const char *op = allowed_operators[i];
            size_t op_len = strlen(op);

            if (op_len > best_len && !strncmp(op, p, op_len) && p[op_len]) {
                best_len = op_len;
                best = i;
            }
        }
        if (best < 0) {
            error = missing_operator_error(arg, allowed_operators, n_allowed);
            goto error;
        }

        if (operatorp) {
            *operatorp = best;
        }
        *valuep = xstrdup(p + best_len);
    } else {
        if (*p != '\0') {
            error = xasprintf("%s: trailing garbage \"%s\" in argument",
                              arg, p);
            goto error;
        }
    }
    return NULL;

 error:
    *columnp = NULL;
    free(*keyp);
    *keyp = NULL;
    if (valuep) {
        free(*valuep);
        *valuep = NULL;
        if (operatorp) {
            *operatorp = -1;
        }
    }
    return error;
}

static char * OVS_WARN_UNUSED_RESULT
pre_parse_column_key_value(struct ctl_context *ctx, const char *arg,
                           const struct ovsdb_idl_table_class *table)
{
    const struct ovsdb_idl_column *column;
    const char *p;
    char *column_name = NULL;
    char *error;

    p = arg;
    error = ovsdb_token_parse(&p, &column_name);
    if (error) {
        goto out;
    }
    if (column_name[0] == '\0') {
        error = xasprintf("%s: missing column name", arg);
        goto out;
    }

    error = pre_get_column(ctx, table, column_name, &column);
out:
    free(column_name);

    return error;
}

/* Checks if the 'column' is mutable. Returns NULL if it is mutable, or a
 * malloc()'ed error message otherwise. */
static char * OVS_WARN_UNUSED_RESULT
check_mutable(const struct ovsdb_idl_row *row,
              const struct ovsdb_idl_column *column)
{
    if (!ovsdb_idl_is_mutable(row, column)) {
        return xasprintf("cannot modify read-only column %s in table %s",
                         column->name, row->table->class_->name);
    }
    return NULL;
}

#define RELOPS                                  \
    RELOP(RELOP_EQ,     "=")                    \
    RELOP(RELOP_NE,     "!=")                   \
    RELOP(RELOP_LT,     "<")                    \
    RELOP(RELOP_GT,     ">")                    \
    RELOP(RELOP_LE,     "<=")                   \
    RELOP(RELOP_GE,     ">=")                   \
    RELOP(RELOP_SET_EQ, "{=}")                  \
    RELOP(RELOP_SET_NE, "{!=}")                 \
    RELOP(RELOP_SET_LT, "{<}")                  \
    RELOP(RELOP_SET_GT, "{>}")                  \
    RELOP(RELOP_SET_LE, "{<=}")                 \
    RELOP(RELOP_SET_GE, "{>=}")                 \
    RELOP(RELOP_SET_IN, "{in}")                 \
    RELOP(RELOP_SET_NOT_IN, "{not-in}")

enum relop {
#define RELOP(ENUM, STRING) ENUM,
    RELOPS
#undef RELOP
};

static bool
is_set_operator(enum relop op)
{
    return (op == RELOP_SET_EQ || op == RELOP_SET_NE ||
            op == RELOP_SET_LT || op == RELOP_SET_GT ||
            op == RELOP_SET_LE || op == RELOP_SET_GE ||
            op == RELOP_SET_IN || op == RELOP_SET_NOT_IN);
}

static bool
evaluate_relop(const struct ovsdb_datum *a, const struct ovsdb_datum *b,
               const struct ovsdb_type *type, enum relop op)
{
    switch (op) {
    case RELOP_EQ:
    case RELOP_SET_EQ:
        return ovsdb_datum_compare_3way(a, b, type) == 0;
    case RELOP_NE:
    case RELOP_SET_NE:
        return ovsdb_datum_compare_3way(a, b, type) != 0;
    case RELOP_LT:
        return ovsdb_datum_compare_3way(a, b, type) < 0;
    case RELOP_GT:
        return ovsdb_datum_compare_3way(a, b, type) > 0;
    case RELOP_LE:
        return ovsdb_datum_compare_3way(a, b, type) <= 0;
    case RELOP_GE:
        return ovsdb_datum_compare_3way(a, b, type) >= 0;

    case RELOP_SET_LT:
        return b->n > a->n && ovsdb_datum_includes_all(a, b, type);
    case RELOP_SET_GT:
        return a->n > b->n && ovsdb_datum_includes_all(b, a, type);
    case RELOP_SET_LE:
    case RELOP_SET_IN:
        return ovsdb_datum_includes_all(a, b, type);
    case RELOP_SET_GE:
        return ovsdb_datum_includes_all(b, a, type);
    case RELOP_SET_NOT_IN:
        return ovsdb_datum_excludes_all(a, b, type);

    default:
        OVS_NOT_REACHED();
    }
}

/* Checks if given row satisfies the specified condition. Returns the result of
 * evaluating the condition in 'satisfied' flag and NULL as a return value on
 * success. On failure returns a malloc()'ed error message and 'satisfied'
 * value is not modified. */
static char * OVS_WARN_UNUSED_RESULT
check_condition(const struct ovsdb_idl_table_class *table,
                const struct ovsdb_idl_row *row, const char *arg,
                struct ovsdb_symbol_table *symtab, bool *satisfied)
{
    static const char *operators[] = {
#define RELOP(ENUM, STRING) STRING,
        RELOPS
#undef RELOP
    };

    const struct ovsdb_idl_column *column;
    const struct ovsdb_datum *have_datum;
    char *key_string = NULL;
    char *value_string = NULL;
    struct ovsdb_type type;
    int operator;
    bool retval;
    char *error;

    ovs_assert(satisfied);

    error = parse_column_key_value(arg, table, &column, &key_string,
                                   &operator, operators, ARRAY_SIZE(operators),
                                   &value_string);
    if (error) {
        goto out;
    }
    if (!value_string) {
        error = xasprintf("%s: missing value", arg);
        goto out;
    }

    type = column->type;
    type.n_max = UINT_MAX;

    have_datum = ovsdb_idl_read(row, column);
    if (key_string) {
        union ovsdb_atom want_key;
        struct ovsdb_datum b;
        unsigned int idx;

        if (column->type.value.type == OVSDB_TYPE_VOID) {
            error = xasprintf("cannot specify key to check for non-map column "
                              "%s", column->name);
            goto out;
        }

        error = ovsdb_atom_from_string(&want_key, NULL, &column->type.key,
                                       key_string, symtab);
        if (error) {
            goto out;
        }

        type.key = type.value;
        type.value.type = OVSDB_TYPE_VOID;
        error = ovsdb_datum_from_string(&b, &type, value_string, symtab);
        if (error) {
            goto out;
        }

        bool found = ovsdb_datum_find_key(have_datum, &want_key,
                                          column->type.key.type, &idx);
        if (!found && !is_set_operator(operator)) {
            retval = false;
        } else {
            struct ovsdb_datum a;

            if (found) {
                a.n = 1;
                a.keys = &have_datum->values[idx];
                a.values = NULL;
            } else {
                a.n = 0;
                a.keys = NULL;
                a.values = NULL;
            }

            retval = evaluate_relop(&a, &b, &type, operator);
        }

        ovsdb_atom_destroy(&want_key, column->type.key.type);
        ovsdb_datum_destroy(&b, &type);
    } else {
        struct ovsdb_datum want_datum;

        error = ovsdb_datum_from_string(&want_datum, &column->type,
                                        value_string, symtab);
        if (error) {
            goto out;
        }
        retval = evaluate_relop(have_datum, &want_datum, &type, operator);
        ovsdb_datum_destroy(&want_datum, &column->type);
    }

    *satisfied = retval;
out:
    free(key_string);
    free(value_string);

    return error;
}

static void
invalidate_cache(struct ctl_context *ctx)
{
    if (ctx->invalidate_cache_cb) {
        (ctx->invalidate_cache_cb)(ctx);
    }
}

static void
pre_cmd_get(struct ctl_context *ctx)
{
    const char *id = shash_find_data(&ctx->options, "--id");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    /* Using "get" without --id or a column name could possibly make sense.
     * Maybe, for example, a *ctl command run wants to assert that a row
     * exists.  But it is unlikely that an interactive user would want to do
     * that, so issue a warning if we're running on a terminal. */
    if (!id && ctx->argc <= 3 && isatty(STDOUT_FILENO)) {
        VLOG_WARN("\"get\" command without row arguments or \"--id\" is "
                  "possibly erroneous");
    }

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    for (i = 3; i < ctx->argc; i++) {
        if (!strcasecmp(ctx->argv[i], "_uuid")
            || !strcasecmp(ctx->argv[i], "-uuid")) {
            continue;
        }

        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
        if (ctx->error) {
            return;
        }
    }
}

static void
cmd_get(struct ctl_context *ctx)
{
    const char *id = shash_find_data(&ctx->options, "--id");
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    struct ds *out = &ctx->output;
    int i;

    if (id && !must_exist) {
        ctl_error(ctx, "--if-exists and --id may not be specified together");
        return;
    }

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        return;
    }

    if (id) {
        struct ovsdb_symbol *symbol = NULL;
        bool new = false;

        ctx->error = create_symbol(ctx->symtab, id, &symbol, &new);
        if (ctx->error) {
            return;
        }
        if (!new) {
            ctl_error(ctx, "row id \"%s\" specified on \"get\" command was "
                      "used before it was defined", id);
            return;
        }
        symbol->uuid = row->uuid;

        /* This symbol refers to a row that already exists, so disable warnings
         * about it being unreferenced. */
        symbol->strong_ref = true;
    }
    for (i = 3; i < ctx->argc; i++) {
        const struct ovsdb_idl_column *column;
        const struct ovsdb_datum *datum;
        char *key_string;

        /* Special case for obtaining the UUID of a row.  We can't just do this
         * through parse_column_key_value() below since it returns a "struct
         * ovsdb_idl_column" and the UUID column doesn't have one. */
        if (!strcasecmp(ctx->argv[i], "_uuid")
            || !strcasecmp(ctx->argv[i], "-uuid")) {
            ds_put_format(out, UUID_FMT"\n", UUID_ARGS(&row->uuid));
            continue;
        }

        ctx->error = parse_column_key_value(ctx->argv[i], table, &column,
                                            &key_string, NULL, NULL, 0, NULL);
        if (ctx->error) {
            return;
        }

        ovsdb_idl_txn_verify(row, column);
        datum = ovsdb_idl_read(row, column);
        if (key_string) {
            union ovsdb_atom key;
            unsigned int idx;

            if (column->type.value.type == OVSDB_TYPE_VOID) {
                ctl_error(ctx,
                          "cannot specify key to get for non-map column %s",
                          column->name);
                free(key_string);
                return;
            }

            ctx->error = ovsdb_atom_from_string(&key, NULL, &column->type.key,
                                                key_string, ctx->symtab);
            if (ctx->error) {
                free(key_string);
                return;
            }

            if (!ovsdb_datum_find_key(datum, &key,
                                      column->type.key.type, &idx)) {
                if (must_exist) {
                    ctl_error(
                        ctx, "no key \"%s\" in %s record \"%s\" column %s",
                        key_string, table->name, record_id, column->name);
                    free(key_string);
                    ovsdb_atom_destroy(&key, column->type.key.type);
                    return;
                }
            } else {
                ovsdb_atom_to_string(&datum->values[idx],
                                     column->type.value.type, out);
            }
            ovsdb_atom_destroy(&key, column->type.key.type);
        } else {
            ovsdb_datum_to_string(datum, &column->type, out);
        }
        ds_put_char(out, '\n');

        free(key_string);
    }
}

/* Returns NULL on success or malloc()'ed error message on failure. */
static char * OVS_WARN_UNUSED_RESULT
parse_column_names(const char *column_names,
                   const struct ovsdb_idl_table_class *table,
                   const struct ovsdb_idl_column ***columnsp,
                   size_t *n_columnsp)
{
    const struct ovsdb_idl_column **columns;
    size_t n_columns;

    if (!column_names) {
        size_t i;

        n_columns = table->n_columns + 1;
        columns = xmalloc(n_columns * sizeof *columns);
        columns[0] = NULL;
        for (i = 0; i < table->n_columns; i++) {
            columns[i + 1] = &table->columns[i];
        }
    } else {
        char *s = xstrdup(column_names);
        size_t allocated_columns;
        char *save_ptr = NULL;
        char *column_name;

        columns = NULL;
        allocated_columns = n_columns = 0;
        for (column_name = strtok_r(s, ", ", &save_ptr); column_name;
             column_name = strtok_r(NULL, ", ", &save_ptr)) {
            const struct ovsdb_idl_column *column;

            if (!strcasecmp(column_name, "_uuid")) {
                column = NULL;
            } else {
                char *error = get_column(table, column_name, &column);
                if (error) {
                    free(columns);
                    free(s);
                    return error;
                }
            }
            if (n_columns >= allocated_columns) {
                columns = x2nrealloc(columns, &allocated_columns,
                                     sizeof *columns);
            }
            columns[n_columns++] = column;
        }
        free(s);

        if (!n_columns) {
            return xstrdup("must specify at least one column name");
        }
    }
    *columnsp = columns;
    *n_columnsp = n_columns;
    return NULL;
}

static char * OVS_WARN_UNUSED_RESULT
pre_list_columns(struct ctl_context *ctx,
                 const struct ovsdb_idl_table_class *table,
                 const char *column_names)
{
    const struct ovsdb_idl_column **columns;
    size_t n_columns;
    size_t i;
    char *error;

    error = parse_column_names(column_names, table, &columns, &n_columns);
    if (error) {
        return error;
    }
    for (i = 0; i < n_columns; i++) {
        if (columns[i]) {
            ovsdb_idl_add_column(ctx->idl, columns[i]);
        }
    }
    free(columns);
    return NULL;
}

static void
pre_cmd_list(struct ctl_context *ctx)
{
    const char *column_names = shash_find_data(&ctx->options, "--columns");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = pre_list_columns(ctx, table, column_names);
    if (ctx->error) {
        return;
    }
}

static struct table *
list_make_table(const struct ovsdb_idl_column **columns, size_t n_columns)
{
    struct table *out;
    size_t i;

    out = xmalloc(sizeof *out);
    table_init(out);

    for (i = 0; i < n_columns; i++) {
        const struct ovsdb_idl_column *column = columns[i];
        const char *column_name = column ? column->name : "_uuid";

        table_add_column(out, "%s", column_name);
    }

    return out;
}

static void
list_record(const struct ovsdb_idl_row *row,
            const struct ovsdb_idl_column **columns, size_t n_columns,
            struct table *out)
{
    size_t i;

    if (!row) {
        return;
    }

    table_add_row(out);
    for (i = 0; i < n_columns; i++) {
        const struct ovsdb_idl_column *column = columns[i];
        struct cell *cell = table_add_cell(out);

        if (!column) {
            struct ovsdb_datum datum;
            union ovsdb_atom atom;

            atom.uuid = row->uuid;

            datum.keys = &atom;
            datum.values = NULL;
            datum.n = 1;

            cell->json = ovsdb_datum_to_json(&datum, &ovsdb_type_uuid);
            cell->type = &ovsdb_type_uuid;
        } else {
            const struct ovsdb_datum *datum = ovsdb_idl_read(row, column);

            cell->json = ovsdb_datum_to_json(datum, &column->type);
            cell->type = &column->type;
        }
    }
}

static void
cmd_list(struct ctl_context *ctx)
{
    const char *column_names = shash_find_data(&ctx->options, "--columns");
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const struct ovsdb_idl_column **columns;
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    struct table *out;
    size_t n_columns;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = parse_column_names(column_names, table, &columns, &n_columns);
    if (ctx->error) {
        return;
    }
    out = ctx->table = list_make_table(columns, n_columns);
    if (ctx->argc > 2) {
        for (i = 2; i < ctx->argc; i++) {
            const struct ovsdb_idl_row *row;

            ctx->error = ctl_get_row(ctx, table, ctx->argv[i], must_exist,
                                     &row);
            if (ctx->error) {
                free(columns);
                return;
            }
            list_record(row, columns, n_columns, out);
        }
    } else {
        const struct ovsdb_idl_row *row;

        for (row = ovsdb_idl_first_row(ctx->idl, table); row != NULL;
             row = ovsdb_idl_next_row(row)) {
            list_record(row, columns, n_columns, out);
        }
    }
    free(columns);
}

/* Finds the "struct ovsdb_idl_table_class *" with 'table_name' by searching
 * the tables in these schema. Returns NULL and sets 'tablep' on success, or a
 * malloc()'ed error message on failure. */
static char * OVS_WARN_UNUSED_RESULT
get_table(const char *table_name, const struct ovsdb_idl_table_class **tablep)
{
    struct sset best_matches = SSET_INITIALIZER(&best_matches);
    const struct ovsdb_idl_table_class *best_match = NULL;
    unsigned int best_score = 0;

    for (const struct ovsdb_idl_table_class *table = idl_classes;
         table < &idl_classes[n_classes]; table++) {
        unsigned int score = score_partial_match(table->name, table_name);
        if (score && score >= best_score) {
            if (score > best_score) {
                sset_clear(&best_matches);
            }
            sset_add(&best_matches, table->name);
            best_match = table;
            best_score = score;
        }
    }

    char *error = NULL;
    if (!best_match) {
        error = xasprintf("unknown table \"%s\"", table_name);
    } else if (sset_count(&best_matches) == 1) {
        *tablep = best_match;
    } else {
        char *matches = sset_join(&best_matches, ", ", "");
        error = xasprintf("\"%s\" matches multiple table names: %s",
                          table_name, matches);
        free(matches);
    }
    sset_destroy(&best_matches);
    return error;
}

static void
pre_cmd_find(struct ctl_context *ctx)
{
    const char *column_names = shash_find_data(&ctx->options, "--columns");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = pre_list_columns(ctx, table, column_names);
    if (ctx->error) {
        return;
    }
    for (i = 2; i < ctx->argc; i++) {
        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
        if (ctx->error) {
            return;
        }
    }
}

static void
cmd_find(struct ctl_context *ctx)
{
    const char *column_names = shash_find_data(&ctx->options, "--columns");
    const struct ovsdb_idl_column **columns;
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    struct table *out;
    size_t n_columns;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = parse_column_names(column_names, table, &columns, &n_columns);
    if (ctx->error) {
        return;
    }
    out = ctx->table = list_make_table(columns, n_columns);
    for (row = ovsdb_idl_first_row(ctx->idl, table); row;
         row = ovsdb_idl_next_row(row)) {
        int i;

        for (i = 2; i < ctx->argc; i++) {
            bool satisfied = false;

            ctx->error = check_condition(table, row, ctx->argv[i],
                                         ctx->symtab, &satisfied);
            if (ctx->error) {
                free(columns);
                return;
            }
            if (!satisfied) {
                goto next_row;
            }
        }
        list_record(row, columns, n_columns, out);

    next_row: ;
    }
    free(columns);
}

/* Sets the column of 'row' in 'table'. Returns NULL on success or a
 * malloc()'ed error message on failure.
 *
 * If 'use_partial_update' is true, then this function will try to use
 * partial set/map updates, if possible.  As a side effect, result will
 * not be reflected in the IDL until the transaction is committed.
 * The last access to a particular column is a good candidate to use
 * this option. */
static char * OVS_WARN_UNUSED_RESULT
set_column(const struct ovsdb_idl_table_class *table,
           const struct ovsdb_idl_row *row, const char *arg,
           struct ovsdb_symbol_table *symtab, bool use_partial_update)
{
    const struct ovsdb_idl_column *column;
    char *key_string = NULL;
    char *value_string = NULL;
    char *error;

    error = parse_column_key_value(arg, table, &column, &key_string,
                                   NULL, NULL, 0, &value_string);
    if (error) {
        goto out;
    }
    if (!value_string) {
        error = xasprintf("%s: missing value", arg);
        goto out;
    }
    error = check_mutable(row, column);
    if (error) {
        goto out;
    }

    if (key_string) {
        union ovsdb_atom key, value;
        struct ovsdb_datum *datum;

        if (column->type.value.type == OVSDB_TYPE_VOID) {
            error = xasprintf("cannot specify key to set for non-map column "
                              "%s", column->name);
            goto out;
        }

        error = ovsdb_atom_from_string(&key, NULL, &column->type.key,
                                       key_string, symtab);
        if (error) {
            goto out;
        }
        error = ovsdb_atom_from_string(&value, NULL, &column->type.value,
                                       value_string, symtab);
        if (error) {
            goto out;
        }

        datum = xmalloc(sizeof *datum);
        ovsdb_datum_init_empty(datum);
        ovsdb_datum_add_unsafe(datum, &key, &value, &column->type, NULL);

        ovsdb_atom_destroy(&key, column->type.key.type);
        ovsdb_atom_destroy(&value, column->type.value.type);

        if (use_partial_update) {
            ovsdb_idl_txn_write_partial_map(row, column, datum);
        } else {
            ovsdb_datum_union(datum, ovsdb_idl_read(row, column),
                              &column->type);
            ovsdb_idl_txn_verify(row, column);
            ovsdb_idl_txn_write(row, column, datum);
            free(datum);
        }
    } else {
        struct ovsdb_datum datum;

        error = ovsdb_datum_from_string(&datum, &column->type,
                                        value_string, symtab);
        if (error) {
            goto out;
        }
        ovsdb_idl_txn_write(row, column, &datum);
    }

out:
    free(key_string);
    free(value_string);

    return error;
}

static void
pre_cmd_set(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    for (i = 3; i < ctx->argc; i++) {
        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
        if (ctx->error) {
            return;
        }
    }
}

static void
cmd_set(struct ctl_context *ctx)
{
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        return;
    }

    for (i = 3; i < ctx->argc; i++) {
        ctx->error = set_column(table, row, ctx->argv[i], ctx->symtab,
                                ctx->last_command);
        if (ctx->error) {
            return;
        }
    }

    invalidate_cache(ctx);
}

static void
pre_cmd_add(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const char *column_name = ctx->argv[3];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_column *column;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = pre_get_column(ctx, table, column_name, &column);
    if (ctx->error) {
        return;
    }
}

static void
cmd_add(struct ctl_context *ctx)
{
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const char *column_name = ctx->argv[3];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_column *column;
    const struct ovsdb_idl_row *row;
    const struct ovsdb_type *type;
    struct ovsdb_datum old;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = get_column(table, column_name, &column);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        return;
    }
    ctx->error = check_mutable(row, column);
    if (ctx->error) {
        return;
    }

    type = &column->type;
    ovsdb_datum_clone(&old, ovsdb_idl_read(row, column));
    for (i = 4; i < ctx->argc; i++) {
        struct ovsdb_type add_type;
        struct ovsdb_datum add;

        add_type = *type;
        add_type.n_min = 1;
        add_type.n_max = UINT_MAX;
        ctx->error = ovsdb_datum_from_string(&add, &add_type, ctx->argv[i],
                                             ctx->symtab);
        if (ctx->error) {
            ovsdb_datum_destroy(&old, &column->type);
            return;
        }
        ovsdb_datum_union(&old, &add, type);
        ovsdb_datum_destroy(&add, type);
    }
    if (old.n > type->n_max) {
        ctl_error(ctx, "\"add\" operation would put %u %s in column %s of "
                  "table %s but the maximum number is %u",
                  old.n,
                  type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
                  column->name, table->name, type->n_max);
        ovsdb_datum_destroy(&old, &column->type);
        return;
    }
    ovsdb_idl_txn_verify(row, column);
    ovsdb_idl_txn_write(row, column, &old);

    invalidate_cache(ctx);
}

static void
pre_cmd_remove(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const char *column_name = ctx->argv[3];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_column *column;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = pre_get_column(ctx, table, column_name, &column);
    if (ctx->error) {
        return;
    }
}

static void
cmd_remove(struct ctl_context *ctx)
{
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const char *column_name = ctx->argv[3];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_column *column;
    const struct ovsdb_idl_row *row;
    const struct ovsdb_type *type;
    struct ovsdb_datum old;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = get_column(table, column_name, &column);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        return;
    }
    ctx->error = check_mutable(row, column);
    if (ctx->error) {
        return;
    }

    type = &column->type;
    ovsdb_datum_clone(&old, ovsdb_idl_read(row, column));
    for (i = 4; i < ctx->argc; i++) {
        struct ovsdb_type rm_type;
        struct ovsdb_datum rm;
        char *error;

        rm_type = *type;
        rm_type.n_min = 1;
        rm_type.n_max = UINT_MAX;
        error = ovsdb_datum_from_string(&rm, &rm_type,
                                        ctx->argv[i], ctx->symtab);

        if (error) {
            if (ovsdb_type_is_map(&rm_type)) {
                rm_type.value.type = OVSDB_TYPE_VOID;
                free(error);
                ctx->error = ovsdb_datum_from_string(&rm, &rm_type,
                                                     ctx->argv[i],
                                                     ctx->symtab);
                if (ctx->error) {
                    ovsdb_datum_destroy(&old, &column->type);
                    return;
                }
            } else {
                ctx->error = error;
                ovsdb_datum_destroy(&old, &column->type);
                return;
            }
        }
        ovsdb_datum_subtract(&old, type, &rm, &rm_type);
        ovsdb_datum_destroy(&rm, &rm_type);
    }
    if (old.n < type->n_min) {
        ctl_error(ctx, "\"remove\" operation would put %u %s in column %s of "
                  "table %s but the minimum number is %u", old.n,
                  type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
                  column->name, table->name, type->n_min);
        ovsdb_datum_destroy(&old, &column->type);
        return;
    }
    ovsdb_idl_txn_verify(row, column);
    ovsdb_idl_txn_write(row, column, &old);

    invalidate_cache(ctx);
}

static void
pre_cmd_clear(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }
    for (i = 3; i < ctx->argc; i++) {
        const struct ovsdb_idl_column *column;

        ctx->error = pre_get_column(ctx, table, ctx->argv[i], &column);
        if (ctx->error) {
            return;
        }
    }
}

static void
cmd_clear(struct ctl_context *ctx)
{
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        return;
    }

    for (i = 3; i < ctx->argc; i++) {
        const struct ovsdb_idl_column *column;
        const struct ovsdb_type *type;
        struct ovsdb_datum datum;

        ctx->error = get_column(table, ctx->argv[i], &column);
        if (ctx->error) {
            return;
        }
        ctx->error = check_mutable(row, column);
        if (ctx->error) {
            return;
        }

        type = &column->type;
        if (type->n_min > 0) {
            ctl_error(ctx, "\"clear\" operation cannot be applied to column "
                      "%s of table %s, which is not allowed to be empty",
                      column->name, table->name);
            return;
        }

        ovsdb_datum_init_empty(&datum);
        ovsdb_idl_txn_write(row, column, &datum);
    }

    invalidate_cache(ctx);
}

static void
pre_create(struct ctl_context *ctx)
{
    const char *id = shash_find_data(&ctx->options, "--id");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    if (!id && !table->is_root) {
        VLOG_WARN("applying \"create\" command to table %s without --id "
                  "option will have no effect", table->name);
    }
}

static void
cmd_create(struct ctl_context *ctx)
{
    const char *id = shash_find_data(&ctx->options, "--id");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    const struct uuid *uuid = NULL;
    bool persist_uuid = false;
    struct uuid uuid_;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }

    if (id) {
        if (uuid_from_string(&uuid_, id)) {
            uuid = &uuid_;
            persist_uuid = true;
        } else {
            struct ovsdb_symbol *symbol = NULL;

            ctx->error = create_symbol(ctx->symtab, id, &symbol, NULL);
            if (ctx->error) {
                return;
            }
            if (table->is_root) {
                /* This table is in the root set, meaning that rows created in
                 * it won't disappear even if they are unreferenced, so disable
                 * warnings about that by pretending that there is a
                 * reference. */
                symbol->strong_ref = true;
            }
            uuid = &symbol->uuid;
        }
    }

    if (persist_uuid) {
        row = ovsdb_idl_txn_insert_persist_uuid(ctx->txn, table, uuid);
    } else {
        row = ovsdb_idl_txn_insert(ctx->txn, table, uuid);
    }

    for (i = 2; i < ctx->argc; i++) {
        ctx->error = set_column(table, row, ctx->argv[i], ctx->symtab, false);
        if (ctx->error) {
            return;
        }
    }
    ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
}

/* This function may be used as the 'postprocess' function for commands that
 * insert new rows into the database.  It expects that the command's 'run'
 * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's
 * sole output.  It replaces that output by the row's permanent UUID assigned
 * by the database server and appends a new-line.
 *
 * Currently we use this only for "create", because the higher-level commands
 * are supposed to be independent of the actual structure of the vswitch
 * configuration. */
static void
post_create(struct ctl_context *ctx)
{
    const struct uuid *real;
    struct uuid dummy;

    if (!uuid_from_string(&dummy, ds_cstr(&ctx->output))) {
        OVS_NOT_REACHED();
    }
    real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy);
    if (real) {
        ds_clear(&ctx->output);
        ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real));
    }
    ds_put_char(&ctx->output, '\n');
}

static void
pre_cmd_destroy(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];

    ctx->error = pre_get_table(ctx, table_name, NULL);
    if (ctx->error) {
        return;
    }
}

static void
cmd_destroy(struct ctl_context *ctx)
{
    bool must_exist = !shash_find(&ctx->options, "--if-exists");
    bool delete_all = shash_find(&ctx->options, "--all");
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }

    if (delete_all && ctx->argc > 2) {
        ctl_error(ctx, "--all and records argument should not be specified "
                  "together");
        return;
    }

    if (delete_all && !must_exist) {
        ctl_error(ctx, "--all and --if-exists should not be specified "
                  "together");
        return;
    }

    if (!delete_all && ctx->argc == 2) {
        VLOG_WARN("either --all or records argument should be specified");
        return;
    }

    if (delete_all) {
        const struct ovsdb_idl_row *row;
        const struct ovsdb_idl_row *next_row;

        for (row = ovsdb_idl_first_row(ctx->idl, table);
             row;) {
            next_row = ovsdb_idl_next_row(row);
            ovsdb_idl_txn_delete(row);
            row = next_row;
        }
    } else {
        for (i = 2; i < ctx->argc; i++) {
            const struct ovsdb_idl_row *row;

            ctx->error = ctl_get_row(ctx, table, ctx->argv[i], must_exist,
                                     &row);
            if (ctx->error) {
                return;
            }
            if (row) {
                ovsdb_idl_txn_delete(row);
            }
        }
    }
    invalidate_cache(ctx);
}

static void
pre_cmd_wait_until(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const struct ovsdb_idl_table_class *table;
    int i;

    ctx->error = pre_get_table(ctx, table_name, &table);
    if (ctx->error) {
        return;
    }

    for (i = 3; i < ctx->argc; i++) {
        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
        if (ctx->error) {
            return;
        }
    }
}

static void
cmd_wait_until(struct ctl_context *ctx)
{
    const char *table_name = ctx->argv[1];
    const char *record_id = ctx->argv[2];
    const struct ovsdb_idl_table_class *table;
    const struct ovsdb_idl_row *row;
    int i;

    ctx->error = get_table(table_name, &table);
    if (ctx->error) {
        return;
    }
    ctx->error = ctl_get_row(ctx, table, record_id, false, &row);
    if (ctx->error) {
        return;
    }
    if (!row) {
        ctx->try_again = true;
        return;
    }

    for (i = 3; i < ctx->argc; i++) {
        bool satisfied;

        ctx->error = check_condition(table, row, ctx->argv[i], ctx->symtab,
                                     &satisfied);
        if (ctx->error) {
            return;
        }
        if (!satisfied) {
            ctx->try_again = true;
            return;
        }
    }
}

/* Parses one command. */
static char * OVS_WARN_UNUSED_RESULT
parse_command(int argc, char *argv[], struct shash *local_options,
              struct ctl_command *command)
{
    const struct ctl_command_syntax *p;
    struct shash_node *node;
    int n_arg;
    int i;
    char *error;

    shash_init(&command->options);
    shash_swap(local_options, &command->options);
    for (i = 0; i < argc; i++) {
        const char *option = argv[i];
        const char *equals;
        char *key, *value;

        if (option[0] != '-') {
            break;
        }

        equals = strchr(option, '=');
        if (equals) {
            key = xmemdup0(option, equals - option);
            value = xstrdup(equals + 1);
        } else {
            key = xstrdup(option);
            value = NULL;
        }

        if (shash_find(&command->options, key)) {
            free(key);
            free(value);
            error = xasprintf("'%s' option specified multiple times", argv[i]);
            goto error;
        }
        shash_add_nocopy(&command->options, key, value);
    }
    if (i == argc) {
        error = xstrdup("missing command name (use --help for help)");
        goto error;
    }

    p = shash_find_data(&all_commands, argv[i]);
    if (!p) {
        error = xasprintf("unknown command '%s'; use --help for help",
                          argv[i]);
        goto error;
    }

    SHASH_FOR_EACH (node, &command->options) {
        const char *s = strstr(p->options, node->name);
        int end = s ? s[strlen(node->name)] : EOF;

        if (!strchr("=,? ", end)) {
            error = xasprintf("'%s' command has no '%s' option",
                              argv[i], node->name);
            goto error;
        }
        if (end != '?' && (end == '=') != (node->data != NULL)) {
            if (end == '=') {
                error = xasprintf("missing argument to '%s' option on '%s' "
                                  "command", node->name, argv[i]);
                goto error;
            } else {
                error = xasprintf("'%s' option on '%s' does not accept an "
                                  "argument", node->name, argv[i]);
                goto error;
            }
        }
    }

    n_arg = argc - i - 1;
    if (n_arg < p->min_args) {
        error = xasprintf("'%s' command requires at least %d arguments",
                          p->name, p->min_args);
        goto error;
    } else if (n_arg > p->max_args) {
        int j;

        for (j = i + 1; j < argc; j++) {
            if (argv[j][0] == '-') {
                error = xasprintf("'%s' command takes at most %d arguments "
                                  "(note that options must precede command "
                                  "names and follow a \"--\" argument)",
                                  p->name, p->max_args);
                goto error;
            }
        }

        error = xasprintf("'%s' command takes at most %d arguments",
                          p->name, p->max_args);
        goto error;
    }

    command->syntax = p;
    command->argc = n_arg + 1;
    command->argv = &argv[i];
    return NULL;

error:
    shash_destroy_free_data(&command->options);
    return error;
}

static void
pre_cmd_show(struct ctl_context *ctx)
{
    const struct cmd_show_table *show;

    for (show = cmd_show_tables; show->table; show++) {
        size_t i;

        ovsdb_idl_add_table(ctx->idl, show->table);
        if (show->name_column) {
            ovsdb_idl_add_column(ctx->idl, show->name_column);
        }
        for (i = 0; i < ARRAY_SIZE(show->columns); i++) {
            const struct ovsdb_idl_column *column = show->columns[i];
            if (column) {
                ovsdb_idl_add_column(ctx->idl, column);
            }
        }
        if (show->wref_table.table) {
            ovsdb_idl_add_table(ctx->idl, show->wref_table.table);
        }
        if (show->wref_table.name_column) {
            ovsdb_idl_add_column(ctx->idl, show->wref_table.name_column);
        }
        if (show->wref_table.wref_column) {
            ovsdb_idl_add_column(ctx->idl, show->wref_table.wref_column);
        }
    }
}

static const struct cmd_show_table *
cmd_show_find_table_by_row(const struct ovsdb_idl_row *row)
{
    const struct cmd_show_table *show;

    for (show = cmd_show_tables; show->table; show++) {
        if (show->table == row->table->class_) {
            return show;
        }
    }
    return NULL;
}

static const struct cmd_show_table *
cmd_show_find_table_by_name(const char *name)
{
    const struct cmd_show_table *show;

    for (show = cmd_show_tables; show->table; show++) {
        if (!strcmp(show->table->name, name)) {
            return show;
        }
    }
    return NULL;
}

/*  Prints table entries that weak reference the 'cur_row'. */
static void
cmd_show_weak_ref(struct ctl_context *ctx, const struct cmd_show_table *show,
                  const struct ovsdb_idl_row *cur_row, int level)
{
    const struct ovsdb_idl_row *row_wref;
    const struct ovsdb_idl_table_class *table = show->wref_table.table;
    const struct ovsdb_idl_column *name_column
        = show->wref_table.name_column;
    const struct ovsdb_idl_column *wref_column
        = show->wref_table.wref_column;

    if (!table || !name_column || !wref_column) {
        return;
    }

    for (row_wref = ovsdb_idl_first_row(ctx->idl, table); row_wref;
         row_wref = ovsdb_idl_next_row(row_wref)) {
        const struct ovsdb_datum *wref_datum
            = ovsdb_idl_read(row_wref, wref_column);
        /* If weak reference refers to the 'cur_row', prints it. */
        if (wref_datum->n
            && uuid_equals(&cur_row->uuid, &wref_datum->keys[0].uuid)) {
            const struct ovsdb_datum *name_datum
                = ovsdb_idl_read(row_wref, name_column);
            ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
            ds_put_format(&ctx->output, "%s ", table->name);
            ovsdb_datum_to_string(name_datum, &name_column->type, &ctx->output);
            ds_put_char(&ctx->output, '\n');
        }
    }
}

/* 'shown' records the tables that has been displayed by the current
 * command to avoid duplicated prints.
 */
static void
cmd_show_row(struct ctl_context *ctx, const struct ovsdb_idl_row *row,
             int level, struct sset *shown)
{
    const struct cmd_show_table *show = cmd_show_find_table_by_row(row);
    size_t i;

    ds_put_char_multiple(&ctx->output, ' ', level * 4);
    if (show && show->name_column) {
        const struct ovsdb_datum *datum;

        ds_put_format(&ctx->output, "%s ", show->table->name);
        datum = ovsdb_idl_read(row, show->name_column);
        ovsdb_datum_to_string(datum, &show->name_column->type, &ctx->output);
    } else {
        ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
    }
    ds_put_char(&ctx->output, '\n');

    if (!show || sset_find(shown, show->table->name)) {
        return;
    }

    sset_add(shown, show->table->name);
    for (i = 0; i < ARRAY_SIZE(show->columns); i++) {
        const struct ovsdb_idl_column *column = show->columns[i];
        const struct ovsdb_datum *datum;

        if (!column) {
            break;
        }

        datum = ovsdb_idl_read(row, column);
        if (column->type.key.type == OVSDB_TYPE_UUID &&
            column->type.key.uuid.refTableName) {
            const struct cmd_show_table *ref_show;
            size_t j;

            ref_show = cmd_show_find_table_by_name(
                column->type.key.uuid.refTableName);
            if (ref_show) {
                for (j = 0; j < datum->n; j++) {
                    const struct ovsdb_idl_row *ref_row;

                    ref_row = ovsdb_idl_get_row_for_uuid(ctx->idl,
                                                         ref_show->table,
                                                         &datum->keys[j].uuid);
                    if (ref_row) {
                        cmd_show_row(ctx, ref_row, level + 1, shown);
                    }
                }
                continue;
            }
        } else if (ovsdb_type_is_map(&column->type) &&
                   column->type.value.type == OVSDB_TYPE_UUID &&
                   column->type.value.uuid.refTableName) {
            const struct cmd_show_table *ref_show;
            size_t j;

            /* Prints the key to ref'ed table name map if the ref'ed table
             * is also defined in 'cmd_show_tables'.  */
            ref_show = cmd_show_find_table_by_name(
                column->type.value.uuid.refTableName);
            if (ref_show && ref_show->name_column) {
                ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
                ds_put_format(&ctx->output, "%s:\n", column->name);
                for (j = 0; j < datum->n; j++) {
                    const struct ovsdb_idl_row *ref_row;

                    ref_row = ovsdb_idl_get_row_for_uuid(ctx->idl,
                                                         ref_show->table,
                                                         &datum->values[j].uuid);

                    ds_put_char_multiple(&ctx->output, ' ', (level + 2) * 4);
                    ovsdb_atom_to_string(&datum->keys[j], column->type.key.type,
                                         &ctx->output);
                    ds_put_char(&ctx->output, '=');
                    if (ref_row) {
                        const struct ovsdb_datum *ref_datum;

                        ref_datum = ovsdb_idl_read(ref_row,
                                                   ref_show->name_column);
                        ovsdb_datum_to_string(ref_datum,
                                              &ref_show->name_column->type,
                                              &ctx->output);
                    } else {
                        ds_put_cstr(&ctx->output, "\"<null>\"");
                    }
                    ds_put_char(&ctx->output, '\n');
                }
                continue;
            }
        }

        if (!ovsdb_datum_is_default(datum, &column->type)) {
            ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
            ds_put_format(&ctx->output, "%s: ", column->name);
            ovsdb_datum_to_string(datum, &column->type, &ctx->output);
            ds_put_char(&ctx->output, '\n');
        }
    }
    cmd_show_weak_ref(ctx, show, row, level);
    sset_find_and_delete_assert(shown, show->table->name);
}

static void
cmd_show(struct ctl_context *ctx)
{
    const struct ovsdb_idl_row *row;
    struct sset shown = SSET_INITIALIZER(&shown);

    for (row = ovsdb_idl_first_row(ctx->idl, cmd_show_tables[0].table);
         row; row = ovsdb_idl_next_row(row)) {
        cmd_show_row(ctx, row, 0, &shown);
    }

    ovs_assert(sset_is_empty(&shown));
    sset_destroy(&shown);
}


/* Given pointer to dynamic array 'options_p',  array's current size
 * 'allocated_options_p' and number of added options 'n_options_p',
 * adds all command options to the array.  Enlarges the array if
 * necessary. */
void
ctl_add_cmd_options(struct option **options_p, size_t *n_options_p,
                    size_t *allocated_options_p, int opt_val)
{
    struct option *o;
    const struct shash_node *node;
    size_t n_existing_options = *n_options_p;

    SHASH_FOR_EACH (node, &all_commands) {
        const struct ctl_command_syntax *p = node->data;

        if (p->options[0]) {
            char *save_ptr = NULL;
            char *name;
            char *s;

            s = xstrdup(p->options);
            for (name = strtok_r(s, ",", &save_ptr); name != NULL;
                 name = strtok_r(NULL, ",", &save_ptr)) {
                ovs_assert(name[0] == '-' && name[1] == '-' && name[2]);
                name += 2;

                size_t n = strcspn(name, "=?");
                int has_arg = (name[n] == '\0' ? no_argument
                               : name[n] == '=' ? required_argument
                               : optional_argument);
                name[n] = '\0';

                o = find_option(name, *options_p, *n_options_p);
                if (o) {
                    ovs_assert(o - *options_p >= n_existing_options);
                    ovs_assert(o->has_arg == has_arg);
                } else {
                    o = add_option(options_p, n_options_p, allocated_options_p);
                    o->name = xstrdup(name);
                    o->has_arg = has_arg;
                    o->flag = NULL;
                    o->val = opt_val;
                }
            }

            free(s);
        }
    }
    o = add_option(options_p, n_options_p, allocated_options_p);
    memset(o, 0, sizeof *o);
}

/* Parses command-line input for commands. */
char *
ctl_parse_commands(int argc, char *argv[], struct shash *local_options,
                   struct ctl_command **commandsp, size_t *n_commandsp)
{
    struct ctl_command *commands;
    size_t n_commands, allocated_commands;
    int i, start;
    char *error;

    commands = NULL;
    n_commands = allocated_commands = 0;

    *commandsp = NULL;
    *n_commandsp = 0;

    for (start = i = 0; i <= argc; i++) {
        if (i == argc || !strcmp(argv[i], "--")) {
            if (i > start) {
                if (n_commands >= allocated_commands) {
                    struct ctl_command *c;

                    commands = x2nrealloc(commands, &allocated_commands,
                                          sizeof *commands);
                    for (c = commands; c < &commands[n_commands]; c++) {
                        shash_moved(&c->options);
                    }
                }
                error = parse_command(i - start, &argv[start], local_options,
                                      &commands[n_commands]);
                if (error) {
                    struct ctl_command *c;

                    for (c = commands; c < &commands[n_commands]; c++) {
                        shash_destroy_free_data(&c->options);
                    }
                    free(commands);

                    return error;
                }

                n_commands++;
            } else if (!shash_is_empty(local_options)) {
                return xstrdup("missing command name (use --help for help)");
            }
            start = i + 1;
        }
    }
    if (!n_commands) {
        return xstrdup("missing command name (use --help for help)");
    }
    *commandsp = commands;
    *n_commandsp = n_commands;
    return NULL;
}

/* Prints all registered commands. */
void
ctl_print_commands(void)
{
    const struct shash_node *node;

    SHASH_FOR_EACH (node, &all_commands) {
        const struct ctl_command_syntax *p = node->data;
        char *options = xstrdup(p->options);
        char *options_begin = options;
        char *item;

        for (item = strsep(&options, ","); item != NULL;
             item = strsep(&options, ",")) {
            if (item[0] != '\0') {
                printf("[%s] ", item);
            }
        }
        printf(",%s,", p->name);
        print_command_arguments(p);
        printf("\n");

        free(options_begin);
    }

    exit(EXIT_SUCCESS);
}

/* Given array of options 'options', prints them. */
void
ctl_print_options(const struct option *options)
{
    for (; options->name; options++) {
        const struct option *o = options;

        printf("--%s%s\n", o->name, o->has_arg ? "=ARG" : "");
        if (o->flag == NULL && o->val > 0 && o->val <= UCHAR_MAX) {
            printf("-%c%s\n", o->val, o->has_arg ? " ARG" : "");
        }
    }

    exit(EXIT_SUCCESS);
}

/* Returns the default local database path. */
char *
ctl_default_db(void)
{
    static char *def;
    if (!def) {
        def = xasprintf("unix:%s/db.sock", ovs_rundir());
    }
    return def;
}

/* Returns true if it looks like this set of arguments might modify the
 * database, otherwise false.  (Not very smart, so it's prone to false
 * positives.) */
bool
ctl_might_write_to_db(const struct ctl_command *commands, size_t n)
{
    for (size_t i = 0; i < n; i++) {
        if (commands[i].syntax->mode == RW) {
            return true;
        }
    }
    return false;
}

/* Report an error while running in the command context. Caller should return
 * to its caller immediately after reporting the error. */
void
ctl_error(struct ctl_context *ctx, const char *format, ...)
{
    va_list args;

    ovs_assert(ctx);

    if (ctx->error) {
        VLOG_ERR("Discarding unhandled error: %s", ctx->error);
        free(ctx->error);
    }

    va_start(args, format);
    ctx->error = xvasprintf(format, args);
    va_end(args);
}

void
ctl_fatal(const char *format, ...)
{
    char *message;
    va_list args;

    va_start(args, format);
    message = xvasprintf(format, args);
    va_end(args);

    vlog_set_levels(&this_module, VLF_CONSOLE, VLL_OFF);
    VLOG_ERR("%s", message);
    ovs_error(0, "%s", message);
    ctl_exit(EXIT_FAILURE);
}

/* Frees the current transaction and the underlying IDL and then calls
 * exit(status).
 *
 * Freeing the transaction and the IDL is not strictly necessary, but it makes
 * for a clean memory leak report from valgrind in the normal case.  That makes
 * it easier to notice real memory leaks. */
static void
ctl_exit(int status)
{
    if (ctl_exit_func) {
        ctl_exit_func(status);
    }
    exit(status);
}

/* Comman database commands to be registered. */
static const struct ctl_command_syntax db_ctl_commands[] = {
    {"comment", 0, INT_MAX, "[ARG]...", NULL, NULL, NULL, "", RO},
    {"get", 2, INT_MAX, "TABLE RECORD [COLUMN[:KEY]]...",pre_cmd_get, cmd_get,
     NULL, "--if-exists,--id=", RO},
    {"list", 1, INT_MAX, "TABLE [RECORD]...", pre_cmd_list, cmd_list, NULL,
     "--if-exists,--columns=", RO},
    {"find", 1, INT_MAX, "TABLE [COLUMN[:KEY]=VALUE]...", pre_cmd_find,
     cmd_find, NULL, "--columns=", RO},
    {"set", 3, INT_MAX, "TABLE RECORD COLUMN[:KEY]=VALUE...", pre_cmd_set,
     cmd_set, NULL, "--if-exists", RW},
    {"add", 4, INT_MAX, "TABLE RECORD COLUMN [KEY=]VALUE...", pre_cmd_add,
     cmd_add, NULL, "--if-exists", RW},
    {"remove", 4, INT_MAX, "TABLE RECORD COLUMN KEY|VALUE|KEY=VALUE...",
     pre_cmd_remove, cmd_remove, NULL, "--if-exists", RW},
    {"clear", 3, INT_MAX, "TABLE RECORD COLUMN...", pre_cmd_clear, cmd_clear,
     NULL, "--if-exists", RW},
    {"create", 2, INT_MAX, "TABLE COLUMN[:KEY]=VALUE...", pre_create,
     cmd_create, post_create, "--id=", RW},
    {"destroy", 1, INT_MAX, "TABLE [RECORD]...", pre_cmd_destroy, cmd_destroy,
     NULL, "--if-exists,--all", RW},
    {"wait-until", 2, INT_MAX, "TABLE RECORD [COLUMN[:KEY]=VALUE]...",
     pre_cmd_wait_until, cmd_wait_until, NULL, "", RO},
    {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
};

static void
ctl_register_command(const struct ctl_command_syntax *command)
{
    shash_add_assert(&all_commands, command->name, command);
}

/* Registers commands represented by 'struct ctl_command_syntax's to
 * 'all_commands'.  The last element of 'commands' must be an all-NULL
 * element. */
void
ctl_register_commands(const struct ctl_command_syntax *commands)
{
    const struct ctl_command_syntax *p;

    for (p = commands; p->name; p++) {
        ctl_register_command(p);
    }
}

/* Registers the 'db_ctl_commands' to 'all_commands'. */
void
ctl_init__(const struct ovsdb_idl_class *idl_class_,
           const struct ctl_table_class *ctl_classes_,
           const struct cmd_show_table cmd_show_tables_[],
           void (*ctl_exit_func_)(int status))
{
    idl_class = idl_class_;
    idl_classes = idl_class_->tables;
    ctl_classes = ctl_classes_;
    n_classes = idl_class->n_tables;
    ctl_exit_func = ctl_exit_func_;
    ctl_register_commands(db_ctl_commands);

    cmd_show_tables = cmd_show_tables_;
    if (cmd_show_tables) {
        static const struct ctl_command_syntax show =
            {"show", 0, 0, "", pre_cmd_show, cmd_show, NULL, "", RO};
        ctl_register_command(&show);
    }
}

/* Returns the text for the database commands usage.  */
const char *
ctl_get_db_cmd_usage(void)
{
    return "Database commands:\n\
  list TBL [REC]              list RECord (or all records) in TBL\n\
  find TBL CONDITION...       list records satisfying CONDITION in TBL\n\
  get TBL REC COL[:KEY]       print values of COLumns in RECord in TBL\n\
  set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
  add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
  remove TBL REC COL [KEY=]VALUE  remove (KEY=)VALUE from COLumn\n\
  clear TBL REC COL           clear values from COLumn in RECord in TBL\n\
  create TBL COL[:KEY]=VALUE  create and initialize new record\n\
  destroy TBL REC             delete RECord from TBL\n\
  wait-until TBL REC [COL[:KEY]=VALUE]  wait until condition is true\n\
Potentially unsafe database commands require --force option.\n";
}

const char *
ctl_list_db_tables_usage(void)
{
    static struct ds s = DS_EMPTY_INITIALIZER;
    if (s.length) {
        return ds_cstr(&s);
    }

    ds_put_cstr(&s, "Database commands may reference a row in each table in the following ways:\n");
    for (int i = 0; i < n_classes; i++) {
        struct svec options = SVEC_EMPTY_INITIALIZER;

        svec_add(&options, "by UUID");
        if (idl_classes[i].is_singleton) {
            svec_add(&options, "as \".\"");
        }

        for (int j = 0; j < ARRAY_SIZE(ctl_classes[i].row_ids); j++) {
            const struct ctl_row_id *id = &ctl_classes[i].row_ids[j];
            if (!id->name_column) {
                continue;
            }

            struct ds o = DS_EMPTY_INITIALIZER;
            if (id->uuid_column) {
                ds_put_format(&o, "via \"%s\"", id->uuid_column->name);
                const struct ovsdb_idl_table_class *referrer
                    = ovsdb_idl_table_class_from_column(idl_class,
                                                        id->uuid_column);
                if (referrer != &idl_classes[i]) {
                    ds_put_format(&o, " of %s", referrer->name);
                }
                if (id->key) {
                    ds_put_format(&o, " with matching \"%s:%s\"",
                                  id->name_column->name, id->key);
                } else {
                    ds_put_format(&o, " with matching \"%s\"", id->name_column->name);
                }
            } else if (id->key) {
                ds_put_format(&o, "by \"%s:%s\"", id->name_column->name, id->key);
            } else {
                ds_put_format(&o, "by \"%s\"", id->name_column->name);
            }
            svec_add_nocopy(&options, ds_steal_cstr(&o));
        }

        ds_put_format(&s, "  %s:", idl_classes[i].name);
        for (int j = 0; j < options.n; j++) {
            ds_put_format(&s, "\n    %s", options.names[j]);
        }
        ds_put_char(&s, '\n');
        svec_destroy(&options);
    }

    return ds_cstr(&s);
}

/* Initializes 'ctx' from 'command'. */
void
ctl_context_init_command(struct ctl_context *ctx,
                         struct ctl_command *command,
                         bool last)
{
    ctx->argc = command->argc;
    ctx->argv = command->argv;
    ctx->options = command->options;

    ds_swap(&ctx->output, &command->output);
    ctx->table = command->table;
    ctx->try_again = false;
    ctx->last_command = last;
    ctx->error = NULL;
}

/* Initializes the entire 'ctx'. */
void
ctl_context_init(struct ctl_context *ctx, struct ctl_command *command,
                 struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
                 struct ovsdb_symbol_table *symtab,
                 void (*invalidate_cache_cb)(struct ctl_context *))
{
    if (command) {
        ctl_context_init_command(ctx, command, false);
    }
    ctx->idl = idl;
    ctx->txn = txn;
    ctx->symtab = symtab;
    ctx->invalidate_cache_cb = invalidate_cache_cb;
}

/* Completes processing of 'command' within 'ctx'. */
void
ctl_context_done_command(struct ctl_context *ctx,
                         struct ctl_command *command)
{
    ds_swap(&ctx->output, &command->output);
    command->table = ctx->table;
    free(ctx->error);
    ctx->error = NULL;
}

/* Finishes up with 'ctx'.
 *
 * If command is nonnull, first calls ctl_context_done_command() to complete
 * processing that command within 'ctx'. */
void
ctl_context_done(struct ctl_context *ctx,
                 struct ctl_command *command)
{
    if (command) {
        ctl_context_done_command(ctx, command);
    }
    invalidate_cache(ctx);
}

char * OVS_WARN_UNUSED_RESULT
ctl_set_column(const char *table_name, const struct ovsdb_idl_row *row,
               const char *arg, struct ovsdb_symbol_table *symtab)
{
    const struct ovsdb_idl_table_class *table;
    char *error;

    error = get_table(table_name, &table);
    if (error) {
        return error;
    }
    error = set_column(table, row, arg, symtab, false);
    if (error) {
        return error;
    }

    return NULL;
}
