Last active
July 1, 2022 11:47
-
-
Save fbiville/db472db5807fd1f39eecbc83bfa01323 to your computer and use it in GitHub Desktop.
Cypher merge is not sufficient to guarantee uniqueness
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package container | |
import ( | |
"context" | |
"fmt" | |
"github.com/neo4j/neo4j-go-driver/v4/neo4j" | |
"github.com/testcontainers/testcontainers-go" | |
"github.com/testcontainers/testcontainers-go/wait" | |
) | |
type ContainerConfiguration struct { | |
Neo4jVersion string | |
Username string | |
Password string | |
Scheme string | |
} | |
func (config ContainerConfiguration) neo4jAuthEnvVar() string { | |
return fmt.Sprintf("%s/%s", config.Username, config.Password) | |
} | |
func (config ContainerConfiguration) neo4jAuthToken() neo4j.AuthToken { | |
return neo4j.BasicAuth(config.Username, config.Password, "") | |
} | |
func StartSingleInstance(ctx context.Context, config ContainerConfiguration) (testcontainers.Container, neo4j.Driver, error) { | |
version := config.Neo4jVersion | |
request := testcontainers.ContainerRequest{ | |
Image: fmt.Sprintf("neo4j:%s", version), | |
ExposedPorts: []string{"7687/tcp"}, | |
Env: map[string]string{ | |
"NEO4J_AUTH": config.neo4jAuthEnvVar(), | |
"NEO4J_ACCEPT_LICENSE_AGREEMENT": "yes", | |
}, | |
WaitingFor: boltReadyStrategy(), | |
} | |
container, err := testcontainers.GenericContainer(ctx, | |
testcontainers.GenericContainerRequest{ | |
ContainerRequest: request, | |
Started: true, | |
}) | |
if err != nil { | |
return nil, nil, err | |
} | |
driver, err := newNeo4jDriver(ctx, config.Scheme, container, config.neo4jAuthToken()) | |
return container, driver, err | |
} | |
func boltReadyStrategy() *wait.LogStrategy { | |
return wait.ForLog("Bolt enabled") | |
} | |
func newNeo4jDriver(ctx context.Context, scheme string, container testcontainers.Container, auth neo4j.AuthToken) (neo4j.Driver, error) { | |
port, err := container.MappedPort(ctx, "7687") | |
if err != nil { | |
return nil, err | |
} | |
return newDriver(scheme, port.Int(), auth) | |
} | |
func newDriver(scheme string, port int, auth neo4j.AuthToken) (neo4j.Driver, error) { | |
uri := fmt.Sprintf("%s://localhost:%d", scheme, port) | |
return neo4j.NewDriver(uri, auth) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"github.com/fbiville/neo4j-merge-gotcha/pkg/container" | |
"github.com/fbiville/neo4j-merge-gotcha/pkg/errors" | |
"github.com/neo4j/neo4j-go-driver/v4/neo4j" | |
"sync" | |
) | |
func main() { | |
ctx := context.Background() | |
instance, driver, err := container.StartSingleInstance(ctx, container.ContainerConfiguration{ | |
Neo4jVersion: "4.4", | |
Username: "neo4j", | |
Password: "s3cr3t", | |
Scheme: "neo4j", | |
}) | |
errors.PanicOnErr(err) | |
defer func() { | |
errors.PanicOnErr(instance.Terminate(ctx)) | |
}() | |
defer func() { | |
errors.PanicOnErr(driver.Close()) | |
}() | |
params := map[string]interface{}{ | |
"name": "Jane Doe", | |
} | |
goRoutines := 10_000 | |
group := sync.WaitGroup{} | |
group.Add(goRoutines) | |
for i := 0; i < goRoutines; i++ { | |
go func() { | |
session := driver.NewSession(neo4j.SessionConfig{}) | |
result, err := session.Run("MERGE (:Person {name: $name})", params) | |
errors.PanicOnErr(err) | |
_, err = result.Consume() | |
errors.PanicOnErr(err) | |
errors.PanicOnErr(session.Close()) | |
group.Done() | |
}() | |
} | |
group.Wait() | |
session := driver.NewSession(neo4j.SessionConfig{}) | |
defer func() { | |
errors.PanicOnErr(session.Close()) | |
}() | |
result, err := session.Run("MATCH (:Person {name: $name}) RETURN COUNT(*) AS count", params) | |
errors.PanicOnErr(err) | |
record, err := result.Single() | |
errors.PanicOnErr(err) | |
count, _ := record.Get("count") | |
fmt.Printf("Got %d person node(s)", count) // this will likely be larger than 1 | |
// use https://neo4j.com/docs/cypher-manual/current/constraints/syntax/#administration-constraints-syntax-create-unique to make sure uniqueness is guaranteed | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment