//
// This checks to make sure that sharded regex queries behave the same as unsharded regex queries
//

var options = { mongosOptions : { binVersion : "" },
                shardOptions : { binVersion : "" },
                configOptions : { binVersion : "" },
                separateConfig : true };

var st = new ShardingTest({ shards : 2, other : options });
st.stopBalancer();

var mongos = st.s0;

//
// Set up write command mode, this is a backport of a new test
//

mongos.forceWriteMode("commands");

var admin = mongos.getDB("admin");
var shards = mongos.getDB("config").shards.find().toArray();

//
// Set up multiple collections to target with regex shard keys on two shards
//

var coll = mongos.getCollection("foo.bar");
var collSharded = mongos.getCollection("foo.barSharded");
var collCompound = mongos.getCollection("foo.barCompound");
var collNested = mongos.getCollection("foo.barNested");
var collHashed = mongos.getCollection("foo.barHashed");

assert.commandWorked(admin.runCommand({ enableSharding : coll.getDB().toString() }));
admin.runCommand({ movePrimary : coll.getDB().toString(), to : shards[0]._id });

//
// Split the collection so that "abcde-0" and "abcde-1" go on different shards when possible
//

assert.commandWorked(admin.runCommand({ shardCollection : collSharded.toString(),
                                        key: { a : 1 } }));
assert.commandWorked(admin.runCommand({ split : collSharded.toString(),
                                        middle : { a : "abcde-1" } }));
assert.commandWorked(admin.runCommand({ moveChunk : collSharded.toString(),
                                        find : { a : 0 },
                                        to : shards[1]._id,
                                        _waitForDelete : true }));

assert.commandWorked(admin.runCommand({ shardCollection : collCompound.toString(),
                                        key: { a : 1, b : 1 } }));
assert.commandWorked(admin.runCommand({ split : collCompound.toString(),
                                        middle : { a : "abcde-1", b : 0 } }));
assert.commandWorked(admin.runCommand({ moveChunk : collCompound.toString(),
                                        find : { a : 0, b : 0 },
                                        to : shards[1]._id,
                                        _waitForDelete : true }));

assert.commandWorked(admin.runCommand({ shardCollection : collNested.toString(),
                                        key : { 'a.b' : 1 } }));
assert.commandWorked(admin.runCommand({ split : collNested.toString(),
                                        middle : { 'a.b' : "abcde-1" } }));
assert.commandWorked(admin.runCommand({ moveChunk : collNested.toString(),
                                        find : { a : { b : 0 } },
                                        to : shards[1]._id,
                                        _waitForDelete : true }));

assert.commandWorked(admin.runCommand({ shardCollection : collHashed.toString(),
                                        key: { hash : "hashed" } }));

st.printShardingStatus();

//
//
// Cannot insert regex _id
assert.writeError(coll.insert({ _id : /regex value/ }));
assert.writeError(collSharded.insert({ _id : /regex value/, a : 0 }));
assert.writeError(collCompound.insert({ _id : /regex value/, a : 0, b : 0 }));
assert.writeError(collNested.insert({ _id : /regex value/, a : { b : 0 } }));
assert.writeError(collHashed.insert({ _id : /regex value/, hash : 0 }));


//
//
// (For now) we can insert a regex shard key
assert.writeOK(collSharded.insert({ a : /regex value/ }));
assert.writeOK(collCompound.insert({ a : /regex value/, b : "other value" }));
assert.writeOK(collNested.insert({ a : { b : /regex value/ } }));
assert.writeOK(collHashed.insert({ hash : /regex value/ }));


//
//
// Query by regex should hit all matching keys, across all shards if applicable
coll.remove({});
assert.writeOK(coll.insert({ a : "abcde-0" }));
assert.writeOK(coll.insert({ a : "abcde-1" }));
assert.writeOK(coll.insert({ a : /abcde.*/ }));
assert.eq(coll.find().itcount(), coll.find({ a : /abcde.*/ }).itcount());

collSharded.remove({});
assert.writeOK(collSharded.insert({ a : "abcde-0" }));
assert.writeOK(collSharded.insert({ a : "abcde-1" }));
assert.writeOK(collSharded.insert({ a : /abcde.*/ }));
assert.eq(collSharded.find().itcount(), collSharded.find({ a : /abcde.*/ }).itcount());

