summaryrefslogtreecommitdiff
path: root/ext/librethinkdbxx/test/upstream/changefeeds
diff options
context:
space:
mode:
Diffstat (limited to 'ext/librethinkdbxx/test/upstream/changefeeds')
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/edge.yaml142
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/geo.rb.yaml27
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/idxcopy.yaml38
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/include_states.yaml58
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/point.yaml147
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/sindex.yaml50
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/squash.yaml62
-rw-r--r--ext/librethinkdbxx/test/upstream/changefeeds/table.yaml101
8 files changed, 625 insertions, 0 deletions
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/edge.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/edge.yaml
new file mode 100644
index 00000000..e66b6847
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/edge.yaml
@@ -0,0 +1,142 @@
+desc: Test edge cases of changefeed operations
+table_variable_name: tbl
+tests:
+
+ - def: common_prefix = r.expr([0,1,2,3,4,5,6,7,8])
+
+ - js: tbl.indexCreate('sindex', function (row) { return common_prefix.append(row('value')); })
+ py: tbl.index_create('sindex', lambda row:common_prefix.append(row['value']))
+ rb: tbl.index_create('sindex'){ |row| common_prefix.append(row['value']) }
+ ot: ({'created':1})
+ - cd: tbl.index_wait('sindex')
+
+ # create target values
+ - cd: pre = r.range(7).coerce_to('array').add(r.range(10,70).coerce_to('array')).append(100).map(r.row.coerce_to('string'))
+ rb: pre = r.range(7).coerce_to('array').add(r.range(10,70).coerce_to('array')).append(100).map{ |row| row.coerce_to('string') }
+ - cd: mid = r.range(2,9).coerce_to('array').add(r.range(20,90).coerce_to('array')).map(r.row.coerce_to('string'))
+ rb: mid = r.range(2,9).coerce_to('array').add(r.range(20,90).coerce_to('array')).map{ |row| row.coerce_to('string') }
+ - cd: post = r.range(3,10).coerce_to('array').add(r.range(30,100).coerce_to('array')).map(r.row.coerce_to('string'))
+ rb: post = r.range(3,10).coerce_to('array').add(r.range(30,100).coerce_to('array')).map{ |row| row.coerce_to('string') }
+
+ - cd: erroredres = r.range(2).coerce_to('array').add(r.range(10, 20).coerce_to('array')).append(100).map(r.row.coerce_to('string'))
+ rb: erroredres = r.range(2).coerce_to('array').add(r.range(10, 20).coerce_to('array')).append(100).map{ |val| val.coerce_to('string') }
+
+ # Start overlapping changefeeds
+ - js: pre_changes = tbl.between(r.minval, commonPrefix.append('7'), {index:'sindex'}).changes({squash:false}).limit(pre.length)('new_val')('value')
+ py: pre_changes = tbl.between(r.minval, common_prefix.append('7'), index='sindex').changes(squash=False).limit(len(pre))['new_val']['value']
+ rb: pre_changes = tbl.between(r.minval, common_prefix.append('7'), index:'sindex').changes(squash:false).limit(pre.length)['new_val']['value']
+ - js: mid_changes = tbl.between(commonPrefix.append('2'), common_prefix.append('9'), {index:'sindex'}).changes({squash:false}).limit(post.length)('new_val')('value')
+ py: mid_changes = tbl.between(common_prefix.append('2'), common_prefix.append('9'), index='sindex').changes(squash=False).limit(len(post))['new_val']['value']
+ rb: mid_changes = tbl.between(common_prefix.append('2'), common_prefix.append('9'), index:'sindex').changes(squash:false).limit(post.length)['new_val']['value']
+ - js: post_changes = tbl.between(commonPrefix.append('3'), r.maxval, {index:'sindex'}).changes({squash:false}).limit(mid.length)('new_val')('value')
+ py: post_changes = tbl.between(common_prefix.append('3'), r.maxval, index='sindex').changes(squash=False).limit(len(mid))['new_val']['value']
+ rb: post_changes = tbl.between(common_prefix.append('3'), r.maxval, index:'sindex').changes(squash:false).limit(mid.length)['new_val']['value']
+
+ # Start changefeeds with non-existence errors
+
+ - js: premap_changes1 = tbl.map(r.branch(r.row('value').lt('2'), r.row, r.row("dummy"))).changes({squash:false}).limit(erroredres.length)('new_val')('value')
+ py: premap_changes1 = tbl.map(r.branch(r.row['value'].lt('2'), r.row, r.row["dummy"])).changes(squash=False).limit(len(erroredres))['new_val']['value']
+ rb: premap_changes1 = tbl.map{ |row| r.branch(row['value'].lt('2'), row, row["dummy"]) }.changes(squash:false).limit(erroredres.length)['new_val']['value']
+
+ - js: postmap_changes1 = tbl.changes({squash:false}).map(r.branch(r.row('new_val')('value').lt('2'), r.row, r.row("dummy"))).limit(erroredres.length)('new_val')('value')
+ py: postmap_changes1 = tbl.changes(squash=False).map(r.branch(r.row['new_val']['value'].lt('2'), r.row, r.row["dummy"])).limit(len(erroredres))['new_val']['value']
+ rb: postmap_changes1 = tbl.changes(squash:false).map{ |row| r.branch(row['new_val']['value'].lt('2'), row, row["dummy"]) }.limit(erroredres.length)['new_val']['value']
+
+ - js: prefilter_changes1 = tbl.filter(r.branch(r.row('value').lt('2'), true, r.row("dummy"))).changes({squash:false}).limit(erroredres.length)('new_val')('value')
+ py: prefilter_changes1 = tbl.filter(r.branch(r.row['value'].lt('2'), True, r.row["dummy"])).changes(squash=False).limit(len(erroredres))['new_val']['value']
+ rb: prefilter_changes1 = tbl.filter{ |row| r.branch(row['value'].lt('2'), true, row["dummy"]) }.changes(squash:false).limit(erroredres.length)['new_val']['value']
+
+ - js: postfilter_changes1 = tbl.changes({squash:false}).filter(r.branch(r.row('new'+'_'+'val')('value').lt('2'), true, r.row("dummy"))).limit(erroredres.length)('new_val')('value')
+ py: postfilter_changes1 = tbl.changes(squash=False).filter(r.branch(r.row['new_val']['value'].lt('2'), True, r.row["dummy"])).limit(len(erroredres))['new_val']['value']
+ rb: postfilter_changes1 = tbl.changes(squash:false).filter{ |row| r.branch(row['new_val']['value'].lt('2'), true, row["dummy"]) }.limit(erroredres.length)['new_val']['value']
+
+ # Start changefeeds with runtime errors
+
+ - js: premap_changes2 = tbl.map(r.branch(r.row('value').lt('2'), r.row, r.expr([]).nth(1))).changes({squash:false}).limit(erroredres.length)('new_val')('value')
+ py: premap_changes2 = tbl.map(r.branch(r.row['value'].lt('2'), r.row, r.expr([])[1])).changes(squash=False).limit(len(erroredres))['new_val']['value']
+ rb: premap_changes2 = tbl.map{ |row| r.branch(row['value'].lt('2'), row, r.expr([])[1]) }.changes(squash:false).limit(erroredres.length)['new_val']['value']
+
+ - js: postmap_changes2 = tbl.changes({squash:false}).map(r.branch(r.row('new'+'_'+'val')('value').lt('2'), r.row, r.expr([]).nth(1))).limit(erroredres.length)('new_val')('value')
+ py: postmap_changes2 = tbl.changes(squash=False).map(r.branch(r.row['new_val']['value'].lt('2'), r.row, r.expr([])[1])).limit(len(erroredres))['new_val']['value']
+ rb: postmap_changes2 = tbl.changes(squash:false).map{ |row| r.branch(row['new_val']['value'].lt('2'), row, r.expr([])[1]) }.limit(erroredres.length)['new_val']['value']
+
+ - js: prefilter_changes2 = tbl.filter(r.branch(r.row('value').lt('2'), true, r.expr([]).nth(1))).changes({squash:false}).limit(erroredres.length)('new_val')('value')
+ py: prefilter_changes2 = tbl.filter(r.branch(r.row['value'].lt('2'), True, r.expr([])[1])).changes(squash=False).limit(len(erroredres))['new_val']['value']
+ rb: prefilter_changes2 = tbl.filter{ |row| r.branch(row['value'].lt('2'), true, r.expr([])[1]) }.changes(squash:false).limit(erroredres.length)['new_val']['value']
+
+ - js: postfilter_changes2 = tbl.changes({squash:false}).filter(r.branch(r.row('new'+'_'+'val')('value').lt('2'), true, r.expr([]).nth(1))).limit(erroredres.length)('new_val')('value')
+ py: postfilter_changes2 = tbl.changes(squash=False).filter(r.branch(r.row['new_val']['value'].lt('2'), True, r.expr([])[1])).limit(len(erroredres))['new_val']['value']
+ rb: postfilter_changes2 = tbl.changes(squash:false).filter{ |row| r.branch(row['new_val']['value'].lt('2'), true, r.expr([])[1]) }.limit(erroredres.length)['new_val']['value']
+
+ # Start non-deterministic changefeeds - very small chance of these hanging due to not enough results
+ - def:
+ py: nondetermmap = r.branch(r.random().gt(0.5), r.row, r.error("dummy"))
+ js: nondetermmap = function (row) { return r.branch(r.random().gt(0.5), row, r.error("dummy")); }
+ rb: nondetermmap = Proc.new { |row| r.branch(r.random().gt(0.5), row, r.error("dummy")) }
+ - def:
+ py: nondetermfilter = lambda row:r.random().gt(0.5)
+ js: nondetermfilter = function (row) { return r.random().gt(0.5); }
+ rb: nondetermfilter = Proc.new { |row| r.random().gt(0.5) }
+
+ - rb: tbl.map(nondetermmap).changes(squash:false)
+ js: tbl.map(nondetermmap).changes({squash:false})
+ py: tbl.map(nondetermmap).changes(squash=False)
+ ot: err('ReqlQueryLogicError', 'Cannot call `changes` after a non-deterministic function.')
+
+ - rb: postmap_changes3 = tbl.changes(squash:false).map(nondetermmap).limit(100)
+ js: postmap_changes3 = tbl.changes({squash:false}).map(nondetermmap).limit(100)
+ py: postmap_changes3 = tbl.changes(squash=False).map(nondetermmap).limit(100)
+
+ - rb: tbl.filter(nondetermfilter).changes(squash:false)
+ js: tbl.filter(nondetermfilter).changes({squash:false})
+ py: tbl.filter(nondetermfilter).changes(squash=False)
+ ot: err('ReqlQueryLogicError', 'Cannot call `changes` after a non-deterministic function.')
+
+ - rb: postfilter_changes3 = tbl.changes(squash:false).filter(nondetermfilter).limit(4)
+ js: postfilter_changes3 = tbl.changes({squash:false}).filter(nondetermfilter).limit(4)
+ py: postfilter_changes3 = tbl.changes(squash=False).filter(nondetermfilter).limit(4)
+
+ # Insert several rows that will and will not be returned
+ - cd: tbl.insert(r.range(101).map({'id':r.uuid().coerce_to('binary').slice(0,r.random(4,24)).coerce_to('string'),'value':r.row.coerce_to('string')}))
+ rb: tbl.insert(r.range(101).map{ |row| {'id'=>r.uuid().coerce_to('binary').slice(0,r.random(4,24)).coerce_to('string'),'value'=>row.coerce_to('string')}})
+ ot: ({'skipped':0,'deleted':0,'unchanged':0,'errors':0,'replaced':0,'inserted':101})
+
+ # Check that our limited watchers have been satified
+ - cd: pre_changes
+ ot: bag(pre)
+
+ - cd: mid_changes
+ ot: bag(mid)
+
+ - cd: post_changes
+ ot: bag(post)
+
+ - cd: premap_changes1
+ ot: bag(erroredres)
+
+ - cd: premap_changes2
+ ot: bag(erroredres)
+
+ - cd: postmap_changes1
+ ot: err('ReqlNonExistenceError', "No attribute `dummy` in object:")
+
+ - cd: postmap_changes2
+ ot: err('ReqlNonExistenceError', "Index out of bounds:" + " 1")
+
+ - cd: postmap_changes3
+ ot: err('ReqlUserError', "dummy")
+
+ - cd: prefilter_changes1
+ ot: bag(erroredres)
+
+ - cd: prefilter_changes2
+ ot: bag(erroredres)
+
+ - cd: postfilter_changes1
+ ot: bag(erroredres)
+
+ - cd: postfilter_changes2
+ ot: bag(erroredres)
+
+ - ot: arrlen(postfilter_changes3)
+ ot: 4
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/geo.rb.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/geo.rb.yaml
new file mode 100644
index 00000000..7755ff96
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/geo.rb.yaml
@@ -0,0 +1,27 @@
+desc: Geo indexed changefeed operations
+table_variable_name: tbl
+tests:
+ - rb: tbl.index_create('L', {geo: true})
+ ot: partial({'created': 1})
+
+ - rb: tbl.index_wait().count
+ ot: 1
+
+ - def: obj11 = {id: "11", L: r.point(1,1)}
+ - def: obj12 = {id: "12", L: r.point(1,2)}
+ - def: obj21 = {id: "21", L: r.point(2,1)}
+ - def: obj22 = {id: "22", L: r.point(2,2)}
+
+ # A distance of 130,000 meters from 1,1 is enough to cover 1,2 and 2,1 (~110km
+ # distance) but not 2,2 (~150km distance.)
+ #
+ # This is useful because the S2LatLngRect bounding box passed to the shards contains
+ # 2,2 yet it should not be returned in the changefeed results.
+ - rb: feed = tbl.get_intersecting(r.circle(r.point(1,1), 130000), {index: "L"}).get_field("id").changes(include_initial: true)
+
+ - rb: tbl.insert([obj11, obj12, obj21, obj22])
+ ot: partial({'errors': 0, 'inserted': 4})
+
+ - rb: fetch(feed, 3)
+ ot: bag([{"new_val" => "11", "old_val" => nil}, {"new_val" => "12", "old_val" => nil}, {"new_val" => "21", "old_val" => nil}])
+
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/idxcopy.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/idxcopy.yaml
new file mode 100644
index 00000000..f7110ab1
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/idxcopy.yaml
@@ -0,0 +1,38 @@
+desc: Test duplicate indexes with squashing
+table_variable_name: tbl
+tests:
+ - cd: tbl.index_create('a')
+ ot: partial({'created':1})
+ - cd: tbl.index_wait('a')
+
+ - py: feed = tbl.order_by(index='a').limit(10).changes(squash=2)
+ rb: feed = tbl.orderby(index:'a').limit(10).changes(squash:2).limit(9)
+ js: feed = tbl.orderBy({index:'a'}).limit(10).changes({squash:2}).limit(9)
+ runopts:
+ # limit the number of pre-fetched rows
+ max_batch_rows: 1
+
+ - py: tbl.insert(r.range(0, 12).map({'id':r.row, 'a':5}))
+ rb: tbl.insert(r.range(0, 12).map{|row| {'id':row, 'a':5}})
+ js: tbl.insert(r.range(0, 12).map(function(row){ return {'id':row, 'a':5}; }))
+ ot: partial({'inserted':12, 'errors':0})
+
+ - py: tbl.get_all(1, 8, 9, index='id').delete()
+ rb: tbl.get_all(1, 8, 9, index:'id').delete()
+ js: tbl.get_all(1, 8, 9, {index:'id'}).delete()
+ ot: partial({'deleted':3, 'errors':0})
+
+ # should be replaced with a noreplyWait
+ - cd: wait(2)
+
+ - cd: fetch(feed)
+ ot: bag([
+ {"new_val":{"a":5, "id":0}, "old_val":nil},
+ {"new_val":{"a":5, "id":2}, "old_val":nil},
+ {"new_val":{"a":5, "id":3}, "old_val":nil},
+ {"new_val":{"a":5, "id":4}, "old_val":nil},
+ {"new_val":{"a":5, "id":5}, "old_val":nil},
+ {"new_val":{"a":5, "id":6}, "old_val":nil},
+ {"new_val":{"a":5, "id":7}, "old_val":nil},
+ {"new_val":{"a":5, "id":10}, "old_val":nil},
+ {"new_val":{"a":5, "id":11}, "old_val":nil}])
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/include_states.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/include_states.yaml
new file mode 100644
index 00000000..68dadb08
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/include_states.yaml
@@ -0,0 +1,58 @@
+desc: Test `include_states`
+table_variable_name: tbl
+tests:
+ - py: tbl.changes(squash=true, include_states=true).limit(1)
+ rb: tbl.changes(squash:true, include_states:true).limit(1)
+ js: tbl.changes({squash:true, includeStates:true}).limit(1)
+ ot: [{'state':'ready'}]
+
+ - py: tbl.get(0).changes(squash=true, include_states=true, include_initial=true).limit(3)
+ rb: tbl.get(0).changes(squash:true, include_states:true, include_initial:true).limit(3)
+ js: tbl.get(0).changes({squash:true, includeStates:true, includeInitial:true}).limit(3)
+ ot: [{'state':'initializing'}, {'new_val':null}, {'state':'ready'}]
+
+ - py: tbl.order_by(index='id').limit(10).changes(squash=true, include_states=true, include_initial=true).limit(2)
+ rb: tbl.order_by(index:'id').limit(10).changes(squash:true, include_states:true, include_initial:true).limit(2)
+ js: tbl.orderBy({index:'id'}).limit(10).changes({squash:true, includeStates:true, includeInitial:true}).limit(2)
+ ot: [{'state':'initializing'}, {'state':'ready'}]
+
+ - cd: tbl.insert({'id':1})
+
+ - py: tbl.order_by(index='id').limit(10).changes(squash=true, include_states=true, include_initial=true).limit(3)
+ rb: tbl.order_by(index:'id').limit(10).changes(squash:true, include_states:true, include_initial:true).limit(3)
+ js: tbl.orderBy({index:'id'}).limit(10).changes({squash:true, includeStates:true, includeInitial:true}).limit(3)
+ ot: [{'state':'initializing'}, {'new_val':{'id':1}}, {'state':'ready'}]
+
+ - py: tblchanges = tbl.changes(squash=true, include_states=true)
+ rb: tblchanges = tbl.changes(squash:true, include_states:true)
+ js: tblchanges = tbl.changes({squash:true, includeStates:true})
+
+ - cd: tbl.insert({'id':2})
+
+ - cd: fetch(tblchanges, 2)
+ ot: [{'state':'ready'},{'new_val':{'id':2},'old_val':null}]
+
+ - py: getchanges = tbl.get(2).changes(include_states=true, include_initial=true)
+ rb: getchanges = tbl.get(2).changes(include_states:true, include_initial:true)
+ js: getchanges = tbl.get(2).changes({includeStates:true, includeInitial:true})
+
+ - cd: tbl.get(2).update({'a':1})
+
+ - cd: fetch(getchanges, 4)
+ ot: [{'state':'initializing'}, {'new_val':{'id':2}}, {'state':'ready'}, {'old_val':{'id':2},'new_val':{'id':2,'a':1}}]
+
+ - py: limitchanges = tbl.order_by(index='id').limit(10).changes(include_states=true, include_initial=true)
+ rb: limitchanges = tbl.order_by(index:'id').limit(10).changes(include_states:true, include_initial:true)
+ js: limitchanges = tbl.orderBy({index:'id'}).limit(10).changes({includeStates:true, includeInitial:true})
+
+ - py: limitchangesdesc = tbl.order_by(index=r.desc('id')).limit(10).changes(include_states=true, include_initial=true)
+ rb: limitchangesdesc = tbl.order_by(index:r.desc('id')).limit(10).changes(include_states:true, include_initial:true)
+ js: limitchangesdesc = tbl.orderBy({index:r.desc('id')}).limit(10).changes({includeStates:true, includeInitial:true})
+
+ - cd: tbl.insert({'id':3})
+
+ - cd: fetch(limitchanges, 5)
+ ot: [{'state':'initializing'}, {'new_val':{'id':1}}, {'new_val':{'a':1, 'id':2}}, {'state':'ready'}, {'old_val':null, 'new_val':{'id':3}}]
+
+ - cd: fetch(limitchangesdesc, 5)
+ ot: [{'state':'initializing'}, {'new_val':{'a':1, 'id':2}}, {'new_val':{'id':1}}, {'state':'ready'}, {'old_val':null, 'new_val':{'id':3}}]
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/point.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/point.yaml
new file mode 100644
index 00000000..a444a73d
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/point.yaml
@@ -0,0 +1,147 @@
+desc: Test point changebasics
+table_variable_name: tbl
+tests:
+
+ # -- basic
+
+ # start a feed
+
+ - cd: basic = tbl.get(1).changes({include_initial:true})
+ py: basic = tbl.get(1).changes(include_initial=True)
+
+ # - inital return
+
+ - cd: fetch(basic, 1)
+ ot: [{'new_val':null}]
+
+ # - inserts
+
+ - cd: tbl.insert({'id':1})
+ ot: partial({'errors':0, 'inserted':1})
+
+ - cd: fetch(basic, 1)
+ ot: [{'old_val':null, 'new_val':{'id':1}}]
+
+ # - updates
+
+ - cd: tbl.get(1).update({'update':1})
+ ot: partial({'errors':0, 'replaced':1})
+
+ - cd: fetch(basic, 1)
+ ot: [{'old_val':{'id':1}, 'new_val':{'id':1,'update':1}}]
+
+ # - deletions
+
+ - cd: tbl.get(1).delete()
+ ot: partial({'errors':0, 'deleted':1})
+
+ - cd: fetch(basic, 1)
+ ot: [{'old_val':{'id':1,'update':1}, 'new_val':null}]
+
+ # - closing
+
+ - cd: basic.close()
+ rb: def pass; end
+ # the ruby test driver currently has to mangle cursors, so we can't close them properly
+
+ # -- filter
+
+ - py: filter = tbl.get(1).changes(squash=false,include_initial=True).filter(r.row['new_val']['update'].gt(2))['new_val']['update']
+ rb: filter = tbl.get(1).changes(squash:false,include_initial:true).filter{|row| row['new_val']['update'].gt(2)}['new_val']['update']
+ js: filter = tbl.get(1).changes({squash:false,include_initial:true}).filter(r.row('new_val')('update').gt(2))('new_val')('update')
+
+ - cd: tbl.insert({'id':1, 'update':1})
+ - cd: tbl.get(1).update({'update':4})
+ - cd: tbl.get(1).update({'update':1})
+ - cd: tbl.get(1).update({'update':7})
+
+ - cd: fetch(filter, 2)
+ ot: [4,7]
+
+ # -- pluck on values
+
+ - py: pluck = tbl.get(3).changes(squash=false,include_initial=True).pluck({'new_val':['red', 'blue']})['new_val']
+ rb: pluck = tbl.get(3).changes(squash:false,include_initial:true).pluck({'new_val':['red', 'blue']})['new_val']
+ js: pluck = tbl.get(3).changes({squash:false,include_initial:true}).pluck({'new_val':['red', 'blue']})('new_val')
+
+ - cd: tbl.insert({'id':3, 'red':1, 'green':1})
+ ot: partial({'errors':0, 'inserted':1})
+ - cd: tbl.get(3).update({'blue':2, 'green':3})
+ ot: partial({'errors':0, 'replaced':1})
+ - cd: tbl.get(3).update({'green':4})
+ ot: partial({'errors':0, 'replaced':1})
+ - cd: tbl.get(3).update({'blue':4})
+ ot: partial({'errors':0, 'replaced':1})
+
+ - cd: fetch(pluck, 4)
+ ot: [{'red': 1}, {'blue': 2, 'red': 1}, {'blue': 2, 'red': 1}, {'blue': 4, 'red': 1}]
+
+ # -- virtual tables
+
+ # - rethinkdb._debug_scratch
+
+ - def: dtbl = r.db('rethinkdb').table('_debug_scratch')
+
+ - cd: debug = dtbl.get(1).changes({include_initial:true})
+ py: debug = dtbl.get(1).changes(include_initial=True)
+
+ - cd: fetch(debug, 1)
+ ot: [{'new_val':null}]
+
+ - cd: dtbl.insert({'id':1})
+ ot: partial({'errors':0, 'inserted':1})
+ - cd: fetch(debug, 1)
+ ot: [{'old_val':null, 'new_val':{'id':1}}]
+
+ - cd: dtbl.get(1).update({'update':1})
+ ot: partial({'errors':0, 'replaced':1})
+ - cd: fetch(debug, 1)
+ ot: [{'old_val':{'id':1}, 'new_val':{'id':1,'update':1}}]
+
+ - cd: dtbl.get(1).delete()
+ ot: partial({'errors':0, 'deleted':1})
+ - cd: fetch(debug, 1)
+ ot: [{'old_val':{'id':1,'update':1}, 'new_val':null}]
+
+ - cd: dtbl.insert({'id':5, 'red':1, 'green':1})
+ ot: {'skipped':0, 'deleted':0, 'unchanged':0, 'errors':0, 'replaced':0, 'inserted':1}
+ - py: dtblPluck = dtbl.get(5).changes(include_initial=True).pluck({'new_val':['red', 'blue']})['new_val']
+ rb: dtblPluck = dtbl.get(5).changes(include_initial:true).pluck({'new_val':['red', 'blue']})['new_val']
+ js: dtblPluck = dtbl.get(5).changes({include_initial:true}).pluck({'new_val':['red', 'blue']})('new_val')
+
+ # disabled because inital value is not being reported correctly, so goes missing. see #3723
+ - cd: fetch(dtblPluck, 1)
+ ot: [{'red':1}]
+
+ - cd: dtbl.get(5).update({'blue':2, 'green':3})
+ ot: partial({'errors':0, 'replaced':1})
+
+ - cd: fetch(dtblPluck, 1)
+ ot: [{'blue':2, 'red':1}]
+
+ # - rethinkdb.table_status bad optargs
+
+ # disabled, re-enable once #3725 is done
+ # - py: r.db('rethinkdb').table('table_status').changes(squash=False)
+ # rb: r.db('rethinkdb').table('table_status').changes(squash:False)
+ # js: r.db('rethinkdb').table('table_status').changes({squash:False})
+ # ot: err('ReqlRuntimeError', 'replace with error message decided in \#3725')
+
+ # - rethinkdb.table_status
+
+ - cd: tableId = tbl.info()['id']
+ js: tableId = tbl.info()('id')
+
+ - cd: rtblPluck = r.db('rethinkdb').table('table_status').get(tableId).changes({include_initial:true})
+ py: rtblPluck = r.db('rethinkdb').table('table_status').get(tableId).changes(include_initial=True)
+ - cd: fetch(rtblPluck, 1)
+ ot: partial([{'new_val':partial({'db':'test'})}])
+
+ - py: tbl.reconfigure(shards=3, replicas=1)
+ rb: tbl.reconfigure(shards:3, replicas:1)
+ js: tbl.reconfigure({shards:3, replicas:1})
+ - py: fetch(rtblPluck, 1, 2)
+ js: fetch(rtblPluck, 1, 2)
+ rb: fetch(rtblPluck, 1)
+ ot: partial([{'old_val':partial({'db':'test'}), 'new_val':partial({'db':'test'})}])
+
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/sindex.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/sindex.yaml
new file mode 100644
index 00000000..85ef969d
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/sindex.yaml
@@ -0,0 +1,50 @@
+desc: Test basic changefeed operations
+table_variable_name: tbl
+tests:
+
+ # Fill in some data
+ - rb: tbl.index_create('a')
+ ot: partial({'created':1})
+
+ - rb: tbl.index_wait().count
+ ot: 1
+
+ - rb: tbl.insert([{id:1, a:8}, {id:2, a:7}])
+ ot: partial({'errors':0, 'inserted':2})
+
+ - rb: idmin = tbl.min(index:'id').changes(squash:false, include_initial:true).limit(2)
+ - rb: idmax = tbl.max(index:'id').changes(squash:false, include_initial:true).limit(2)
+ - rb: amin = tbl.min(index:'a').changes(squash:false, include_initial:true).limit(2)
+ - rb: amax = tbl.max(index:'a').changes(squash:false, include_initial:true).limit(2)
+
+ - rb: idmin2 = tbl.min(index:'id').changes(squash:true, include_initial:true).limit(2)
+ - rb: idmax2 = tbl.max(index:'id').changes(squash:true, include_initial:true).limit(2)
+ - rb: amin2 = tbl.min(index:'a').changes(squash:true, include_initial:true).limit(2)
+ - rb: amax2 = tbl.max(index:'a').changes(squash:true, include_initial:true).limit(2)
+
+ - rb: tbl.insert([{id:0, a:9}, {id:3, a:6}])
+ ot: partial({'errors':0, 'inserted':2})
+
+ - rb: idmin.to_a
+ ot: ([{"new_val"=>{"a"=>8, "id"=>1}}, {"new_val"=>{"a"=>9, "id"=>0}, "old_val"=>{"a"=>8, "id"=>1}}])
+
+ - rb: idmax.to_a
+ ot: ([{"new_val"=>{"a"=>7, "id"=>2}}, {"new_val"=>{"a"=>6, "id"=>3}, "old_val"=>{"a"=>7, "id"=>2}}])
+
+ - rb: amin.to_a
+ ot: ([{"new_val"=>{"a"=>7, "id"=>2}}, {"new_val"=>{"a"=>6, "id"=>3}, "old_val"=>{"a"=>7, "id"=>2}}])
+
+ - rb: amax.to_a
+ ot: ([{"new_val"=>{"a"=>8, "id"=>1}}, {"new_val"=>{"a"=>9, "id"=>0}, "old_val"=>{"a"=>8, "id"=>1}}])
+
+ - rb: idmin2.to_a
+ ot: ([{"new_val"=>{"a"=>8, "id"=>1}}, {"new_val"=>{"a"=>9, "id"=>0}, "old_val"=>{"a"=>8, "id"=>1}}])
+
+ - rb: idmax2.to_a
+ ot: ([{"new_val"=>{"a"=>7, "id"=>2}}, {"new_val"=>{"a"=>6, "id"=>3}, "old_val"=>{"a"=>7, "id"=>2}}])
+
+ - rb: amin2.to_a
+ ot: ([{"new_val"=>{"a"=>7, "id"=>2}}, {"new_val"=>{"a"=>6, "id"=>3}, "old_val"=>{"a"=>7, "id"=>2}}])
+
+ - rb: amax2.to_a
+ ot: ([{"new_val"=>{"a"=>8, "id"=>1}}, {"new_val"=>{"a"=>9, "id"=>0}, "old_val"=>{"a"=>8, "id"=>1}}])
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/squash.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/squash.yaml
new file mode 100644
index 00000000..fa4ad7f0
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/squash.yaml
@@ -0,0 +1,62 @@
+desc: Test changefeed squashing
+table_variable_name: tbl
+tests:
+
+ # Check type
+
+ - py: tbl.changes(squash=true).type_of()
+ rb: tbl.changes(squash:true).type_of()
+ js: tbl.changes({squash:true}).typeOf()
+ ot: ("STREAM")
+
+ # comparison changes
+
+ - cd: normal_changes = tbl.changes().limit(2)
+
+ - py: false_squash_changes = tbl.changes(squash=False).limit(2)
+ js: false_squash_changes = tbl.changes({squash:false}).limit(2)
+ rb: false_squash_changes = tbl.changes(squash:false).limit(2)
+
+ - py: long_squash_changes = tbl.changes(squash=0.5).limit(1)
+ js: long_squash_changes = tbl.changes({squash:0.5}).limit(1)
+ rb: long_squash_changes = tbl.changes(squash:0.5).limit(1)
+
+ - py: squash_changes = tbl.changes(squash=true).limit(1)
+ js: squash_changes = tbl.changes({squash:true}).limit(1)
+ rb: squash_changes = tbl.changes(squash:true).limit(1)
+
+ - cd: tbl.insert({'id':100})['inserted']
+ js: tbl.insert({'id':100})('inserted')
+ ot: 1
+
+ - cd: tbl.get(100).update({'a':1})['replaced']
+ js: tbl.get(100).update({'a':1})('replaced')
+ ot: 1
+
+ - cd: normal_changes
+ ot: ([{'new_val':{'id':100}, 'old_val':null},
+ {'new_val':{'a':1, 'id':100}, 'old_val':{'id':100}}])
+
+ - cd: false_squash_changes
+ ot: ([{'new_val':{'id':100}, 'old_val':null},
+ {'new_val':{'a':1, 'id':100}, 'old_val':{'id':100}}])
+
+ - cd: long_squash_changes
+ ot: ([{'new_val':{'a':1, 'id':100}, 'old_val':null}])
+
+ - cd: squash_changes
+ ot:
+ js: ([{'new_val':{'a':1, 'id':100}, 'old_val':null}])
+ cd: ([{'new_val':{'id':100}, 'old_val':null}])
+
+ # Bad squash values
+
+ - py: tbl.changes(squash=null)
+ rb: tbl.changes(squash:null)
+ js: tbl.changes({squash:null})
+ ot: err('ReqlQueryLogicError', 'Expected BOOL or NUMBER but found NULL.')
+
+ - py: tbl.changes(squash=-10)
+ rb: tbl.changes(squash:-10)
+ js: tbl.changes({squash:-10})
+ ot: err('ReqlQueryLogicError', 'Expected BOOL or a positive NUMBER but found a negative NUMBER.')
diff --git a/ext/librethinkdbxx/test/upstream/changefeeds/table.yaml b/ext/librethinkdbxx/test/upstream/changefeeds/table.yaml
new file mode 100644
index 00000000..c089abc9
--- /dev/null
+++ b/ext/librethinkdbxx/test/upstream/changefeeds/table.yaml
@@ -0,0 +1,101 @@
+desc: Test changefeeds on a table
+table_variable_name: tbl
+tests:
+
+ # ==== regular tables
+
+ # - start feeds
+
+ - cd: all = tbl.changes()
+
+ # - note: no initial values from table changefeeds
+
+ # - inserts
+
+ - cd: tbl.insert([{'id':1}, {'id':2}])
+ ot: partial({'errors':0, 'inserted':2})
+ - cd: fetch(all, 2)
+ ot: bag([{'old_val':null, 'new_val':{'id':1}}, {'old_val':null, 'new_val':{'id':2}}])
+
+ # - updates
+
+ - cd: tbl.get(1).update({'version':1})
+ ot: partial({'errors':0, 'replaced':1})
+ - cd: fetch(all, 1)
+ ot: [{'old_val':{'id':1}, 'new_val':{'id':1, 'version':1}}]
+
+ # - deletions
+
+ - cd: tbl.get(1).delete()
+ ot: partial({'errors':0, 'deleted':1})
+ - cd: fetch(all, 1)
+ ot: [{'old_val':{'id':1, 'version':1}, 'new_val':null}]
+
+ # - pluck on values
+
+ - cd: pluck = tbl.changes().pluck({'new_val':['version']})
+ - cd: tbl.insert([{'id':5, 'version':5}])
+ ot: partial({'errors':0, 'inserted':1})
+ - cd: fetch(pluck, 1)
+ ot: [{'new_val':{'version':5}}]
+
+ # - order by
+
+ - cd: tbl.changes().order_by('id')
+ ot: err('ReqlQueryLogicError', "Cannot call a terminal (`reduce`, `count`, etc.) on an infinite stream (such as a changefeed).")
+#
+# ToDo: enable this when #4067 is done
+#
+# - js: orderedLimit = tbl.changes().limit(5).order_by(r.desc('id'))('new_val')('id')
+# cd: orderedLimit = tbl.changes().limit(5).order_by(r.desc('id'))['new_val']['id']
+# - js: tbl.range(100, 105).map(function (row) { return {'id':row} })
+# py: tbl.range(100, 105).map({'id':r.row})
+# rb: tbl.range(100, 105).map{|row| {'id':row}}
+# - cd: fetch(orderedLimit)
+# ot: [104, 103, 102, 101, 100]
+
+ # - changes overflow
+
+ - cd: overflow = tbl.changes()
+ runopts:
+ changefeed_queue_size: 100
+ # add enough entries to make sure we get the overflow error
+ - js: tbl.insert(r.range(200).map(function(x) { return({}); }))
+ py: tbl.insert(r.range(200).map(lambda x: {}))
+ rb: tbl.insert(r.range(200).map{|x| {}})
+ - cd: fetch(overflow, 90)
+ ot: partial([{'error': regex('Changefeed cache over array size limit, skipped \d+ elements.')}])
+
+ # ==== virtual tables
+
+ - def: vtbl = r.db('rethinkdb').table('_debug_scratch')
+ - cd: allVirtual = vtbl.changes()
+
+ # - inserts
+
+ - cd: vtbl.insert([{'id':1}, {'id':2}])
+ ot: partial({'errors':0, 'inserted':2})
+ - cd: fetch(allVirtual, 2)
+ ot: bag([{'old_val':null, 'new_val':{'id':1}}, {'old_val':null, 'new_val':{'id':2}}])
+
+ # - updates
+
+ - cd: vtbl.get(1).update({'version':1})
+ ot: partial({'errors':0, 'replaced':1})
+ - cd: fetch(allVirtual, 1)
+ ot: [{'old_val':{'id':1}, 'new_val':{'id':1, 'version':1}}]
+
+ # - deletions
+
+ - cd: vtbl.get(1).delete()
+ ot: partial({'errors':0, 'deleted':1})
+ - cd: fetch(allVirtual, 1)
+ ot: [{'old_val':{'id':1, 'version':1}, 'new_val':null}]
+
+ # - pluck on values
+
+ - cd: vpluck = vtbl.changes().pluck({'new_val':['version']})
+ - cd: vtbl.insert([{'id':5, 'version':5}])
+ ot: partial({'errors':0, 'inserted':1})
+ - cd: fetch(vpluck, 1)
+ ot: [{'new_val':{'version':5}}]