qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple metho


From: Daniel P. Berrange
Subject: Re: [Qemu-devel] [PATCH v1 01/10] qdict: implement a qdict_crumple method for un-flattening a dict
Date: Thu, 3 Mar 2016 11:01:35 +0000
User-agent: Mutt/1.5.24 (2015-08-30)

On Wed, Mar 02, 2016 at 05:13:59PM +0100, Max Reitz wrote:
> On 19.02.2016 17:47, Daniel P. Berrange wrote:
> > The qdict_flatten() method will take a dict whose elements are
> > further nested dicts/lists and flatten them by concatenating
> > keys.
> > 
> > The qdict_crumple() method aims todo the reverse, taking a flat
> > qdict, and turning it into a set of nested dicts/lists. It will
> > apply nesting based on the key name, with a '.' indicating a
> > new level in the hierarchy. If the keys in the nested structure
> > are all numeric, it will create a list, otherwise it will create
> > a dict.
> > 
> > If the keys are a mixture of numeric and non-numeric, or the
> > numeric keys are not in strictly ascending order, an error will
> > be reported.
> > 
> > As an example, a flat dict containing
> > 
> >  {
> >    'foo.0.bar': 'one',
> >    'foo.0.wizz': '1',
> >    'foo.1.bar': 'two',
> >    'foo.1.wizz': '2'
> >  }
> > 
> > will get turned into a dict with one element 'foo' whose
> > value is a list. The list elements will each in turn be
> > dicts.
> > 
> >  {
> >    'foo' => [
> >      { 'bar': 'one', 'wizz': '1' }
> >      { 'bar': 'two', 'wizz': '2' }
> >    ],
> >  }
> > 
> > If the key is intended to contain a literal '.', then it must
> > be escaped as '..'. ie a flat dict
> > 
> >   {
> >      'foo..bar': 'wizz',
> >      'bar.foo..bar': 'eek',
> >      'bar.hello': 'world'
> >   }
> > 
> > Will end up as
> > 
> >   {
> >      'foo.bar': 'wizz',
> >      'bar': {
> >         'foo.bar': 'eek',
> >         'hello': 'world'
> >      }
> >   }
> > 
> > The intent of this function is that it allows a set of QemuOpts
> > to be turned into a nested data structure that mirrors the nested
> > used when the same object is defined over QMP.
> > 
> > Signed-off-by: Daniel P. Berrange <address@hidden>
> > ---
> >  include/qapi/qmp/qdict.h |   1 +
> >  qobject/qdict.c          | 180 
> > +++++++++++++++++++++++++++++++++++++++++++++++
> >  tests/check-qdict.c      |  39 ++++++++++
> >  3 files changed, 220 insertions(+)
> > 

