# run subcommands
#
# add-new-key <key>
# keys
# singledel-keys <writerID>
# op <operation string as printed to ops files>

run
add-new-key foo
add-new-key foo
----
"foo" is new
"foo" already tracked

# Test SET; SINGLEDEL on DB.

run
keys
singledel-keys db1
singledel-keys batch1
op db1.Set("foo", "foo")
keys
singledel-keys db1
singledel-keys batch1
bounds db1
op db1.SingleDelete("foo", false)
keys
singledel-keys db1
----
keys: "foo"
can singledel on db1: "foo"
can singledel on batch1: "foo"
[db1.Set("foo", "foo")]
keys: "foo"
can singledel on db1: "foo"
can singledel on batch1: "foo"
db1: ["foo","foo"]
[db1.SingleDelete("foo", false /* maybeReplaceDelete */)]
keys: "foo"
can singledel on db1: "foo"

# Test SET; SINGLEDEL on batch on separate key.

run
add-new-key bar
op batch1.Set("bar", "bar")
keys
singledel-keys db1
singledel-keys batch1
singledel-keys batch2
op batch1.SingleDelete("bar", false)
keys
singledel-keys db1
singledel-keys batch1
op db1.Apply(batch1)
singledel-keys db1
----
"bar" is new
[batch1.Set("bar", "bar")]
keys: "bar", "foo"
can singledel on db1: "bar", "foo"
can singledel on batch1: "bar", "foo"
can singledel on batch2: "bar", "foo"
[batch1.SingleDelete("bar", false /* maybeReplaceDelete */)]
keys: "bar", "foo"
can singledel on db1: "bar", "foo"
can singledel on batch1: "bar", "foo"
[db1.Apply(batch1)]
can singledel on db1: "bar", "foo"

# Test SET on db; SINGLEDEL on batch.

reset
----

run
add-new-key foo
op db1.Set("foo", "foo")
singledel-keys db1
singledel-keys batch1
op batch1.SingleDelete("foo", false)
singledel-keys db1
singledel-keys batch1
op db1.Apply(batch1)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
singledel-keys batch1
----
"foo" is new
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
can singledel on batch1: "foo"
[batch1.SingleDelete("foo", false /* maybeReplaceDelete */)]
can singledel on db1: "foo"
can singledel on batch1: "foo"
[db1.Apply(batch1)]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
can singledel on batch1: "foo"

# Test SET; DEL; SET; SingleDelete on db.

reset
----

run
add-new-key foo
op db1.Set("foo", "foo")
op db1.Delete("foo")
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
op db1.SingleDelete("foo", false)
singledel-keys db1
----
"foo" is new
[db1.Set("foo", "foo")]
[db1.Delete("foo")]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
[db1.SingleDelete("foo", false /* maybeReplaceDelete */)]
can singledel on db1: "foo"

# Test SET; DEL; SET; DEL on batches.

reset
----

run
add-new-key foo
op batch1.Set("foo", "foo")
op batch1.Delete("foo")
op batch1.Set("foo", "foo")
singledel-keys batch1
op db1.Apply(batch1)
----
"foo" is new
[batch1.Set("foo", "foo")]
[batch1.Delete("foo")]
[batch1.Set("foo", "foo")]
can singledel on batch1: "foo"
[db1.Apply(batch1)]

# "foo" should be eligible for single delete on db1 because a Delete separates
# the two sets.

run
singledel-keys db1
----
can singledel on db1: "foo"

# A batch that contains its own Set and SingleDelete should conflict, because
# the two Sets would stack.

run
op batch2.Set("foo", "foo")
op batch2.SingleDelete("foo", false)
conflicts batch2 db1
----
[batch2.Set("foo", "foo")]
[batch2.SingleDelete("foo", false /* maybeReplaceDelete */)]
conflicts merging batch2 into db1: "foo"

# Setting "foo" again on the DB should result in the key no longer be eligible
# for single delete because there are two stacked SETs on db1.s

run
op db1.Set("foo", "foo")
singledel-keys db1
----
[db1.Set("foo", "foo")]
can singledel on db1: (none)

run
op batch2.Delete("foo")
op db1.Apply(batch2)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
----
[batch2.Delete("foo")]
[db1.Apply(batch2)]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"

