Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optional insert operation #15

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 47 additions & 7 deletions lib/json0.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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;
}
}
}
}
}

Expand Down
13 changes: 12 additions & 1 deletion randomizer/json0.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,18 @@ json0.generateRandomOp = (data) ->
# Object
k = randomKey(operand)

if randomReal() > 0.5 or not k?
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
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?) and not json0._testOin
# Insert
k = randomNewKey(operand)
obj = randomThing()
Expand Down
29 changes: 28 additions & 1 deletion test/json0.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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) ->
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -384,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