> > +QObject *qdict_crumple(QDict *src, Error **errp)
> > +{
> > +    const QDictEntry *entry, *next;
> > +    const char *p = NULL;
> > +    QDict *tmp1 = NULL, *tmp2 = NULL;
> > +    QObject *dst = NULL, *child;
> > +    bool isList = false;
> > +    ssize_t listMax = -1;
> > +    size_t listLen = 0;
> 
> As far as I'm aware we don't have any strict policy on names, but in
> general structs use UpperCamelCase and variables and functions use
> c_style_naming.

Yep, ok

> > +    size_t i, j;
> > +    int64_t val;
> > +    char *key;
> > +
> > +    tmp1 = qdict_new();
> 
> I guess I'd like clearer variable names.

Sure

> 
> > +    entry = qdict_first(src);
> > +
> > +    /* Step 1: extract everything as nested dicts */
> > +    while (entry != NULL) {
> > +        next = qdict_next(src, entry);
> > +        qobject_incref(entry->value);
> > +
> > +        /* Find first '.' separator, but treat '..' as
> > +         * an escape sequence */
> > +        p = NULL;
> 
> How about at least "dot" or "separator"?

Sure.

> > +        do {
> > +            if (p) {
> > +                p += 2;
> > +            } else {
> > +                p = entry->key;
> > +            }
> > +            p = strchr(p, '.');
> > +        } while (p && *(p + 1) == '.');
> > +
> > +        if (p) {
> > +            key = g_strndup(entry->key,
> > +                            p - entry->key);
> > +        } else {
> > +            key = g_strdup(entry->key);
> > +        }
> > +
> > +        for (i = 0, j = 0; key[i] != '\0'; i++, j++) {
> > +            if (key[i] == '.' &&
> > +                key[i + 1] == '.') {
> > +                i++;
> > +            }
> > +            key[j] = key[i];
> > +        }
> > +        key[j] = '\0';
> 
> I general I think it would be nice to put this escaping code into an own
> static function which returns a strdup'd key for the QDict and this
> entry's key in that QDict.

Yep, good idea.

> 
> > +
> > +        if (p) {
> > +            tmp2 = qdict_get_qdict(tmp1, key);
> > +            p++;
> > +            if (!tmp2) {
> > +                tmp2 = qdict_new();
> > +                qdict_put(tmp1, key, tmp2);
> 
> This...
> 
> > +            }
> > +            qdict_put_obj(tmp2, p, entry->value);
> > +        } else {
> > +            qdict_put_obj(tmp1, key, entry->value);
> 
> ...and this may silently overwrite entries, e.g. when crumpling
> 
> { "x": 42, "x.y": 23 }
> 
> > +        }
> 
> key is leaked.

Oh that should result in an error being raised as it is
a contradictory input.

> > +
> > +        entry = next;
> > +    }
> > +
> > +    /* Step 2: crumple the new dicts we just created */
> > +    tmp2 = qdict_new();
> 
> Please use more variables with clearer names.
> 
> > +    entry = qdict_first(tmp1);
> > +    while (entry != NULL) {
> > +        next = qdict_next(tmp1, entry);
> > +
> > +        if (qobject_type(entry->value) == QTYPE_QDICT) {
> > +            child = qdict_crumple((QDict *)entry->value, errp);
> 
> The cast works, but I'd prefer qobject_to_qdict() nonetheless.

Ok

> 
> > +            if (!child) {
> > +                goto error;
> > +            }
> > +
> > +            qdict_put_obj(tmp2, entry->key, child);
> > +        } else {
> > +            qobject_incref(entry->value);
> > +            qdict_put_obj(tmp2, entry->key, entry->value);
> > +        }
> > +
> > +        entry = next;
> > +    }
> > +    QDECREF(tmp1);
> > +
> > +    /* Step 3: detect if we need to turn our dict into list */
> 
> You could use qdict_array_entries(tmp2, "") > 0 for this.
> 
> If you do want to make { "0": 42, "2": 23 } and { "0": 42, "x": 23 }
> errors (my qdict_unflatten() just kept those QDicts), then you'd have to
> check on qdict_array_entries() error whether the QDict contained an
> integer key, but that would still be simpler than the following loop and
> the check in step 4.

If I use qdict_array_entries() then it does 2 O(N) iterations
of the input qdict, and to check the errors, I'll have todo
yet another iteration.  My inlined code here does everything
in a single O(N) iteration instead of 3. I think that's
compelling enough to reason to not use that function.



> > +    entry = qdict_first(tmp2);
> > +    while (entry != NULL) {
> > +        next = qdict_next(tmp2, entry);
> > +
> > +        errno = 0;
> > +        if (qemu_strtoll(entry->key, NULL, 10, &val) == 0) {
> > +            if (!dst) {
> > +                dst = (QObject *)qlist_new();
> > +                isList = true;
> > +            } else if (!isList) {
> > +                error_setg(errp,
> > +                           "Key '%s' is for a list, but previous key is "
> > +                           "for a dict", entry->key);
> > +                goto error;
> > +            }
> > +            listLen++;
> > +            if (val > listMax) {
> > +                listMax = val;
> > +            }
> > +        } else {
> > +            if (!dst) {
> > +                dst = (QObject *)tmp2;
> > +                qobject_incref(dst);
> > +                isList = false;
> > +            } else if (isList) {
> > +                error_setg(errp,
> > +                           "Key '%s' is for a dict, but previous key is "
> > +                           "for a list", entry->key);
> > +                goto error;
> > +            }
> > +        }
> > +
> > +        entry = next;
> > +    }
> > +
> > +    /* Step 4: Turn the dict into a list */
> 
> Why not just use qdict_array_split(tmp2, &dst);?

Again qdict_array_split() is a pretty inefficiently written
function. It does multiple nested iterations over its input
giving it O(n^2) time, compared to O(n) for my code here.


The only user of qdict_array_entries() and qdict_array_split()
is the block quorum driver. From a brief look at that code
I think it could probably be changed to just call this new
qdict_crumple() method, and those 2 inefficient functions
could be deleted, so we don't have duplication of code.

> > +    if (isList) {
> > +        if (listLen != (listMax + 1)) {
> > +            error_setg(errp, "List indexes are not continuous, "
> > +                       "saw %zu elements but %zu largest index",
> > +                       listLen, listMax);
> > +            goto error;
> > +        }
> > +
> > +        for (i = 0; i < listLen; i++) {
> > +            char *key = g_strdup_printf("%zu", i);
> > +
> > +            child = qdict_get(tmp2, key);
> > +            g_free(key);
> > +            if (!child) {
> > +                error_setg(errp, "Unexpected missing list entry %zu", i);
> > +                goto error;
> > +            }
> > +
> > +            qobject_incref(child);
> > +            qlist_append_obj((QList *)dst, child);
> > +        }
> > +    }
> > +    QDECREF(tmp2);
> > +
> > +    return dst;
> > +
> > + error:
> > +    QDECREF(tmp2);
> > +    QDECREF(tmp1);
> > +    qobject_decref(dst);
> > +    return NULL;
> > +}
> > +
> > +

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|



reply via email to

[Prev in Thread] Current Thread [Next in Thread]