From dec206f03d6ac9fb1b5ceb90029a2241aa12cc48 Mon Sep 17 00:00:00 2001 From: Rouven Bauer Date: Tue, 11 Apr 2023 14:50:59 +0200 Subject: [PATCH] Stabilize ExecuteQuery and BookmarkManager APIs * Renaming `neo4j.Writers` to `Write` and `Readers` to `Read`. * Renaming `driver.DefaultExecuteQueryBookmarkManager()` to `ExecuteQueryBookmarkManager()`. * Remove experimental tag from ExecuteQuery and its related APIs * `ExecuteQuery()` * `driver.DefaultExecuteQueryBookmarkManager()` * `BookmarkManager`, the corresponding config option (session's `BookmarkManager`) and factory method (`neo4j.NewBookmarkManager()`) * `RoutingControl` enum * `EagerResult` * `ExecuteQueryWith...` config helpers Signed-off-by: Florent Biville --- neo4j/bookmarks.go | 3 - neo4j/driver_with_context.go | 95 ++++++---------------- neo4j/driver_with_context_examples_test.go | 21 ++++- neo4j/driver_with_context_test.go | 35 ++++++-- neo4j/session_with_context.go | 14 ++-- testkit-backend/backend.go | 16 ++-- 6 files changed, 85 insertions(+), 99 deletions(-) diff --git a/neo4j/bookmarks.go b/neo4j/bookmarks.go index 91bbdadc..f5dbe7c2 100644 --- a/neo4j/bookmarks.go +++ b/neo4j/bookmarks.go @@ -32,7 +32,6 @@ import ( type Bookmarks = []string // BookmarkManager centralizes bookmark manager supply and notification -// This is currently a preview feature (see README on what it means in terms of support and compatibility guarantees) type BookmarkManager interface { // UpdateBookmarks updates the bookmark tracked by this bookmark manager // previousBookmarks are the initial bookmarks of the bookmark holder (like a Session) @@ -44,8 +43,6 @@ type BookmarkManager interface { GetBookmarks(ctx context.Context) (Bookmarks, error) } -// BookmarkManagerConfig is part of the BookmarkManager preview feature (see README on what it means in terms of support -// and compatibility guarantees) type BookmarkManagerConfig struct { // Initial bookmarks per database InitialBookmarks Bookmarks diff --git a/neo4j/driver_with_context.go b/neo4j/driver_with_context.go index d8fa4916..379bba49 100644 --- a/neo4j/driver_with_context.go +++ b/neo4j/driver_with_context.go @@ -50,21 +50,18 @@ const ( // DriverWithContext represents a pool of connections to a neo4j server or cluster. It's // safe for concurrent use. type DriverWithContext interface { - // DefaultExecuteQueryBookmarkManager returns the bookmark manager instance used by ExecuteQuery by default. - // - // DefaultExecuteQueryBookmarkManager is part of the BookmarkManager preview feature (see README on what it means in - // terms of support and compatibility guarantees) + // ExecuteQueryBookmarkManager returns the bookmark manager instance used by ExecuteQuery by default. // // This is useful when ExecuteQuery is called without custom bookmark managers and the lower-level // neo4j.SessionWithContext APIs are called as well. // In that case, the recommended approach is as follows: // results, err := driver.ExecuteQuery(ctx, query, params) // // [...] do something with results and error - // bookmarkManager := driver.DefaultExecuteQueryBookmarkManager() + // bookmarkManager := driver.ExecuteQueryBookmarkManager() // // maintain consistency with sessions as well // session := driver.NewSession(ctx, neo4j.SessionConfig {BookmarkManager: bookmarkManager}) // // [...] run something within the session - DefaultExecuteQueryBookmarkManager() BookmarkManager + ExecuteQueryBookmarkManager() BookmarkManager // Target returns the url this driver is bootstrapped Target() url.URL // NewSession creates a new session based on the specified session configuration. @@ -87,9 +84,6 @@ type DriverWithContext interface { } // ResultTransformer is a record accumulator that produces an instance of T when the processing of records is over. -// -// ResultTransformer is part of the ExecuteQuery preview feature (see README on what it means in terms of support -// and compatibility guarantees) type ResultTransformer[T any] interface { // Accept is called whenever a new record is fetched from the server // Implementers are free to accumulate or discard the specified record @@ -261,13 +255,13 @@ func routingContextFromUrl(useRouting bool, u *url.URL) (map[string]string, erro } type sessionRouter interface { - // Readers returns the list of servers that can serve reads on the requested database. + // Read returns the list of servers that can serve reads on the requested database. // note: bookmarks are lazily supplied, only when a new routing table needs to be fetched // this is needed because custom bookmark managers may provide bookmarks from external systems // they should not be called when it is not needed (e.g. when a routing table is cached) Readers(ctx context.Context, bookmarks func(context.Context) ([]string, error), database string, boltLogger log.BoltLogger) ([]string, error) - // Writers returns the list of servers that can serve writes on the requested database. - // note: bookmarks are lazily supplied, see Readers documentation to learn why + // Write returns the list of servers that can serve writes on the requested database. + // note: bookmarks are lazily supplied, see Read documentation to learn why Writers(ctx context.Context, bookmarks func(context.Context) ([]string, error), database string, boltLogger log.BoltLogger) ([]string, error) // GetNameOfDefaultDatabase returns the name of the default database for the specified user. // The correct database name is needed when requesting readers or writers. @@ -292,7 +286,7 @@ type driverWithContext struct { executeQueryBookmarkManagerInitializer sync.Once // instance of the bookmark manager only used by default by managed sessions of ExecuteQuery // this is *not* used by default by user-created session (see NewSession) - defaultExecuteQueryBookmarkManager BookmarkManager + executeQueryBookmarkManager BookmarkManager } func (d *driverWithContext) Target() url.URL { @@ -352,8 +346,6 @@ func (d *driverWithContext) Close(ctx context.Context) error { // ExecuteQuery runs the specified query with its parameters and returns the query result, transformed by the specified // ResultTransformer function. // -// This is currently a preview feature (see README on what it means in terms of support and compatibility guarantees) -// // result, err := ExecuteQuery[*EagerResult](ctx, driver, query, params, EagerResultTransformer) // // Passing a nil ResultTransformer function is invalid and will return an error. @@ -388,7 +380,7 @@ func (d *driverWithContext) Close(ctx context.Context) error { // // ExecuteQuery[T](ctx, driver, query, params, transformerFunc, func(config *neo4j.ExecuteQueryConfiguration) { // config.Database = "my-db" -// config.RoutingControl = neo4j.Writers +// config.RoutingControl = neo4j.Write // config.ImpersonatedUser = "selda_bağcan" // }) // @@ -413,7 +405,7 @@ func (d *driverWithContext) Close(ctx context.Context) error { // BookmarkManager: bookmarkManager, // }) // defer handleClose(ctx, session) -// // session.ExecuteRead is called if the routing is set to neo4j.Readers +// // session.ExecuteRead is called if the routing is set to neo4j.Read // result, _ := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (any, error) { // result, _ := tx.Run(ctx, "", parameters) // records, _ := result.Collect(ctx) // real implementation does not use Collect @@ -457,7 +449,7 @@ func ExecuteQuery[T any]( "ResultTransformer implementation"} } - bookmarkManager := driver.DefaultExecuteQueryBookmarkManager() + bookmarkManager := driver.ExecuteQueryBookmarkManager() configuration := &ExecuteQueryConfiguration{ BookmarkManager: bookmarkManager, } @@ -479,13 +471,13 @@ func ExecuteQuery[T any]( return result.(T), err } -func (d *driverWithContext) DefaultExecuteQueryBookmarkManager() BookmarkManager { +func (d *driverWithContext) ExecuteQueryBookmarkManager() BookmarkManager { d.executeQueryBookmarkManagerInitializer.Do(func() { - if d.defaultExecuteQueryBookmarkManager == nil { // this allows tests to init the field themselves - d.defaultExecuteQueryBookmarkManager = NewBookmarkManager(BookmarkManagerConfig{}) + if d.executeQueryBookmarkManager == nil { // this allows tests to init the field themselves + d.executeQueryBookmarkManager = NewBookmarkManager(BookmarkManagerConfig{}) } }) - return d.defaultExecuteQueryBookmarkManager + return d.executeQueryBookmarkManager } func executeQueryCallback[T any]( @@ -546,35 +538,23 @@ func (e *eagerResultTransformer) Complete(keys []string, summary ResultSummary) } // ExecuteQueryConfigurationOption is a callback that configures the execution of DriverWithContext.ExecuteQuery -// -// ExecuteQueryConfigurationOption is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) type ExecuteQueryConfigurationOption func(*ExecuteQueryConfiguration) // ExecuteQueryWithReadersRouting configures DriverWithContext.ExecuteQuery to route to reader members of the cluster -// -// ExecuteQueryWithReadersRouting is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithReadersRouting() ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { - configuration.Routing = Readers + configuration.Routing = Read } } // ExecuteQueryWithWritersRouting configures DriverWithContext.ExecuteQuery to route to writer members of the cluster -// -// ExecuteQueryWithWritersRouting is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithWritersRouting() ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { - configuration.Routing = Writers + configuration.Routing = Write } } // ExecuteQueryWithImpersonatedUser configures DriverWithContext.ExecuteQuery to impersonate the specified user -// -// ExecuteQueryWithImpersonatedUser is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithImpersonatedUser(user string) ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { configuration.ImpersonatedUser = user @@ -582,9 +562,6 @@ func ExecuteQueryWithImpersonatedUser(user string) ExecuteQueryConfigurationOpti } // ExecuteQueryWithDatabase configures DriverWithContext.ExecuteQuery to target the specified database -// -// ExecuteQueryWithDatabase is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithDatabase(db string) ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { configuration.Database = db @@ -592,9 +569,6 @@ func ExecuteQueryWithDatabase(db string) ExecuteQueryConfigurationOption { } // ExecuteQueryWithBookmarkManager configures DriverWithContext.ExecuteQuery to rely on the specified BookmarkManager -// -// ExecuteQueryWithBookmarkManager is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithBookmarkManager(bookmarkManager BookmarkManager) ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { configuration.BookmarkManager = bookmarkManager @@ -602,9 +576,6 @@ func ExecuteQueryWithBookmarkManager(bookmarkManager BookmarkManager) ExecuteQue } // ExecuteQueryWithoutBookmarkManager configures DriverWithContext.ExecuteQuery to not rely on any BookmarkManager -// -// ExecuteQueryWithoutBookmarkManager is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithoutBookmarkManager() ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { configuration.BookmarkManager = nil @@ -612,9 +583,6 @@ func ExecuteQueryWithoutBookmarkManager() ExecuteQueryConfigurationOption { } // ExecuteQueryWithBoltLogger configures DriverWithContext.ExecuteQuery to log Bolt messages with the provided BoltLogger -// -// ExecuteQueryWithBoltLogger is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) func ExecuteQueryWithBoltLogger(boltLogger log.BoltLogger) ExecuteQueryConfigurationOption { return func(configuration *ExecuteQueryConfiguration) { configuration.BoltLogger = boltLogger @@ -622,9 +590,6 @@ func ExecuteQueryWithBoltLogger(boltLogger log.BoltLogger) ExecuteQueryConfigura } // ExecuteQueryConfiguration holds all the possible configuration settings for DriverWithContext.ExecuteQuery -// -// ExecuteQueryConfiguration is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) type ExecuteQueryConfiguration struct { Routing RoutingControl ImpersonatedUser string @@ -634,22 +599,13 @@ type ExecuteQueryConfiguration struct { } // RoutingControl specifies how the query executed by DriverWithContext.ExecuteQuery is to be routed -// -// RoutingControl is part of the ExecuteQuery preview feature (see README on what it means in terms of support and -// compatibility guarantees) type RoutingControl int const ( - // Writers routes the query to execute to a writer member of the cluster - // - // Writers is part of the ExecuteQuery preview feature (see README on what it means in terms of - // support and compatibility guarantees) - Writers RoutingControl = iota - // Readers routes the query to execute to a writer member of the cluster - // - // Readers is part of the ExecuteQuery preview feature (see README on what it means in terms of - // support and compatibility guarantees) - Readers + // Write routes the query to execute to a writer member of the cluster + Write RoutingControl = iota + // Read routes the query to execute to a writer member of the cluster + Read ) func (c *ExecuteQueryConfiguration) toSessionConfig() SessionConfig { @@ -665,19 +621,16 @@ type transactionFunction func(context.Context, ManagedTransactionWork, ...func(* func (c *ExecuteQueryConfiguration) selectTxFunctionApi(session SessionWithContext) (transactionFunction, error) { switch c.Routing { - case Readers: + case Read: return session.ExecuteRead, nil - case Writers: + case Write: return session.ExecuteWrite, nil } - return nil, fmt.Errorf("unsupported routing control, expected %d (Writers) or %d (Readers) "+ - "but got: %d", Writers, Readers, c.Routing) + return nil, fmt.Errorf("unsupported routing control, expected %d (Write) or %d (Read) "+ + "but got: %d", Write, Read, c.Routing) } // EagerResult holds the result and result metadata of the query executed via DriverWithContext.ExecuteQuery -// -// EagerResult is part of the ExecuteQuery preview feature (see README on what it means in terms of -// support and compatibility guarantees) type EagerResult struct { Keys []string Records []*Record diff --git a/neo4j/driver_with_context_examples_test.go b/neo4j/driver_with_context_examples_test.go index 38f5dde4..2df5428f 100644 --- a/neo4j/driver_with_context_examples_test.go +++ b/neo4j/driver_with_context_examples_test.go @@ -1,3 +1,22 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed 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 + * + * https://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. + */ + package neo4j import ( @@ -63,7 +82,7 @@ func ExampleExecuteQuery_default_bookmark_manager_explicit_reuse() { // retrieve the default bookmark manager used by the previous call (since there was no bookmark manager explicitly // configured) - bookmarkManager := myDriver.DefaultExecuteQueryBookmarkManager() + bookmarkManager := myDriver.ExecuteQueryBookmarkManager() session := myDriver.NewSession(ctx, SessionConfig{BookmarkManager: bookmarkManager}) // the following transaction function is guaranteed to see the result of the previous query diff --git a/neo4j/driver_with_context_test.go b/neo4j/driver_with_context_test.go index 16ad235d..87e76d32 100644 --- a/neo4j/driver_with_context_test.go +++ b/neo4j/driver_with_context_test.go @@ -1,3 +1,22 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed 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 + * + * https://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. + */ + package neo4j import ( @@ -182,7 +201,7 @@ func TestDriverExecuteQuery(outer *testing.T) { summary: summary, }}, expectedSessionConfig: defaultSessionConfig, - expectedErr: fmt.Errorf("unsupported routing control, expected 0 (Writers) or 1 (Readers) but got: 42"), + expectedErr: fmt.Errorf("unsupported routing control, expected 0 (Write) or 1 (Read) but got: 42"), }, { description: "returns error when result transformer function is nil", @@ -397,8 +416,8 @@ func TestDriverExecuteQuery(outer *testing.T) { return testCase.createSession }, delegate: &driverWithContext{ - defaultExecuteQueryBookmarkManager: defaultBookmarkManager, - mut: racing.NewMutex(), + executeQueryBookmarkManager: defaultBookmarkManager, + mut: racing.NewMutex(), }, } @@ -431,7 +450,7 @@ func TestDriverExecuteQuery(outer *testing.T) { callExecuteQueryOrBookmarkManagerGetter(driver, i) storeBookmarkManagerAddress( &bookmarkManagerAddresses, - driver.delegate.defaultExecuteQueryBookmarkManager.(*bookmarkManager)) + driver.delegate.executeQueryBookmarkManager.(*bookmarkManager)) wait.Done() }(i) } @@ -445,7 +464,7 @@ func TestDriverExecuteQuery(outer *testing.T) { if len(addressCounts) != 1 { t.Errorf("expected exactly 1 bookmark manager pointer to have been created, got %v", addressCounts) } - address := uintptr(unsafe.Pointer(driver.delegate.defaultExecuteQueryBookmarkManager.(*bookmarkManager))) + address := uintptr(unsafe.Pointer(driver.delegate.executeQueryBookmarkManager.(*bookmarkManager))) if count, found := addressCounts[address]; !found || count != int32(goroutineCount) { t.Errorf("expected pointer address %v to be seen %d time(s), got these instead %v", address, count, addressCounts) } @@ -455,7 +474,7 @@ func TestDriverExecuteQuery(outer *testing.T) { func callExecuteQueryOrBookmarkManagerGetter(driver DriverWithContext, i int) { if i%2 == 0 { // this lazily initializes the default bookmark manager - _ = driver.DefaultExecuteQueryBookmarkManager() + _ = driver.ExecuteQueryBookmarkManager() } else { // this as well _, _ = ExecuteQuery[*EagerResult](context.Background(), driver, "RETURN 42", nil, EagerResultTransformer) @@ -500,8 +519,8 @@ type driverDelegate struct { newSession func(context.Context, SessionConfig) SessionWithContext } -func (d *driverDelegate) DefaultExecuteQueryBookmarkManager() BookmarkManager { - return d.delegate.DefaultExecuteQueryBookmarkManager() +func (d *driverDelegate) ExecuteQueryBookmarkManager() BookmarkManager { + return d.delegate.ExecuteQueryBookmarkManager() } func (d *driverDelegate) Target() url.URL { diff --git a/neo4j/session_with_context.go b/neo4j/session_with_context.go index 77ec09ce..3641bd44 100644 --- a/neo4j/session_with_context.go +++ b/neo4j/session_with_context.go @@ -8,13 +8,13 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://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. + * 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. */ package neo4j @@ -150,8 +150,6 @@ type SessionConfig struct { ImpersonatedUser string // BookmarkManager defines a central point to externally supply bookmarks // and be notified of bookmark updates per database - // This is part of the BookmarkManager preview feature (see README on what it means in terms of - // support and compatibility guarantees) // Since 5.0 // default: nil (no-op) BookmarkManager BookmarkManager diff --git a/testkit-backend/backend.go b/testkit-backend/backend.go index 2ed4ab48..74da7d40 100644 --- a/testkit-backend/backend.go +++ b/testkit-backend/backend.go @@ -8,13 +8,13 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * https://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. + * 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. */ package main @@ -518,9 +518,9 @@ func (b *backend) handleRequest(req map[string]any) { if routing != nil { switch routing { case "r": - config.Routing = neo4j.Readers + config.Routing = neo4j.Read case "w": - config.Routing = neo4j.Writers + config.Routing = neo4j.Write default: b.writeError(fmt.Errorf("unexpected executequery routing value: %v", routing)) return