rlch / neogo

A Golang-ORM for Neo4J which creates idiomatic & fluent Cypher.
MIT License
7 stars 4 forks source link

Can't run example code #15

Open guilinonline opened 1 month ago

guilinonline commented 1 month ago
func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }

    c := neogo.New(driver)
    var n []any

    err = c.Exec().Match(db.Node(db.Var(n, db.Name("n")))).Return(n).Print().Run(ctx)
    if err != nil {
        panic(err)
    }
    for _, a := range n {
        fmt.Println(a)
    }
}

这个代码老是报空指针 image

guilinonline commented 1 month ago
type Person struct {
    neogo.Node `neo4j:"Person"`

    Name    string `json:"name"`
    Surname string `json:"surname"`
    Age     int    `json:"age"`
}

func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }

    c := neogo.New(driver)
    person := Person{
        Name:    "Spongebob",
        Surname: "Squarepants",
    }
    // person.GenerateID() can be used
    person.ID = "some-unique-id"

    err = c.Exec().
        Create(db.Node(&person)).
        Set(db.SetPropValue(&person.Age, 20)).
        Return(&person).
        Print().
        Run(ctx)
    if err != nil {
        panic(err)
    }
}

这个代码都跑不通我就很郁闷了

rlch commented 4 weeks ago

Hey @guilinonline

Please speak in English so others facing similar issues can benefit from this discussion without needing to translate.

这个代码老是报空指针

This is false, you can use the WithCausalConsistency config to add causal consistency to a given context. Though we've tested in production, I would advise against using this functionality for now given it lacks documentation and unit-tests. I've exposed it for internal usage with my team.

这个代码都跑不通我就很郁闷了

If you want me to help you here, you'll need to send me the error you're receiving when running this code instead of telling me how depressed you are. On my end this code works fine with a Neo4J database running on my local system authorized using basic authentication. I would say the most likely problem here is a networking one. Make sure you're running Neo4J on the same system as the Go binary if using localhost. If you're using Docker then set network_mode: host.

guilinonline commented 4 weeks ago

My Neo4j database is running via Docker, with the port mapped to the host machine. During development, I connect to the port on the host machine. Below is the code:

func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }

    c := neogo.New(driver)

var n []any
    err = c.Exec().
        Match(db.Node(db.Var(n,db.Name("n")))).
        Return(n).
        Print().
        Run(ctx)
    if err != nil {
        panic(err)
    }
}
# MATCH (n)
   RETURN n

The correct CQL code can actually be rendered, and when I output it to the screen using the Print method, there’s no issue. However, when I execute the Run(ctx) method, I get a null pointer exception. Through debugging, I found that on line 173 in the driver.go file under /Users/apple/go/pkg/mod/github.com/rlch/neogo@v0.0.0-20240808010006-886465d4aa30/driver.go, the variable d is nil. This obviously suggests that there might be an issue with initialization, or certain configurations were not called, as shown in the figure below: image

guilinonline commented 4 weeks ago

Could you provide some production-ready examples for me? My email is 21108858@qq.com. thanks

guilinonline commented 4 weeks ago

log:

GOROOT=/Users/apple/.gvm/gos/go1.22.4 #gosetup
GOPATH=/Users/apple/go #gosetup
GOPROXY=https://goproxy.io,direct #gosetup
/Users/apple/.gvm/gos/go1.22.4/bin/go build -o /Users/apple/Library/Caches/JetBrains/GoLand2024.1/tmp/GoLand/___1go_build_uploadFile -gcflags all=-N -l uploadFile #gosetup
/Applications/GoLand.app/Contents/plugins/go-plugin/lib/dlv/mac/dlv --listen=127.0.0.1:50927 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /Users/apple/Library/Caches/JetBrains/GoLand2024.1/tmp/GoLand/___1go_build_uploadFile --
API server listening at: 127.0.0.1:50927
debugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-1403.0.17.67
 for x86_64.