# Test SET; MERGE; DEL; SINGLEDEL on DB.

reset
----

run
add-new-key foo
op db.Set("foo", "foo")
singledel-keys db1
op db1.Merge("foo", "foo")
singledel-keys db1
op db1.Delete("foo")
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
op db1.SingleDelete("foo", false)
singledel-keys db1
----
"foo" is new
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
[db1.Merge("foo", "foo")]
can singledel on db1: (none)
[db1.Delete("foo")]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
[db1.SingleDelete("foo", false /* maybeReplaceDelete */)]
can singledel on db1: "foo"

# Test SET; DEL (db); SET; SINGLEDEL (batch)

reset
----

run
add-new-key foo
op db1.Set("foo", "foo")
singledel-keys db1
op db1.Delete("foo")
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
singledel-keys batch1
op batch1.SingleDelete("foo", false)
singledel-keys db1
singledel-keys batch1
op db1.Apply(batch1)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
----
"foo" is new
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
[db1.Delete("foo")]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"
can singledel on batch1: "foo"
[batch1.SingleDelete("foo", false /* maybeReplaceDelete */)]
can singledel on db1: "foo"
can singledel on batch1: "foo"
[db1.Apply(batch1)]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"

# A delete range should "reset" keys, even if the delete range is applied to a
# batch that doesn't yet contain the relevant key.

reset
----

run
add-new-key foo
add-new-key bar
op db1.Set("foo", "foo")
op db1.Set("foo", "foo")
singledel-keys db1
op batch1.DeleteRange("a", "z")
conflicts collapsed batch1 db1
op db1.Apply(batch1)
singledel-keys db1
----
"foo" is new
"bar" is new
[db1.Set("foo", "foo")]
[db1.Set("foo", "foo")]
can singledel on db1: "bar"
[batch1.DeleteRange("a", "z")]
conflicts merging batch1 (collapsed) into db1: (none)
[db1.Apply(batch1)]
can singledel on db1: "bar", "foo"

# Ingestion flattens keys, with any range dels contained within the batch
# semantically applying beneath the most recent point. In this case, foo should
# remain eligible immediately after ingestion because the DeleteRange shadows
# the original Set on db1. It should not be eligible after the final Set,
# because the ingested set and the final set stack, both on top of the delete
# range.

reset
----

run
add-new-key foo
add-new-key bar
op db1.Set("foo", "foo")
singledel-keys db1
op batch1.Set("foo", "foo")
op batch1.DeleteRange("a", "z")
conflicts collapsed batch1 db1
op db1.Ingest(batch1)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
bounds db1
----
"foo" is new
"bar" is new
[db1.Set("foo", "foo")]
can singledel on db1: "bar", "foo"
[batch1.Set("foo", "foo")]
[batch1.DeleteRange("a", "z")]
conflicts merging batch1 (collapsed) into db1: (none)
[db1.Ingest(batch1)]
can singledel on db1: "bar", "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "bar"
db1: ["a","z")

# Repeat the above test, but this time with an ingestion that should fail due to
# overlapping key ranges.

run
add-new-key foo
add-new-key bar
op db1.Set("foo", "foo")
singledel-keys db1
op batch1.Set("foo", "foo")
bounds batch1
op batch1.DeleteRange("a", "z")
bounds batch1
op batch2.DeleteRange("y", "z")
conflicts collapsed batch1 db1
op db1.Ingest(batch1, batch2)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
bounds db1
----
"foo" already tracked
"bar" already tracked
[db1.Set("foo", "foo")]
can singledel on db1: "bar"
[batch1.Set("foo", "foo")]
batch1: ["foo","foo"]
[batch1.DeleteRange("a", "z")]
batch1: ["a","z")
[batch2.DeleteRange("y", "z")]
conflicts merging batch1 (collapsed) into db1: (none)
[db1.Ingest(batch1, batch2)]
can singledel on db1: "bar"
[db1.Set("foo", "foo")]
can singledel on db1: "bar"
db1: ["a","z")

# Since ingestion flattens keys, foo should be single-deletable on the db after
# ingest, even though it couldn't be single deleted from the batch before
# ingestion.

reset
----

