Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefano-ottolenghi committed Dec 13, 2024
1 parent 5ecf10a commit 50c8801
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 169 deletions.
194 changes: 86 additions & 108 deletions dotnet-manual/modules/ROOT/pages/bookmarks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,41 @@ A _bookmark_ is a token that represents some state of the database.
By passing one or multiple bookmarks along with a query, the server will make sure that the query does not get executed before the represented state(s) have been established.


== Bookmarks with `.executableQuery()`
== Bookmarks with `.ExecutableQuery()`

When xref:query-simple.adoc[querying the database with `.executableQuery()`], the driver manages bookmarks for you.
When xref:query-simple.adoc[querying the database with `.ExecutableQuery()`], the driver manages bookmarks for you.
In this case, you have the guarantee that subsequent queries can read previous changes with no further action.

[source, java]
[source, csharp]
----
driver.executableQuery("<QUERY 1>").execute();
await driver.ExecutableQuery("<QUERY 1>").AsyncExecute();
// subsequent .executableQuery() calls will be causally chained
// subsequent .ExecutableQuery() calls will be causally chained
driver.executableQuery("<QUERY 2>").execute(); // can read result of <QUERY 1>
driver.executableQuery("<QUERY 3>").execute(); // can read result of <QUERY 2>
await driver.ExecutableQueryAsync("<QUERY 2>").ExecuteAsync(); // can read result of <QUERY 1>
await driver.ExecutableQueryAsync("<QUERY 3>").ExecuteAsync(); // can read result of <QUERY 2>
----

To disable bookmark management and causal consistency, use `.withBookmarkManager(null)` in the query configuration.
To disable bookmark management and causal consistency, use `enableBookmarkManager: false` in the query configuration.

[source, java]
[source, csharp]
----
driver.executableQuery("<QUERY>")
.withConfig(QueryConfig.builder().withBookmarkManager(null).build())
.execute();
await driver.executableQuery("<QUERY>")
.WithConfig(new QueryConfig(enableBookmarkManager: false))
.ExecuteAsync();
----


== Bookmarks within a single session

Bookmark management happens automatically for queries run within a single session, so that you can trust that queries inside one session are causally chained.
Bookmark management happens automatically for queries run within a single session, so you can trust that queries inside one session are causally chained.

[source, java]
[source, csharp]
----
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 1>"));
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 2>")); // can read QUERY 1
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 3>")); // can read QUERY 1,2
}
using var session = driver.AsyncSession(conf => conf.WithDatabase("neo4j"));
await session.ExecuteWriteAsync(async tx => await tx.RunAsync("<QUERY 1>"));
await session.ExecuteWriteAsync(async tx => await tx.RunAsync("<QUERY 2>")); // can read QUERY 1
await session.ExecuteWriteAsync(async tx => await tx.RunAsync("<QUERY 3>")); // can read QUERY 1,2
----


Expand All @@ -56,102 +55,81 @@ In the example below, `sessionA` and `sessionB` are allowed to run concurrently,
This guarantees the `Person` nodes `sessionC` wants to act on actually exist.

