////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
image::apache-tinkerpop-logo.png[width=500,link="https://tinkerpop.apache.org"]

*x.y.z - Proposal 4*

== TinkerGraph Transaction Support

=== Introduction
Now, if you need to use transactions in TinkerPop, the only solution is to use the Neo4j plugin. Unfortunately, this plugin has not been updated for a long time and is only compatible with Neo4j version 3.4, which reached end of life in March 2020. Gremlin cannot be used as a replacement for other graphs that support transactions.

=== Motivation
* Users will be able to use transactions without the outdated Neo4j plugin.
* Test framework simplification.
* A more robust Gremlin Server setup out-of-the-box.
* A better learning experience for new users.
* A less complicated transaction story for new users.
* A better test graph that is more readily swappable with other graphs.

=== Assumptions

* Need to reuse existing code as much as possible.
* Users should be able to work both with and without transactions.
* Transaction implementation should not have additional external dependencies.
* Existing drivers should work without changes.

=== Specifications
==== Design Overview
The main changes relate to TinkerGraph, need to make a similar solution but with transaction support. Also TinkerGraph by default will have 2 configurations on Gremlin Server and users themselves will choose what they need, TinkerGraph or TinkerTransactionGraph.
Locks will be write-only to reduce the impact of transactions on performance. In this case the isolation level will be `read committed`, the same as now with the Neo4j plugin. 
Only ThreadLocalTransaction is planned, therefore embedded graph transactions may not be fully supported.

==== Overview of changes to code base
===== ElementContainer
Storage for current Element value and ThreadLocal updated value with `isDeleted` flag. Similar for `Vertices` and `Edges`.
[code]
----
class ElementContainer {
    Vertex current;
    ThreadLocal<Vertex> transactionUpdatedValue;
    ThreadLocal<Boolean> isDeleted;
}
----

===== TinkerTransactionGraph
Vertices and Edges will store with version number and dirty transaction values
`Map<Object, ElementContainer> vertices` where `Object` is Vertex Id.
Shared code with TinkerGraph will be moved to AbstractTinkerGraph.

===== TinkerTransactionElement
Add `currentVersion` to all TinkerElements. This is the number of the transaction that last updated it.

===== TinkerThreadLocalTransaction
Extension of AbstractThreadLocalTransaction. Add a unique transaction number. Can use `AtomicLong.incrementAndGet`

===== Graph Feature files
Add TinkerTransactionGraphFeatures, TinkerTransactionGraphVertexFeatures, TinkerTransactionGraphEdgeFeatures, TinkerTransactionGraphGraphFeatures, TinkerTransactionGraphVertexPropertyFeatures.

==== CRUD operations without transaction
1. Wrap into transaction
2. Commit

==== CRUD operations with transaction
===== Create
Add new ElementContainer to `vertices` and `edges` in TinkerTransactionGraph.

==== Read
Read values from `vertices` ElementContainer, `transactionUpdatedValue` if present, otherwise `current`.
if `isDeleted` flag is set, then the element is deleted in transaction and should be skipped.

===== Update
Update corresponding ElementContainer  in `vertices`.

===== Delete
Set `isDeleted` flag in ElementContainer  in `vertices`.

==== Transaction flows
===== Commit flow
To reduce lock time double-checked locking used.
1. Make list of all affected elements sorted by transaction#. The reason is to reduce the lock time.
2. If any has a newer version, then fail.
3. Try to lock all Vertices/Edges changed in transaction. For vertex/edge delete operation also need to lock adjacent edges/vertices. Lock is for write operations only. If some Vertex/Edge is already locked then fail.
4. Check versions again, fail if some element is updated. Code: `Vertices.get(id).current()).currentVersion() !=Vertices.get(id).transactionUpdatedValue().currentVersion()`
5. For all Elements replace current version with value updated in transaction (or remove Element on Delete operation). Cleanup `transactionUpdatedValue`.
6. Change version of all updated Elements.
7. Unlock.
8. Update indexes if needed.

===== Rollback
Cleanup `transactionUpdatedValue` in `vertices`

===== Error
On any error, including transaction conflict:
1. Rollback
2. Throw exception

===== Timeout
Add transactionWatchdog which will rollback locked/expired transactions. Similar to `Session.touch`.
1. If the transaction was open longer than some preconfigured (default 10-20 minutes?) time.
2. If the transaction start commit, but not finish (default 1-2 seconds?).

===== Examples of conflict resolution
* tx1 adds a property to v1, tx2 deletes v1 (same for edges)
transaction that executed first will set the lock on v1, second transaction will fail on one of steps 2-4.
* tx1 adds an edge from v1 to v2, tx2 deletes v1 or v2
If tx1 try to commit first: tx1 add edge, tx2 delete v1 AND all edges including newly added.
If tx2 try to commit first: tx2 deletes v1 or v2, tx1 will fail on step 1.
* tx1 changes a Cardinality.single property to x, tx2 changes the same Cardinality.single property to y
Last transaction will fail on step 2 or 4.
* tx1 adds a new vertex v1, tx2 adds the same vertex (same for edges)
Last transaction will fail on step 2 or 4 or 5.
* tx1 removes vertex v1, tx2 removes the same vertex (same for edges)
Both transactions will be successful. 
* tx1 adds vertex v1, tx2 removes vertex v1 (same for edges)
If v1 exists before and tx1 commit first: tx1 fail on step 5
If v1 exists before and tx2 commit first: tx1 fail on step 2 or 4
If v1 not exists before and tx1 commit first: tx1 ok; tx2 depends on commit time, ok when tx1 finished and fail on step 2 or 4 otherwise.
If v1 not exists before and tx2 commit first: tx2 ok, tx1 ok


==== Additional changes
Split TinkerFactory into AbstractTinkerFactory and TinkerFactory, add TinkerTransactionGraphFactory.
Transfer responsibility of graph update operations from static TinkerHelper to TinkerGraph/TinkerTransactionGraph.
