From ee868ff6739ccf1f26407ff4db16659fe92ae0f6 Mon Sep 17 00:00:00 2001 From: Dmitry Uvarov Date: Fri, 25 Apr 2014 20:11:12 +0400 Subject: [PATCH 1/3] optional insert operation --- lib/json0.js | 54 +++++++++++++++++++++++++++++++++++++++++------ test/json0.coffee | 24 ++++++++++++++++++++- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 593b10c..8b23607 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -70,6 +70,7 @@ json.invertComponent = function(c) { if (c.li !== void 0) c_.ld = c.li; if (c.ld !== void 0) c_.li = c.ld; if (c.na !== void 0) c_.na = -c.na; + if (c.oin !== void 0) c_.od = c.oin; if (c.lm !== void 0) { c_.lm = c.p[c.p.length-1]; @@ -215,6 +216,15 @@ json.apply = function(snapshot, op) { delete elem[key]; } + // Init value + else if (c.oin !== void 0) { + json.checkObj(elem); + // set value if not exists in the object + if(elem[key] === void 0) { + elem[key] = c.oin; + } + } + else { throw new Error('invalid / missing instruction in op'); } @@ -301,19 +311,23 @@ json.append = function(dest,c) { } else { dest.pop(); } - } else if (last.od !== undefined && last.oi === undefined && c.oi !== undefined && c.od === undefined) { - last.oi = c.oi; - } else if (last.oi !== undefined && c.od !== undefined) { + } else if (last.od !== undefined && last.oi === undefined && last.oin === undefined && (c.oi !== undefined || c.oin !== undefined) && c.od === undefined) { + last.oi = c.oi === undefined ? c.oin : c.oi; + } else if ((last.oi !== undefined || last.oin !== undefined) && c.od !== undefined) { // The last path component inserted something that the new component deletes (or replaces). // Just merge them. if (c.oi !== undefined) { + delete last.oin; // just in case we had it, overwrite with object insert last.oi = c.oi; } else if (last.od !== undefined) { delete last.oi; + delete last.oin; } else { // An insert directly followed by a delete turns into a no-op and can be removed. dest.pop(); } + } else if ((last.oi !== undefined || last.oin !== undefined) && c.oin !== undefined) { + // last component has optional insert over previous insert - ignore it } else if (c.lm !== undefined && c.p[c.p.length - 1] === c.lm) { // don't do anything } else { @@ -602,7 +616,7 @@ json.transformComponent = function(dest, c, otherC, type) { } else if (otherC.oi !== void 0 && otherC.od !== void 0) { if (c.p[common] === otherC.p[common]) { - if (c.oi !== void 0 && commonOperand) { + if ((c.oi !== void 0 || c.oin !== void 0) && commonOperand) { // we inserted where someone else replaced if (type === 'right') { // left wins @@ -617,12 +631,23 @@ json.transformComponent = function(dest, c, otherC, type) { } } } else if (otherC.oi !== void 0) { - if (c.oi !== void 0 && c.p[common] === otherC.p[common]) { + if ((c.oi !== void 0 || c.oin !== void 0) && c.p[common] === otherC.p[common]) { // left wins if we try to insert at the same place if (type === 'left') { - json.append(dest,{p: c.p, od:otherC.oi}); + if (c.oi !== void 0) { + json.append(dest,{p: c.p, od:otherC.oi}); + } else { + return dest; + } } else { - return dest; + if(c.oin !== void 0) { + // we need to rebuild operation into object insert + json.append(dest,{p: c.p, od:c.oin}); + c.oi = otherC.oi; + delete c.oin; + } else { + return dest; + } } } } else if (otherC.od !== void 0) { @@ -635,6 +660,21 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; } } + } else if (otherC.oin !== void 0) { + if ((c.oi !== void 0 || c.oin !== void 0) && c.p[common] === otherC.p[common]) { + // left wins if we try to insert at the same place + if (type === 'left') { + if (c.oi !== void 0) { + json.append(dest,{p: c.p, od:otherC.oin}); + } else { + return dest; + } + } else { + if(c.oi !== void 0 || c.oin !== void 0) { + return dest; + } + } + } } } diff --git a/test/json0.coffee b/test/json0.coffee index 4fdde59..465bea4 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -24,7 +24,18 @@ genTests = (type) -> assert.deepEqual [{p:['foo'], od:1},{p:['bar'], oi:2}], type.compose [{p:['foo'],od:1}],[{p:['bar'],oi:2}] it 'merges od+oi, od+oi -> od+oi', -> assert.deepEqual [{p:['foo'], od:1, oi:2}], type.compose [{p:['foo'],od:1,oi:3}],[{p:['foo'],od:3,oi:2}] - + it 'oi,oin -> oi', -> + assert.deepEqual [{p:['foo'], oi:1}], type.compose [{p:['foo'],oi:1}],[{p:['foo'],oin:2}] + it 'oin,oin` -> oin', -> + assert.deepEqual [{p:['foo'], oin:1}], type.compose [{p:['foo'],oin:1}],[{p:['foo'],oin:2}] + it 'od,oin -> od+oi', -> + assert.deepEqual [{p:['foo'], od:1, oi:2}], type.compose [{p:['foo'],od:1}],[{p:['foo'],oin:2}] + it 'od+oi,oin -> od+oi', -> + assert.deepEqual [{p:['foo'], od:1, oi:3}], type.compose [{p:['foo'],od:1,oi:3}],[{p:['foo'],oin:2}] + + describe '#invert()', -> + it 'optional insert inverts to delete', -> + assert.deepEqual [{p:['foo'], od:1}], type.invert [{p:['foo'], oin:1}] describe '#transform()', -> it 'returns sane values', -> t = (op1, op2) -> @@ -316,6 +327,7 @@ genTests = (type) -> assert.deepEqual {x:'a', y:'b'}, type.apply {x:'a'}, [{p:['y'], oi:'b'}] assert.deepEqual {}, type.apply {x:'a'}, [{p:['x'], od:'a'}] assert.deepEqual {x:'b'}, type.apply {x:'a'}, [{p:['x'], od:'a', oi:'b'}] + assert.deepEqual {x:'a'}, type.apply {x:'a'}, [{p:['x'], oin: 'b'}] it 'Ops on deleted elements become noops', -> assert.deepEqual [], type.transform [{p:[1, 0], si:'hi'}], [{p:[1], od:'x'}], 'left' @@ -374,6 +386,16 @@ genTests = (type) -> assert.deepEqual [], type.transform [{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'left' assert.deepEqual [], type.transform [{p:['k'], od:'x'}], [{p:['k'], od:'x'}], 'right' + it 'Optional insert vs insert', -> + assert.deepEqual [], type.transform [{p:[1], oin:'a'}], [{p:[1], oi:'b'}], 'left' + assert.deepEqual [{p:[1], od:'a', oi:'b'}], type.transform [{p:[1], oin:'a'}], [{p:[1], oi:'b'}], 'right' + + it 'Optional vs optional, no one wins', -> + assert.deepEqual [], type.transform [{p:[1], oin:'a'}], [{p:[1], oin:'b'}], 'left' + assert.deepEqual [], type.transform [{p:[1], oin:'a'}], [{p:[1], oin:'b'}], 'right' + + + describe 'randomizer', -> it 'passes', -> @slow 6000 From e2972fdc25fedccdd88c69907044abf3098dfbd3 Mon Sep 17 00:00:00 2001 From: Dmitry Uvarov Date: Thu, 26 Jun 2014 15:40:30 +0400 Subject: [PATCH 2/3] fixed randomizer test for "oin" operation --- randomizer/json0.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/randomizer/json0.coffee b/randomizer/json0.coffee index 6d56cc6..3eeb953 100644 --- a/randomizer/json0.coffee +++ b/randomizer/json0.coffee @@ -159,7 +159,18 @@ json0.generateRandomOp = (data) -> # Object k = randomKey(operand) - if randomReal() > 0.5 or not k? + if randomReal() < 0.5 and not k? and operand[k] is undefined + # "oin" is only valid when client knows there is no value for the key and + # someone can initiate the same key. If we try to insert over existing value + # (and operation will be ignored since key exists), invertion will fail + k = randomNewKey(operand) + # All clients should start with the same initial value + obj = {alwaysSameInitialValue: k} + + path.push(k); + operand[k] = obj + {p:path, oin:clone(obj)} + else if randomReal() > 0.7 or not k? # Insert k = randomNewKey(operand) obj = randomThing() From 3b8b33274c754e0bb5cd42738f895530441c1c7e Mon Sep 17 00:00:00 2001 From: Dmitry Uvarov Date: Fri, 27 Jun 2014 14:08:23 +0400 Subject: [PATCH 3/3] separate 'oin' test from 'oi' see PR discussion for details --- randomizer/json0.coffee | 4 ++-- test/json0.coffee | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/randomizer/json0.coffee b/randomizer/json0.coffee index 3eeb953..3748767 100644 --- a/randomizer/json0.coffee +++ b/randomizer/json0.coffee @@ -159,7 +159,7 @@ json0.generateRandomOp = (data) -> # Object k = randomKey(operand) - if randomReal() < 0.5 and not k? and operand[k] is undefined + if json0._testOin and not k? and operand[k] is undefined # "oin" is only valid when client knows there is no value for the key and # someone can initiate the same key. If we try to insert over existing value # (and operation will be ignored since key exists), invertion will fail @@ -170,7 +170,7 @@ json0.generateRandomOp = (data) -> path.push(k); operand[k] = obj {p:path, oin:clone(obj)} - else if randomReal() > 0.7 or not k? + else if (randomReal() > 0.7 or not k?) and not json0._testOin # Insert k = randomNewKey(operand) obj = randomThing() diff --git a/test/json0.coffee b/test/json0.coffee index 465bea4..fbd5479 100644 --- a/test/json0.coffee +++ b/test/json0.coffee @@ -406,6 +406,11 @@ genTests = (type) -> randomizer type, 1000 delete type._testStringSubtype + it 'passes with `oin` operation', -> + type._testOin = true # hack + randomizer type, 1000 + delete type._testOin + describe 'json', -> describe 'native type', -> genTests nativetype #exports.webclient = genTests require('../helpers/webclient').types.json