.Coordinate multiple sessions using bookmarks
[source, java]
[source, csharp]
----
package demo;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.TransactionContext;
public class App {
private static final int employeeThreshold = 10;
public static void main(String... args) {
final String dbUri = "<URI for Neo4j database>";
final String dbUser = "<Username>";
final String dbPassword = "<Password>";
try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) {
createSomeFriends(driver);
}
}
public static void createSomeFriends(Driver driver) {
List<Bookmark> savedBookmarks = new ArrayList<>(); // to collect the sessions' bookmarks
// Create the first person and employment relationship
try (var sessionA = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionA.executeWriteWithoutResult(tx -> createPerson(tx, "Alice"));
sessionA.executeWriteWithoutResult(tx -> employ(tx, "Alice", "Wayne Enterprises"));
savedBookmarks.addAll(sessionA.lastBookmarks()); // <1>
}
// Create the second person and employment relationship
try (var sessionB = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionB.executeWriteWithoutResult(tx -> createPerson(tx, "Bob"));
sessionB.executeWriteWithoutResult(tx -> employ(tx, "Bob", "LexCorp"));
savedBookmarks.addAll(sessionB.lastBookmarks()); // <1>
}
// Create a friendship between the two people created above
try (var sessionC = driver.session(SessionConfig.builder()
.withDatabase("neo4j")
.withBookmarks(savedBookmarks) // <2>
.build())) {
sessionC.executeWriteWithoutResult(tx -> createFriendship(tx, "Alice", "Bob"));
sessionC.executeWriteWithoutResult(tx -> printFriendships(tx));
}
}
using Neo4j.Driver;
const string dbUri = "<URI for Neo4j database>";
const string dbUser = "<Username>";
const string dbPassword = "<Password>";
await using var driver = GraphDatabase.Driver(dbUri, AuthTokens.Basic(dbUser, dbPassword));
await driver.VerifyConnectivityAsync();
await createSomeFriends(driver);
async Task createSomeFriends(IDriver driver) {
Bookmarks savedBookmarks = Bookmarks.From(new List<string>()); // to collect the sessions' bookmarks
// Create the first person and employment relationship
using var sessionA = driver.AsyncSession(conf => conf.WithDatabase("neo4j"));
await sessionA.ExecuteWriteAsync(tx => createPerson(tx, "Alice"));
await sessionA.ExecuteWriteAsync(tx => employ(tx, "Alice", "Wayne Enterprises"));
savedBookmarks += sessionA.LastBookmarks; // <1>
// Create the second person and employment relationship
using var sessionB = driver.AsyncSession(conf => conf.WithDatabase("neo4j"));
await sessionB.ExecuteWriteAsync(tx => createPerson(tx, "Bob"));
await sessionB.ExecuteWriteAsync(tx => employ(tx, "Bob", "LexCorp"));
savedBookmarks += sessionB.LastBookmarks; // <1>
// Create a friendship between the two people created above
using var sessionC = driver.AsyncSession(conf => conf
.WithDatabase("neo4j")
.WithBookmarks(savedBookmarks) // <2>
);
await sessionC.ExecuteWriteAsync(tx => createFriendship(tx, "Alice", "Bob"));
await sessionC.ExecuteWriteAsync(tx => printFriendships(tx));
}
// Create a person node
static void createPerson(TransactionContext tx, String name) {
tx.run("MERGE (:Person {name: $name})", Map.of("name", name));
}
// Create a person node
async Task createPerson(IAsyncQueryRunner tx, string name) {
await tx.RunAsync("MERGE (:Person {name: $name})", new { name = name });
}
// Create an employment relationship to a pre-existing company node
// This relies on the person first having been created.
static void employ(TransactionContext tx, String personName, String companyName) {
tx.run("""
MATCH (person:Person {name: $personName})
MATCH (company:Company {name: $companyName})
CREATE (person)-[:WORKS_FOR]->(company)
""", Map.of("personName", personName, "companyName", companyName)
);
}
// Create an employment relationship to a pre-existing company node
// This relies on the person first having been created.
async Task employ(IAsyncQueryRunner tx, string personName, string companyName) {
await tx.RunAsync(@"
MATCH (person:Person {name: $personName})
MATCH (company:Company {name: $companyName})
CREATE (person)-[:WORKS_FOR]->(company)
", new { personName = personName, companyName = companyName }
);
}
// Create a friendship between two people
static void createFriendship(TransactionContext tx, String nameA, String nameB) {
tx.run("""
MATCH (a:Person {name: $nameA})
MATCH (b:Person {name: $nameB})
MERGE (a)-[:KNOWS]->(b)
""", Map.of("nameA", nameA, "nameB", nameB)
);
}
// Create a friendship between two people
async Task createFriendship(IAsyncQueryRunner tx, string nameA, string nameB) {
await tx.RunAsync(@"
MATCH (a:Person {name: $nameA})
MATCH (b:Person {name: $nameB})
MERGE (a)-[:KNOWS]->(b)
", new { nameA = nameA, nameB = nameB }
);
}
// Retrieve and display all friendships
static void printFriendships(TransactionContext tx) {
var result = tx.run("MATCH (a)-[:KNOWS]->(b) RETURN a.name, b.name");
while (result.hasNext()) {
var record = result.next();
System.out.println(record.get("a.name").asString() + " knows " + record.get("b.name").asString());
}
// Retrieve and display all friendships
async Task printFriendships(IAsyncQueryRunner tx) {
var result = await tx.RunAsync("MATCH (a)-[:KNOWS]->(b) RETURN a.name, b.name");
while (await result.FetchAsync()) {
var record = result.Current;
Console.WriteLine(record.Get<string>("a.name") + " knows " + record.Get<string>("b.name"));
}
}
----

<1> Collect and combine bookmarks from different sessions using `Session.lastBookmarks()`, storing them in a link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Bookmark.html[`Bookmark`] object.
<2> Use them to initialize another session with the `.withBookmarks()` config method.
<1> Collect and combine bookmarks from different sessions using `AsyncSession.LastBookmarks`, storing them in a link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.Bookmarks.html[`Bookmarks`] object.
<2> Use them to initialize another session with the `.WithBookmarks()` config method.

image:{common-image}/driver-passing-bookmarks.svg[]

Expand All @@ -160,9 +138,9 @@ The use of bookmarks can negatively impact performance, since all queries are fo
For simple use-cases, try to group queries within a single transaction, or within a single session.


== Mix `.executableQuery()` and sessions
== Mix `.ExecutableQuery()` and sessions

To ensure causal consistency among transactions executed partly with `.executableQuery()` and partly with sessions, you can retrieve the default bookmark manager for `ExecutableQuery` instances through `driver.executableQueryBookmarkManager()` and pass it to new sessions through the `.withBookmarkManager()` config method.
To ensure causal consistency among transactions executed partly with `.ExecutableQuery()` and partly with sessions, you can retrieve the default bookmark manager for `ExecutableQuery` instances through `driver.executableQueryBookmarkManager()` and pass it to new sessions through the `.WithBookmarkManager()` config method.
This will ensure that all work is executed under the same bookmark manager and thus causally consistent.

[source, java]
Expand Down
Loading

0 comments on commit 50c8801

Please sign in to comment.