run
add-new-key foo
op batch1.Set("foo", "foo")
op batch1.Set("foo", "foo")
conflicts collapsed batch1 db1
singledel-keys batch1
op db1.Ingest(batch1)
singledel-keys db1
----
"foo" is new
[batch1.Set("foo", "foo")]
[batch1.Set("foo", "foo")]
conflicts merging batch1 (collapsed) into db1: (none)
can singledel on batch1: (none)
[db1.Ingest(batch1)]
can singledel on db1: "foo"


# Because ingestion flattens keys, foo remains eligible for single delete the
# entire test case. During flattening, the Delete wins over the Set.

reset
----

run
add-new-key foo
op batch1.Set("foo", "foo")
op batch1.Delete("foo")
singledel-keys batch1
op db1.Ingest(batch1)
singledel-keys db1
op db1.Set("foo", "foo")
singledel-keys db1
----
"foo" is new
[batch1.Set("foo", "foo")]
[batch1.Delete("foo")]
can singledel on batch1: "foo"
[db1.Ingest(batch1)]
can singledel on db1: "foo"
[db1.Set("foo", "foo")]
can singledel on db1: "foo"

# Ingestion flattening means that the batch1.Set sits semantically on top of the
# delete range despite being inserted to the batch before the delete range.

reset
----

run
add-new-key foo
op batch1.Set("foo", "foo")
op batch1.DeleteRange("a", "z")
op db1.Ingest(batch1)
op db1.Set("foo", "foo")
singledel-keys db1
----
"foo" is new
[batch1.Set("foo", "foo")]
[batch1.DeleteRange("a", "z")]
[db1.Ingest(batch1)]
[db1.Set("foo", "foo")]
can singledel on db1: (none)

# In this scenario batch1 would conflict with db1 if it were applied as a batch
# commit, but when ingested and "collapsed" it does not conflict.

reset
----

run
add-new-key foo
op db1.Set("foo", "foo")
op batch1.Set("foo", "foo")
op batch1.SingleDelete("foo", false)
conflicts batch1 db1
conflicts collapsed batch1 db1
op db1.Ingest(batch1)
----
"foo" is new
[db1.Set("foo", "foo")]
[batch1.Set("foo", "foo")]
[batch1.SingleDelete("foo", false /* maybeReplaceDelete */)]
conflicts merging batch1 into db1: "foo"
conflicts merging batch1 (collapsed) into db1: (none)
[db1.Ingest(batch1)]

# Allow a MERGE to be deleted by a single delete, as long as it's the only
# value-carrying key.

reset
----

run
add-new-key foo
op db1.Merge("foo", "foo")
singledel-keys db1
op batch1.Merge("foo", "foo")
op batch1.SingleDelete("foo", true)
conflicts batch1 db1
conflicts collapsed batch1 db1
op db1.Merge("foo", "foo")
singledel-keys db1
----
"foo" is new
[db1.Merge("foo", "foo")]
can singledel on db1: "foo"
[batch1.Merge("foo", "foo")]
[batch1.SingleDelete("foo", true /* maybeReplaceDelete */)]
conflicts merging batch1 into db1: "foo"
conflicts merging batch1 (collapsed) into db1: (none)
[db1.Merge("foo", "foo")]
can singledel on db1: (none)

# Regression test for #4267. In the below example, foo@3 must not be eligible
# for single delete on db1 at the end of the test. The external object will end
# up containing the point key foo@3 (NOT foo@5).

reset
----

run
add-new-key foo@5
op db1.Set("foo@5", "foo5")
add-new-key foo@3
op db1.Set("foo@3", "foo3")
op batch1.DeleteRange("a", "foo@3")
op batch1.Set("foo@3", "foo3batch1")
op external1 = batch1.NewExternalObj()
op db1.IngestExternalFiles(external1, "a", "z", "", "")
singledel-keys db1
----
"foo@5" is new
[db1.Set("foo@5", "foo5")]
"foo@3" is new
[db1.Set("foo@3", "foo3")]
[batch1.DeleteRange("a", "foo@3")]
[batch1.Set("foo@3", "foo3batch1")]
[external1 = batch1.NewExternalObj()]
[db1.IngestExternalFiles(external1, "a" /* start */, "z" /* end */, "" /* syntheticSuffix */, "" /* syntheticPrefix */)]
can singledel on db1: "foo@5"
