Skip to content

Commit

Permalink
Improve field multi-resolution
Browse files Browse the repository at this point in the history
For example, this will allow resolving...

  record { int foo; }

...into...

  record {
    int foo;
    long @Aliases(["foo"]) fooAsLong;
    double @Aliases(["foo"]) fooAsDouble;
  }

Note that under the hood the field will be decoded each time. This is
the fastest way to produce independent values when the fields aren't
guaranteed immutable. Decoding is typically much faster than cloning the
objects so this shouldn't lead to performance issues.
  • Loading branch information
mtth committed Nov 23, 2017
1 parent bddc646 commit 102e78c
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 38 deletions.
55 changes: 28 additions & 27 deletions lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,13 @@ Type.prototype._createBranchConstructor = function () {
return Branch;
};

Type.prototype._peek = function (tap) {
var pos = tap.pos;
var val = this._read(tap);
tap.pos = pos;
return val;
};

Type.prototype._check = utils.abstractFunction;
Type.prototype._copy = utils.abstractFunction;
Type.prototype._deref = utils.abstractFunction;
Expand Down Expand Up @@ -2313,20 +2320,14 @@ RecordType.prototype._update = function (resolver, type, opts) {
innerArgs.push('undefined');
} else {
name = matches[0];
fieldResolver = resolvers[name];
if (!fieldResolver) {
resolvers[name] = {
resolver: field.type.createResolver(wFieldsMap[name].type, opts),
type: field.type, // Reader field type.
names: [field.name] // Reader field name.
};
fieldResolver = {
resolver: field.type.createResolver(wFieldsMap[name].type, opts),
name: field.name, // Reader field name.
};
if (!resolvers[name]) {
resolvers[name] = [fieldResolver];
} else {
if (field.type !== fieldResolver.type) {
// TODO: Support resolving a field to different types. This is
// slightly tricky since we'll need to reset the tap in between.
throw new Error('resolving the same field to distinct types');
}
fieldResolver.names.push(field.name);
resolvers[name].push(fieldResolver);
}
innerArgs.push(field.name);
}
Expand All @@ -2349,22 +2350,20 @@ RecordType.prototype._update = function (resolver, type, opts) {
}
field = type.fields[i];
name = field.name;
body += (~lazyIndex && i >= lazyIndex) ? ' ' : ' ';
fieldResolver = resolvers[name];
if (fieldResolver === undefined) {
args.push('t' + i);
if (resolvers[name] === undefined) {
body += (~lazyIndex && i >= lazyIndex) ? ' ' : ' ';
args.push('r' + i);
values.push(field.type);
body += 't' + i + '._skip(t);\n';
body += 'r' + i + '._skip(t);\n';
} else {
args.push('t' + i);
values.push(fieldResolver.resolver);
names = fieldResolver.names;
if (names.length === 1) {
body += 'var ' + names[0] + ' = ';
body += 't' + i + '._read(t);\n';
} else {
body += 'var ' + names.join(', ') + ';\n';
body += ' ' + names.join(' = ') + ' = ' + 't' + i + '._read(t);\n';
j = resolvers[name].length;
while (j--) {
body += (~lazyIndex && i >= lazyIndex) ? ' ' : ' ';
args.push('r' + i + 'f' + j);
fieldResolver = resolvers[name][j];
values.push(fieldResolver.resolver);
body += 'var ' + fieldResolver.name + ' = ';
body += 'r' + i + 'f' + j + '._' + (j ? 'peek' : 'read') + '(t);\n';
}
}
}
Expand Down Expand Up @@ -2762,6 +2761,8 @@ function Resolver(readerType) {
this.valuesType = null;
}

Resolver.prototype._peek = Type.prototype._peek;

Resolver.prototype.inspect = function () { return '<Resolver>'; };

/** Mutable hash container. */
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "avsc",
"version": "5.0.7",
"version": "5.1.0",
"description": "Avro for JavaScript",
"homepage": "https://github.com/mtth/avsc",
"keywords": [
Expand Down
20 changes: 11 additions & 9 deletions test/test_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,17 @@ suite('index', function () {
encoder.end({name: 'Bob', age: 33});
var n = 0;
encoder.on('finish', function () {
index.createFileDecoder(path)
.on('data', function (obj) {
n++;
assert(type.isValid(obj));
})
.on('end', function () {
assert.equal(n, 2);
cb();
});
setTimeout(function () { // Hack to wait until the file is flushed.
index.createFileDecoder(path)
.on('data', function (obj) {
n++;
assert(type.isValid(obj));
})
.on('end', function () {
assert.equal(n, 2);
cb();
});
}, 50);
});
});

Expand Down
27 changes: 26 additions & 1 deletion test/test_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ suite('types', function () {
assert.throws(function () { v2.createResolver(v1); });
});

test('resolve consolidated reads', function () {
test('resolve consolidated reads same type', function () {
var t1 = Type.forSchema({
type: 'record',
name: 'Person',
Expand All @@ -1832,6 +1832,31 @@ suite('types', function () {
);
});

test('resolve consolidated reads different types', function () {
var t1 = Type.forSchema({
type: 'record',
name: 'Person',
fields: [
{name: 'phone', type: 'int'},
]
});
var t2 = Type.forSchema({
type: 'record',
name: 'Person',
fields: [
{name: 'phoneLong', type: 'long', aliases: ['phone']},
{name: 'phoneDouble', type: 'double', aliases: ['phone']},
{name: 'phone', type: 'int'},
]
});
var rsv = t2.createResolver(t1);
var buf = t1.toBuffer({phone: 123});
assert.deepEqual(
t2.fromBuffer(buf, rsv),
{phoneLong: 123, phoneDouble: 123, phone: 123}
);
});

test('getName', function () {
var t = Type.forSchema({
type: 'record',
Expand Down

0 comments on commit 102e78c

Please sign in to comment.