collCompound.remove({});
assert.writeOK(collCompound.insert({ a : "abcde-0", b : 0 }));
assert.writeOK(collCompound.insert({ a : "abcde-1", b : 0 }));
assert.writeOK(collCompound.insert({ a : /abcde.*/, b : 0 }));
assert.eq(collCompound.find().itcount(), collCompound.find({ a : /abcde.*/ }).itcount());

collNested.remove({});
assert.writeOK(collNested.insert({ a : { b : "abcde-0" } }));
assert.writeOK(collNested.insert({ a : { b : "abcde-1" } }));
assert.writeOK(collNested.insert({ a : { b : /abcde.*/ } }));
assert.eq(collNested.find().itcount(), collNested.find({ 'a.b' : /abcde.*/ }).itcount());

collHashed.remove({});
while (st.shard0.getCollection(collHashed.toString()).count() == 0 ||
       st.shard1.getCollection(collHashed.toString()).count() == 0) {
    assert.writeOK(collHashed.insert({ hash : "abcde-" + ObjectId().toString() }));
}
assert.writeOK(collHashed.insert({ hash : /abcde.*/ }));
assert.eq(collHashed.find().itcount(), collHashed.find({ hash : /abcde.*/ }).itcount());


//
//
// Update by regex should hit all matching keys, across all shards if applicable
coll.remove({});
assert.writeOK(coll.insert({ a : "abcde-0" }));
assert.writeOK(coll.insert({ a : "abcde-1" }));
assert.writeOK(coll.insert({ a : /abcde.*/ }));
assert.writeOK(coll.update({ a : /abcde.*/ },
                           { $set : { updated : true } },
                           { multi : true }));
assert.eq(coll.find().itcount(), coll.find({ updated : true }).itcount());

collSharded.remove({});
assert.writeOK(collSharded.insert({ a : "abcde-0" }));
assert.writeOK(collSharded.insert({ a : "abcde-1" }));
assert.writeOK(collSharded.insert({ a : /abcde.*/ }));
assert.writeOK(collSharded.update({ a : /abcde.*/ },
                                  { $set : { updated : true } },
                                  { multi : true }));
assert.eq(collSharded.find().itcount(), collSharded.find({ updated : true }).itcount());

collCompound.remove({});
assert.writeOK(collCompound.insert({ a : "abcde-0", b : 0 }));
assert.writeOK(collCompound.insert({ a : "abcde-1", b : 0 }));
assert.writeOK(collCompound.insert({ a : /abcde.*/, b : 0 }));
assert.writeOK(collCompound.update({ a : /abcde.*/ },
                                   { $set : { updated : true } },
                                   { multi : true }));
assert.eq(collCompound.find().itcount(), collCompound.find({ updated : true }).itcount());

collNested.remove({});
assert.writeOK(collNested.insert({ a : { b : "abcde-0" } }));
assert.writeOK(collNested.insert({ a : { b : "abcde-1" } }));
assert.writeOK(collNested.insert({ a : { b : /abcde.*/ } }));
assert.writeOK(collNested.update({ 'a.b' : /abcde.*/ },
                                 { $set : { updated : true } },
                                 { multi : true }));
assert.eq(collNested.find().itcount(), collNested.find({ updated : true }).itcount());

collHashed.remove({});
while (st.shard0.getCollection(collHashed.toString()).count() == 0 ||
       st.shard1.getCollection(collHashed.toString()).count() == 0) {
    assert.writeOK(collHashed.insert({ hash : "abcde-" + ObjectId().toString() }));
}
assert.writeOK(collHashed.insert({ hash : /abcde.*/ }));
assert.writeOK(collHashed.update({ hash : /abcde.*/ },
                                 { $set : { updated : true } },
                                 { multi : true }));
assert.eq(collHashed.find().itcount(), collHashed.find({ updated : true }).itcount());


//
//
// Upsert by regex should fail on sharded collections
// Regex is not targeted in queries, so can't be targeted for updates
collSharded.remove({});
assert.writeError(collSharded.update({ a : /abcde.*/ }, { a : /abcde.*/ }, { upsert : true }));
assert.writeError(collCompound.update({ a : /abcde.*/ }, { a : /abcde.*/ }, { upsert : true }));
assert.writeError(collSharded.update({ a : /abcde.*/ }, { a : /abcde.*/ }, { upsert : true }));
assert.writeError(collNested.update({ a : { b : /abcde.*/ } }, { a : { b : /abcde.*/ } },
                                    { upsert : true }));
assert.writeError(collNested.update({ c : 1 }, { a : { b : /abcde.*/ } },
                                    { upsert : true }));


//
//
// Remove by regex should hit all matching keys, across all shards if applicable
coll.remove({});
assert.writeOK(coll.insert({ a : "abcde-0" }));
assert.writeOK(coll.insert({ a : "abcde-1" }));
assert.writeOK(coll.insert({ a : /abcde.*/ }));
assert.writeOK(coll.remove({ a : /abcde.*/ }));
assert.eq(0, coll.find({}).itcount());


collSharded.remove({});
assert.writeOK(collSharded.insert({ a : "abcde-0" }));
assert.writeOK(collSharded.insert({ a : "abcde-1" }));
assert.writeOK(collSharded.insert({ a : /abcde.*/ }));
assert.writeOK(collSharded.remove({ a : /abcde.*/ }));
assert.eq(0, collSharded.find({}).itcount());

collCompound.remove({});
assert.writeOK(collCompound.insert({ a : "abcde-0", b : 0 }));
assert.writeOK(collCompound.insert({ a : "abcde-1", b : 0 }));
assert.writeOK(collCompound.insert({ a : /abcde.*/, b : 0 }));
assert.writeOK(collCompound.remove({ a : /abcde.*/ }));
assert.eq(0, collCompound.find({}).itcount());

collNested.remove({});
assert.writeOK(collNested.insert({ a : { b : "abcde-0" } }));
assert.writeOK(collNested.insert({ a : { b : "abcde-1" } }));
assert.writeOK(collNested.insert({ a : { b : /abcde.*/ } }));
assert.writeOK(collNested.remove({ 'a.b' : /abcde.*/ }));
assert.eq(0, collNested.find({}).itcount());

collHashed.remove({});
while (st.shard0.getCollection(collHashed.toString()).count() == 0 ||
       st.shard1.getCollection(collHashed.toString()).count() == 0) {
    assert.writeOK(collHashed.insert({ hash : "abcde-" + ObjectId().toString() }));
}
assert.writeOK(collHashed.insert({ hash : /abcde.*/ }));
assert.writeOK(collHashed.remove({ hash : /abcde.*/ }));
assert.eq(0, collHashed.find({}).itcount());


//
//
// Query/Update/Remove by nested regex is different depending on how the nested regex is specified
coll.remove({});
assert.writeOK(coll.insert({ a : { b : "abcde-0" } }));
assert.writeOK(coll.insert({ a : { b : "abcde-1" } }));
assert.writeOK(coll.insert({ a : { b : /abcde.*/ } }));
assert.eq(1, coll.find({ a : { b : /abcde.*/ } }).itcount());
assert.writeOK(coll.update({ a : { b : /abcde.*/ } },
                           { $set : { updated : true } },
                           { multi : true }));
assert.eq(1, coll.find({ updated : true }).itcount());
assert.writeOK(coll.remove({ a : { b : /abcde.*/ } }));
assert.eq(2, coll.find().itcount());

collNested.remove({});
assert.writeOK(collNested.insert({ a : { b : "abcde-0" } }));
assert.writeOK(collNested.insert({ a : { b : "abcde-1" } }));
assert.writeOK(collNested.insert({ a : { b : /abcde.*/ } }));
assert.eq(1, collNested.find({ a : { b : /abcde.*/ } }).itcount());
assert.writeOK(collNested.update({ a : { b : /abcde.*/ } },
                                 { $set : { updated : true } },
                                 { multi : true }));
assert.eq(1, collNested.find({ updated : true }).itcount());
assert.writeOK(collNested.remove({ a : { b : /abcde.*/ } }));
assert.eq(2, collNested.find().itcount());

jsTest.log("DONE!");
st.stop();