Got a connection, launched process /Users/apple/Library/Caches/JetBrains/GoLand2024.1/tmp/GoLand/___1go_build_uploadFile (pid = 49075).
MATCH (n)
RETURN n
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xb04f086]

goroutine 1 [running]:
github.com/rlch/neogo.(*driver).ensureCausalConsistency(0x0, {0xb544bb0, 0xc065560}, 0xc0005778d8)
        /Users/apple/go/pkg/mod/github.com/rlch/neogo@v0.0.0-20240808010006-886465d4aa30/driver.go:174 +0x26
github.com/rlch/neogo.(*runnerImpl).executeTransaction(0xc000040660, {0xb544bb0, 0xc065560}, 0xc0005115c0, 0xc00079ce80)
        /Users/apple/go/pkg/mod/github.com/rlch/neogo@v0.0.0-20240808010006-886465d4aa30/client_impl.go:531 +0x24a
github.com/rlch/neogo.(*runnerImpl).run(0xc000040660, {0xb544bb0, 0xc065560}, 0x0, 0x0)
        /Users/apple/go/pkg/mod/github.com/rlch/neogo@v0.0.0-20240808010006-886465d4aa30/client_impl.go:269 +0x647
github.com/rlch/neogo.(*runnerImpl).Run(0xc000040660, {0xb544bb0, 0xc065560})
        /Users/apple/go/pkg/mod/github.com/rlch/neogo@v0.0.0-20240808010006-886465d4aa30/client_impl.go:294 +0x5d
main.main()
        /Users/apple/go/src/uploadFile/neo4jorm.go:28 +0x495
Exiting.

Debugger finished with the exit code 0
guilinonline commented 4 weeks ago
func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }
    sw := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
    result, err := sw.Run(ctx, "MATCH (n) RETURN n", nil)
    if err != nil {
        panic(err)
    }
    for result.Next(ctx) {
        record := result.Record()
        fmt.Println(record)
    }
}

This code is working, so it's not a network issue.

rlch commented 4 weeks ago

Thanks for the information. I believe I've fixed it - can you try the example on the latest commit to main?

guilinonline commented 4 weeks ago

Before your fix, I noticed that after calling WithCausalConsistency, data could be created and read normally. After your fix, did you add the field driver:d to the return when creating the driver? If so, would this override the causalConsistencyKey set by WithCausalConsistency? Below is the usage method I guessed after reading the source code before the fix:

I can read the data, but it's unable to be parsed, possibly due to a type issue.

func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }
    conf := neogo.WithCausalConsistency(func(ctx context.Context) string {
        return "test"
    })

    cli := neogo.New(driver, conf)
    err = add(ctx, cli, "test:lab1", db.Props{
        "si":           "'1025'",
        "dataSysLevel": "'N'",
        "dtc":          "'Arch'",
        "plt":          "'auxo'",
    })
    if err != nil {
        panic(err)
    }

}

func add(ctx context.Context, cli neogo.Driver, node string, data db.Props) error {
    session := cli.WriteSession(ctx, func(config *neo4j.SessionConfig) {
        config.AccessMode = neo4j.AccessModeWrite
    })

    return session.WriteTransaction(ctx, func(start func() neogo.Query) error {
        return start().Create(db.Node(db.Var(node, data))).Print().Run(ctx)
    })
}

After your fix, I tested it and was able to read the data, but couldn't parse the data, possibly due to a type issue. Below is the test code:

func TestDri(t *testing.T) {
    drive, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }
    cli := New(drive)
    var n []any
    err = cli.Exec().Match(db.Node(db.Var(n, db.Name("n")))).Return(n).Print().Run(context.Background())
    if err != nil {
        t.Fatal(err)
    }
}

After your fix, it also works, but I'm concerned it might affect the use of the WithCausalConsistency method. The fix allows for both creating and reading data. However, in both cases, the data cannot be parsed. I set breakpoints in the code and can see that data is being read from the database into variables.

log:

GOROOT=/Users/apple/.gvm/gos/go1.22.4 #gosetup
GOPATH=/Users/apple/go #gosetup
GOPROXY=https://mirrors.aliyun.com/goproxy/,direct #gosetup
/Users/apple/.gvm/gos/go1.22.4/bin/go test -c -o /Users/apple/Library/Caches/JetBrains/GoLand2024.1/tmp/GoLand/___TestDri_in_github_com_rlch_neogo.test -gcflags all=-N -l github.com/rlch/neogo #gosetup
/Users/apple/.gvm/gos/go1.22.4/bin/go tool test2json -t /Applications/GoLand.app/Contents/plugins/go-plugin/lib/dlv/mac/dlv --listen=127.0.0.1:62938 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /Users/apple/Library/Caches/JetBrains/GoLand2024.1/tmp/GoLand/___TestDri_in_github_com_rlch_neogo.test -- -test.v -test.paniconexit0 -test.run ^\QTestDri\E$
API server listening at: 127.0.0.1:62938
=== RUN   TestDri
MATCH (n)
RETURN n
--- FAIL: TestDri (17.65s)
panic: reflect: reflect.Value.Set using unaddressable value [recovered]
    panic: reflect: reflect.Value.Set using unaddressable value

goroutine 50 [running]:
testing.tRunner.func1.2({0x415d660, 0xc000046b00})
    /Users/apple/.gvm/gos/go1.22.4/src/testing/testing.go:1631 +0x49e
testing.tRunner.func1()
    /Users/apple/.gvm/gos/go1.22.4/src/testing/testing.go:1634 +0x669
panic({0x415d660?, 0xc000046b00?})
    /Users/apple/.gvm/gos/go1.22.4/src/runtime/panic.go:770 +0x136
reflect.flag.mustBeAssignableSlow(0x97)
    /Users/apple/.gvm/gos/go1.22.4/src/reflect/value.go:272 +0xa8
reflect.flag.mustBeAssignable(0x97)
    /Users/apple/.gvm/gos/go1.22.4/src/reflect/value.go:259 +0x49
reflect.Value.Set({0x4153220, 0x470a2a0, 0x97}, {0x4153220, 0xc000012678, 0x97})
    /Users/apple/.gvm/gos/go1.22.4/src/reflect/value.go:2319 +0x45
github.com/rlch/neogo.(*session).unmarshalRecords(0xc0005d0090, 0xc00052a210, {0xc0004b8a80, 0x5, 0x6})
    /Users/apple/go/src/neogo/client_impl.go:467 +0xaf8
github.com/rlch/neogo.(*session).unmarshalResult(0xc0005d0090, {0x4277d10, 0x47080e0}, 0xc00052a210, {0x427b1c0, 0xc0000aa100})
    /Users/apple/go/src/neogo/client_impl.go:439 +0x51b
github.com/rlch/neogo.(*runnerImpl).run.func1({0x4274530, 0xc0003bc1c0})
    /Users/apple/go/src/neogo/client_impl.go:277 +0x311
github.com/neo4j/neo4j-go-driver/v5/neo4j.(*sessionWithContext).executeTransactionFunction(0xc0005b2140, {0x4277d10, 0x47080e0}, 0x1, {0x8000000000000000, 0x0}, 0xc00004b3f0, 0xc00052e140)
    /Users/apple/go/pkg/mod/github.com/neo4j/neo4j-go-driver/v5@v5.10.0/neo4j/session_with_context.go:467 +0x7ad
github.com/neo4j/neo4j-go-driver/v5/neo4j.(*sessionWithContext).runRetriable(0xc0005b2140, {0x4277d10, 0x47080e0}, 0x1, 0xc00052e140, {0xc00011c058, 0x1, 0x1})
    /Users/apple/go/pkg/mod/github.com/neo4j/neo4j-go-driver/v5@v5.10.0/neo4j/session_with_context.go:416 +0x556
github.com/neo4j/neo4j-go-driver/v5/neo4j.(*sessionWithContext).ExecuteRead(0xc0005b2140, {0x4277d10, 0x47080e0}, 0xc00052e140, {0xc00011c058, 0x1, 0x1})
    /Users/apple/go/pkg/mod/github.com/neo4j/neo4j-go-driver/v5@v5.10.0/neo4j/session_with_context.go:359 +0xac
github.com/rlch/neogo.(*runnerImpl).executeTransaction(0xc000528110, {0x4277d10, 0x47080e0}, 0xc00052a210, 0xc00052e140)
    /Users/apple/go/src/neogo/client_impl.go:569 +0x7d3
github.com/rlch/neogo.(*runnerImpl).run(0xc000528110, {0x4277d10, 0x47080e0}, 0x0, 0x0)
    /Users/apple/go/src/neogo/client_impl.go:269 +0x647
github.com/rlch/neogo.(*runnerImpl).Run(0xc000528110, {0x4277d10, 0x47080e0})
    /Users/apple/go/src/neogo/client_impl.go:294 +0x5d
github.com/rlch/neogo.TestDri(0xc000594340)
    /Users/apple/go/src/neogo/driver_test.go:329 +0x419
testing.tRunner(0xc000594340, 0x426c358)
    /Users/apple/.gvm/gos/go1.22.4/src/testing/testing.go:1689 +0x1da
created by testing.(*T).Run in goroutine 1
    /Users/apple/.gvm/gos/go1.22.4/src/testing/testing.go:1742 +0x7d3

Debugger finished with the exit code 0

image

guilinonline commented 4 weeks ago

I still don't fully understand the best practices. I noticed that the package provides many methods that can achieve the final goal. Could you introduce the best practices for create, read, update, and delete (CRUD) operations? Currently, the community doesn't have a well-established SDK, and I hope you can make it better.

guilinonline commented 4 weeks ago

new version package,this code is working:

    var n []any
    err = cli.Exec().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Print().Run(ctx)

    if err != nil {
        panic(err)
    }

The reason for not being able to parse the data is that the variable used to receive the data must be a pointer. The example provided is incorrect;

guilinonline commented 4 weeks ago

I found that data can be read using the following two methods. Which one should be used in production? Is my usage correct?

func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }
    conf := neogo.WithCausalConsistency(func(ctx context.Context) string {
        return "test"
    })

    cli := neogo.New(driver, conf)
    var n []any
        //Example 1:
    err = cli.Exec().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Print().Run(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(n)
        //Example 2:
    data, err := readRecord(ctx, cli)
    if err != nil {
        panic(err)
    }
    fmt.Println(data)

}

func readRecord(ctx context.Context, cli neogo.Driver) ([]any, error) {
    session := cli.ReadSession(ctx, func(config *neo4j.SessionConfig) {
        config.AccessMode = neo4j.AccessModeRead
    })
    var n []any
    if err := session.ReadTransaction(ctx, func(start func() neogo.Query) error {
        return start().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Run(ctx)
    }); err != nil {
        return nil, err
    }
    return n, nil
}
rlch commented 4 weeks ago

I found that data can be read using the following two methods. Which one should be used in production? Is my usage correct?

func main() {
  ctx := context.Background()
  // 创建驱动
  driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
      neo4j.BasicAuth("neo4j", "Aa111111", ""))
  if err != nil {
      panic(err)
  }
  conf := neogo.WithCausalConsistency(func(ctx context.Context) string {
      return "test"
  })

  cli := neogo.New(driver, conf)
  var n []any
        //Example 1:
  err = cli.Exec().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Print().Run(ctx)
  if err != nil {
      panic(err)
  }
  fmt.Println(n)
        //Example 2:
  data, err := readRecord(ctx, cli)
  if err != nil {
      panic(err)
  }
  fmt.Println(data)

}

func readRecord(ctx context.Context, cli neogo.Driver) ([]any, error) {
  session := cli.ReadSession(ctx, func(config *neo4j.SessionConfig) {
      config.AccessMode = neo4j.AccessModeRead
  })
  var n []any
  if err := session.ReadTransaction(ctx, func(start func() neogo.Query) error {
      return start().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Run(ctx)
  }); err != nil {
      return nil, err
  }
  return n, nil
}

Both examples you've given are functionally equivalent at the database-level. The only difference is that in the first example you don't have access to the underlying session; and so you can only perform a single transaction. The second gives you access to a read-only session which you can perform multiple transactions on.

I tend to use Exec as the access-mode is inferred (so less developer overhead) and I find most use-cases only require single transactions. It also comes with less boilerplate and doesn't require the session to be closed -- which, by the way, you should do in readRecord!

rlch commented 4 weeks ago

I still don't fully understand the best practices. I noticed that the package provides many methods that can achieve the final goal. Could you introduce the best practices for create, read, update, and delete (CRUD) operations? Currently, the community doesn't have a well-established SDK, and I hope you can make it better.

Completely agree. In honesty me nor my team hasn't found the time to properly document all use-cases of the API, and I'm rather hesitant to establish best-practices and proper usage docs until I can establish full feature-parity with the official SDK.

The goal of this package at this stage is to provide a friendlier interface to interacting with Neo4J (de/serialization, transaction and session-management, ORM) than the official SDK which, from our internal usage, we're quite close to achieving.

Any contributions to improve docs/examples/API are very welcome.

guilinonline commented 4 weeks ago

I still don't fully understand the best practices. I noticed that the package provides many methods that can achieve the final goal. Could you introduce the best practices for create, read, update, and delete (CRUD) operations? Currently, the community doesn't have a well-established SDK, and I hope you can make it better.

Completely agree. In honesty me nor my team hasn't found the time to properly document all use-cases of the API, and I'm rather hesitant to establish best-practices and proper usage docs until I can establish full feature-parity with the official SDK.

The goal of this package at this stage is to provide a friendlier interface to interacting with Neo4J (de/serialization, transaction and session-management, ORM) than the official SDK which, from our internal usage, we're quite close to achieving.

Any contributions to improve docs/examples/API are very welcome.

For daily operations, this package is sufficient and convenient. I think it can be released for public testing. My CMDB project plans to use the neogo package to manage Neo4j transactions. Thank you very much! In the future, I will accumulate some experience and share any issues I encounter.

guilinonline commented 4 weeks ago

I found that data can be read using the following two methods. Which one should be used in production? Is my usage correct?

func main() {
    ctx := context.Background()
    // 创建驱动
    driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687",
        neo4j.BasicAuth("neo4j", "Aa111111", ""))
    if err != nil {
        panic(err)
    }
    conf := neogo.WithCausalConsistency(func(ctx context.Context) string {
        return "test"
    })

    cli := neogo.New(driver, conf)
    var n []any
        //Example 1:
    err = cli.Exec().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Print().Run(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(n)
        //Example 2:
    data, err := readRecord(ctx, cli)
    if err != nil {
        panic(err)
    }
    fmt.Println(data)

}

func readRecord(ctx context.Context, cli neogo.Driver) ([]any, error) {
    session := cli.ReadSession(ctx, func(config *neo4j.SessionConfig) {
        config.AccessMode = neo4j.AccessModeRead
    })
    var n []any
    if err := session.ReadTransaction(ctx, func(start func() neogo.Query) error {
        return start().Match(db.Node(db.Var(&n, db.Name("n")))).Return("n").Run(ctx)
    }); err != nil {
        return nil, err
    }
    return n, nil
}

Both examples you've given are functionally equivalent at the database-level. The only difference is that in the first example you don't have access to the underlying session; and so you can only perform a single transaction. The second gives you access to a read-only session which you can perform multiple transactions on.

I tend to use Exec as the access-mode is inferred (so less developer overhead) and I find most use-cases only require single transactions. It also comes with less boilerplate and doesn't require the session to be closed -- which, by the way, you should do in readRecord!

You are right, and I completely agree with your opinion.