query

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 13 Imported by: 0

README

Neo4j Query API Go SDK

Overview

A Go client for the Neo4j Query API. Execute Cypher queries against Neo4j databases using plain HTTP — no Bolt protocol, no driver, no binary dependencies. The SDK mirrors the transformer pattern from the official Neo4j Go driver, so switching between them is straightforward.

result, err := query.WithTransformer(client.Query, ctx,
    "MATCH (n:Person) RETURN n.name AS name", nil,
    query.EagerResultTransformer)

Table of Contents


Installation

go get github.com/neo4j-contrib/query-go-sdk

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    query "github.com/neo4j-contrib/query-go-sdk"
)

func main() {
    client, err := query.NewClient(
        query.WithBasicAuth("neo4j", "password"),
        query.WithBaseURL("http://localhost:7474"),
        query.WithTimeout(30*time.Second),
    )
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }
    defer client.Close()

    ctx := context.Background()

    result, err := query.WithTransformer(
        client.Query,
        ctx,
        "MATCH (n:Person) RETURN n.name AS name LIMIT 10",
        nil,
        query.EagerResultTransformer,
    )
    if err != nil {
        log.Fatalf("Query failed: %v", err)
    }

    for _, rec := range result.Records {
        name, _ := rec.GetString("name")
        fmt.Printf("Name: %s\n", name)
    }
}

Authentication

Authentication is required and mutually exclusive — pass either WithBasicAuth or WithBearerToken, not both.

Basic authentication
client, err := query.NewClient(
    query.WithBasicAuth("neo4j", "your-password"),
)
Bearer token
client, err := query.NewClient(
    query.WithBearerToken("your-bearer-token"),
)

Configuration

Option Default Description
WithBaseURL(url) http://localhost:7474 Base URL of the Neo4j server
WithDatabase(db) Target database name
WithTimeout(d) 120s Per-request timeout
WithMaxRetry(n) 3 Maximum retry attempts on transient failure
WithLogger(l) warn to stderr Structured *slog.Logger
WithHTTPClient(c) default transport Custom *http.Client (for mTLS, proxies, etc.)
WithUserAgent(ua) query-go-sdk/<version> Value of the User-Agent header
WithMaxResponseSize(n) 10MB Maximum response body size in bytes; returns an error if exceeded
WithDefaultHeaders(map) Extra headers sent with every request; Authorization, Content-Type, Accept, and User-Agent are protected and cannot be overridden
WithAPIFlavor(f) FlavorQueryV2 Select which HTTP API endpoint to target; see API Flavors
WithAccessMode(m) AccessModeWrite Set the access mode header on every request; see Access Mode
client, err := query.NewClient(
    query.WithBasicAuth("neo4j", "password"),
    query.WithBaseURL("http://neo4j.example.com:7474"),
    query.WithDatabase("mydb"),
    query.WithTimeout(60*time.Second),
    query.WithMaxRetry(5),
    query.WithDefaultHeaders(map[string]string{
        "X-Request-Source": "my-service",
    }),
)

API Flavors

The SDK supports two Neo4j HTTP APIs, selectable via WithAPIFlavor:

Constant Endpoint Response format Default
query.FlavorQueryV2 /db/{db}/query/v2 Typed JSON ($type envelopes) yes
query.FlavorLegacyHTTP /db/{db}/tx/commit Plain JSON row format

FlavorQueryV2 is the default and requires no explicit configuration.

Targeting the legacy HTTP API

Use FlavorLegacyHTTP to send queries to the older Cypher HTTP Transaction API. This is useful for performance comparison testing or for connecting to Neo4j versions that pre-date the Query API.

client, err := query.NewClient(
    query.WithBasicAuth("neo4j", "password"),
    query.WithBaseURL("http://localhost:7474"),
    query.WithAPIFlavor(query.FlavorLegacyHTTP),
)

Everything else — transformers, records, error handling — works identically for both flavors.

Type mapping differences

The legacy API returns row values as plain JSON without typed envelopes. Scalars (string, int64, float64, bool, nil) decode identically to FlavorQueryV2. However, graph entities are returned as plain map[string]any instead of *query.Node / *query.Relationship, because the /tx/commit endpoint does not include element IDs or labels in the row format. rec.GetNode and rec.GetRelationship will return (nil, false) for those values.


Access Mode

The WithAccessMode option controls the access mode header sent with every request. This hints to the server whether the operation requires write access or can be served by a read replica.

Constant Header sent Value
AccessModeWrite (default) accessMode (Query API) / Access-Mode (Legacy HTTP API) write / WRITE
AccessModeRead accessMode (Query API) / Access-Mode (Legacy HTTP API) read / READ
// Read-only workload — may be routed to a read replica
client, err := query.NewClient(
    query.WithBasicAuth("neo4j", "password"),
    query.WithBaseURL("http://localhost:7474"),
    query.WithAccessMode(query.AccessModeRead),
)
// Write workload — default, no explicit option needed
client, err := query.NewClient(
    query.WithBasicAuth("neo4j", "password"),
    query.WithBaseURL("http://localhost:7474"),
    query.WithAccessMode(query.AccessModeWrite),
)

Context and Timeouts

Every operation accepts a context.Context as its first argument. The service applies its own timeout via context.WithTimeout on every call — whichever deadline is shorter (parent or service) wins.

// Per-call deadline overrides the service timeout when shorter
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := client.Query.Execute(ctx, "MATCH (n) RETURN count(n)", nil)
// Cancellation propagates through to in-flight HTTP requests
ctx, cancel := context.WithCancel(context.Background())
go func() { <-shutdown; cancel() }()

_, err := client.Query.Execute(ctx, "MATCH (n) RETURN n", nil)
if errors.Is(err, context.Canceled) {
    log.Println("query cancelled")
}

Executing Queries

Direct execution

client.Query.Execute returns a *query.Response containing the raw decoded result.

resp, err := client.Query.Execute(ctx, "RETURN 1 AS n", nil)
if err != nil {
    log.Fatal(err)
}

fmt.Println(resp.Fields)    // ["n"]
fmt.Println(resp.Rows)      // [[int64(1)]]
fmt.Println(resp.Bookmarks)

query.Response fields:

Field Type Description
Fields []string Ordered column names
Rows [][]any Decoded row values (see type mapping below)
Notifications []query.Notification Advisory messages from Neo4j
Bookmarks []string Transaction bookmarks
QueryPlan *query.PlanOperator Non-nil for EXPLAIN/PROFILE queries
Passing parameters
params := map[string]any{"name": "Alice", "age": 30}
resp, err := client.Query.Execute(ctx,
    "MATCH (n:Person {name: $name}) WHERE n.age >= $age RETURN n.name",
    params)

Transformers

The transformer pattern mirrors the Neo4j Go driver's neo4j.ExecuteQuery. A transformer is a function that converts *query.Response into a typed Go value:

type ResultTransformer[T any] func(*query.Response) (T, error)

Three built-in transformers cover the common cases. You can also write your own — see Custom transformers.

result, err := query.WithTransformer(svc, ctx, cypher, params, transformer)
EagerResultTransformer

Collects all rows into a *query.EagerResult. Use for typical queries where the full result set fits in memory.

result, err := query.WithTransformer(
    client.Query, ctx,
    "MATCH (n:Person) RETURN n.name AS name, n.age AS age",
    nil,
    query.EagerResultTransformer,
)
if err != nil {
    log.Fatal(err)
}

fmt.Println(result.Keys)          // ["name", "age"]
fmt.Println(len(result.Records))  // number of rows

for _, rec := range result.Records {
    name, _ := rec.GetString("name")
    age, _  := rec.GetInt64("age")
    fmt.Printf("%s is %d\n", name, age)
}

EagerResult also exposes HasWarnings() and Warnings() for advisory notifications.

Collect

Maps each row to a value using a function, returning []T.

type Person struct {
    Name string
    Age  int64
}

people, err := query.WithTransformer(
    client.Query, ctx,
    "MATCH (n:Person) RETURN n.name AS name, n.age AS age",
    nil,
    query.Collect(func(rec *query.Record) (Person, error) {
        name, _ := rec.GetString("name")
        age, _  := rec.GetInt64("age")
        return Person{Name: name, Age: age}, nil
    }),
)
Single

Expects exactly one row; returns an error if zero or more than one row is returned.

type Movie struct {
    Title    string
    Released int64
}

movie, err := query.WithTransformer(
    client.Query, ctx,
    "MATCH (m:Movie {title: $title}) RETURN m.title AS title, m.released AS released",
    map[string]any{"title": "The Matrix"},
    query.Single(func(rec *query.Record) (Movie, error) {
        title, _    := rec.GetString("title")
        released, _ := rec.GetInt64("released")
        return Movie{Title: title, Released: released}, nil
    }),
)
Custom transformers

Because *query.Response is part of the public API, you can write your own ResultTransformer for cases that EagerResultTransformer, Collect, and Single don't cover.

// A transformer that extracts a single column as a flat []string
func firstColumnStrings(resp *query.Response) ([]string, error) {
    out := make([]string, 0, len(resp.Rows))
    for _, row := range resp.Rows {
        if len(row) == 0 {
            continue
        }
        s, ok := row[0].(string)
        if !ok {
            return nil, fmt.Errorf("expected string, got %T", row[0])
        }
        out = append(out, s)
    }
    return out, nil
}

names, err := query.WithTransformer(
    client.Query, ctx,
    "MATCH (n:Person) RETURN n.name",
    nil,
    query.ResultTransformer[[]string](firstColumnStrings),
)

Custom transformers work with any named function whose signature matches func(*query.Response) (T, error).


Working with Records

*query.Record provides named field access that mirrors the Neo4j Go driver's neo4j.Record.

Typed accessors
s, ok    := rec.GetString("name")          // string
n, ok    := rec.GetInt64("count")          // int64
f, ok    := rec.GetFloat64("score")        // float64
b, ok    := rec.GetBool("active")          // bool
bs, ok   := rec.GetBytes("data")           // []byte
t, ok    := rec.GetTime("createdAt")       // time.Time
d, ok    := rec.GetDuration("lifespan")    // query.Duration
p, ok    := rec.GetPoint("location")       // query.Point
node, ok := rec.GetNode("n")               // *query.Node
rel, ok  := rec.GetRelationship("r")       // *query.Relationship
path, ok := rec.GetPath("p")               // query.Path
list, ok := rec.GetList("tags")            // []any
m, ok    := rec.GetMap("props")            // map[string]any

All accessors return (zero, false) when the field is absent or null.

Graph entity types

query.Node, query.Relationship, and query.Path are part of the public API — you can use them in your own function signatures:

func nodeLabel(n *query.Node) string {
    if len(n.Labels) > 0 {
        return n.Labels[0]
    }
    return ""
}

func relSummary(r *query.Relationship) string {
    return fmt.Sprintf("[%s]", r.Type)
}

query.Node fields: ElementID string, Labels []string, Properties map[string]any.
query.Relationship fields: ElementID, StartNodeElementID, EndNodeElementID string, Type string, Properties map[string]any.

Neo4j → Go type mapping
Neo4j type Go type
Null nil
Boolean bool
Integer int64
Float float64
String string
ByteArray []byte
List []any
Map map[string]any
Date / Time / DateTime / LocalTime / LocalDateTime time.Time
Duration query.Duration
Point (2D / 3D) query.Point
Node *query.Node
Relationship *query.Relationship
Path query.Path
Generic accessor
val, ok := rec.Get("fieldName")  // returns (any, bool)
Other helpers
rec.Keys()           // []string — ordered field names
rec.Values()         // []any   — ordered decoded values
rec.AsMap()          // map[string]any
rec.IsNull("field")  // true if field present and null
List helpers
rawList, _ := rec.GetList("tags")
tags, ok   := query.StringList(rawList)   // []string
counts, ok := query.Int64List(rawList)    // []int64
scores, ok := query.Float64List(rawList)  // []float64

Error Handling

HTTP errors — *query.Error

Returned when the Neo4j server responds with a non-2xx status code.

resp, err := client.Query.Execute(ctx, cypher, params)
if err != nil {
    var apiErr *query.Error
    if errors.As(err, &apiErr) {
        fmt.Printf("HTTP %d: %s\n", apiErr.StatusCode, apiErr.Message)

        switch {
        case apiErr.IsUnauthorized():
            log.Fatal("check credentials")
        case apiErr.IsNotFound():
            log.Fatal("endpoint not found")
        case apiErr.IsBadRequest():
            log.Fatal("bad request")
        }
    }
}

query.IsNotFound(err) is a convenience helper that unwraps the error chain.

Neo4j query errors — *query.QueryErrors

Returned when Neo4j executes the request but reports one or more Cypher errors (syntax errors, constraint violations, etc.).

resp, err := client.Query.Execute(ctx, "INVALID CYPHER", nil)
if err != nil {
    var qErr *query.QueryErrors
    if errors.As(err, &qErr) {
        for _, e := range qErr.Errors {
            fmt.Printf("[%s] %s: %s\n", e.Classification(), e.Title(), e.Message)
        }
    }
}

QueryError helper methods: Classification() (e.g. ClientError), Category() (e.g. Statement), Title() (e.g. SyntaxError).

Context errors
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

_, err := client.Query.Execute(ctx, cypher, nil)
if errors.Is(err, context.DeadlineExceeded) {
    log.Println("query timed out")
}
if errors.Is(err, context.Canceled) {
    log.Println("query cancelled")
}

Best Practices

Secure credentials
user := os.Getenv("NEO4J_USERNAME")
pass := os.Getenv("NEO4J_PASSWORD")

client, err := query.NewClient(
    query.WithBasicAuth(user, pass),
    query.WithBaseURL(os.Getenv("NEO4J_URL")),
)
Context management

Always pass a context with an appropriate deadline or cancellation. Use defer client.Close() to release idle connections when the client is no longer needed.

Error classification

Use errors.As to branch on *query.Error (HTTP-level) versus *query.QueryErrors (Cypher-level). Transient server errors (5xx) may be worth retrying; client errors (4xx) generally should not be.


CI & Releases

Three GitHub Actions workflows manage CI and the release process.

Workflows
Workflow Trigger What it does
CI Push to main, every PR Runs tests with the race detector, golangci-lint, and go build ./...
Changelog check Every PR Fails if the PR changes .go files but has no entry in .changes/unreleased/
Release Push of a vX.Y.Z tag Gates on tests, extracts the changelog section, creates a GitHub Release
Making a release

Releases follow a three-step process. changie collects the unreleased fragment files and determines the correct semver bump automatically from the change kinds (Added → minor, Fixed/Security → patch, Changed/Removed → major).

There is no manual version bump required. ClientVersion uses debug.ReadBuildInfo() at runtime to read the module version that the Go toolchain embeds when a consumer builds their application. It falls back to "development" only in local and test builds.

1. Batch and merge the changelog

changie batch   # collects .changes/unreleased/*.yaml → .changes/vX.Y.Z.md
changie merge   # folds that file into CHANGELOG.md

2. Commit and tag

git add CHANGELOG.md .changes/
git commit -m "chore: release vX.Y.Z"
git tag vX.Y.Z
git push origin main --tags

3. Workflow takes over

Pushing the tag fires the Release workflow, which:

  • Runs go test -race ./... — the release is aborted if any test fails
  • Extracts the ## vX.Y.Z section from CHANGELOG.md
  • Creates a GitHub Release with that text as the release notes

Because this is a Go module with no compiled binaries, the tag itself is what consumers reference:

go get github.com/neo4j-contrib/query-go-sdk@vX.Y.Z
Adding a changelog entry

Every PR that changes Go source files needs a changie fragment. Run:

changie new

Choose a kind and write a one-line summary, then commit the generated .yaml file alongside your code changes. The Changelog check workflow will fail the PR if this step is skipped.

To bypass the check for a PR that genuinely needs no entry (docs-only, CI-only, or test-only changes), add the no-changelog label to the PR.


License

See LICENSE file for details.

Documentation

Overview

Package query provides a Go client library for the Neo4j Query API.

Index

Constants

This section is empty.

Variables

View Source
var ClientVersion = clientVersionFallback

ClientVersion is the version of this client library, embedded in every User-Agent header.

Why debug.ReadBuildInfo()? Go consumers import this library by source (via the module proxy). There are no compiled binaries to stamp at build time. When a consumer builds their application, the Go toolchain records all module dependencies and their exact versions in the binary. debug.ReadBuildInfo() reads that information at runtime, so the User-Agent automatically reflects the version the consumer actually imported (e.g. "v1.10.0") without any source edits or workflow tricks.

In local and test builds, ReadBuildInfo returns "(devel)" or fails entirely, so we fall back to clientVersionFallback ("development") to make it obvious the binary is not a release build.

Functions

func Float64List

func Float64List(list []any) ([]float64, bool)

Float64List converts a []any list to []float64. Returns nil, false if any element is not a float64.

func Int64List

func Int64List(list []any) ([]int64, bool)

Int64List converts a []any list to []int64. Returns nil, false if any element is not an int64.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a 404 Not Found API error.

func StringList

func StringList(list []any) ([]string, bool)

StringList converts a []any list to []string. Returns nil, false if any element is not a string.

func WithTransformer

func WithTransformer[T any](svc QueryService, ctx context.Context, cypher string, params map[string]any, transformer ResultTransformer[T]) (T, error)

WithTransformer executes a Cypher statement via svc and applies transformer to the result. It is a package-level generic function rather than a method on queryService because Go interfaces cannot have generic methods.

This mirrors the pattern used by the Neo4j Go driver, where neo4j.ExecuteQuery is a package-level generic function that accepts the Driver interface:

// Driver pattern:
result, err := neo4j.ExecuteQuery(ctx, driver, cypher, params,
    neo4j.EagerResultTransformer, ...)

// This SDK — same shape:
result, err := query.WithTransformer(svc, ctx, cypher, params,
    query.EagerResultTransformer)

Because it accepts QueryService (the interface), it works against any implementation including test doubles.

Types

type APIFlavor added in v0.2.0

type APIFlavor int

APIFlavor selects which Neo4j HTTP API the client will target.

const (
	// FlavorQueryV2 targets the modern Query API v2 endpoint (/db/{db}/query/v2).
	// This is the default.
	FlavorQueryV2 APIFlavor = iota
	// FlavorLegacyHTTP targets the older Cypher HTTP Transaction API
	// endpoint (/db/{db}/tx/commit). Use this for comparison testing against
	// Neo4j versions that pre-date the Query API.
	FlavorLegacyHTTP
)

type AccessMode added in v0.3.0

type AccessMode int

AccessMode controls the accessMode header sent with every API request.

const (
	// AccessModeWrite sets the accessMode header to "write". This is the default.
	AccessModeWrite AccessMode = iota
	// AccessModeRead sets the accessMode header to "read".
	AccessModeRead
)

type Duration

type Duration = decode.Duration

Duration represents a Neo4j DURATION value. It cannot be represented as time.Duration because months and days are calendar-relative.

type EagerResult

type EagerResult struct {
	Keys          []string
	Records       []*Record
	Notifications []decode.Notification
	QueryPlan     *decode.PlanOperator
	Bookmarks     []string
}

EagerResult holds the fully materialised result of a query. Mirrors neo4j.EagerResult in the Go driver.

func (*EagerResult) HasWarnings

func (e *EagerResult) HasWarnings() bool

HasWarnings returns true if any WARNING severity notifications are present.

func (*EagerResult) String

func (e *EagerResult) String() string

String returns a human-readable summary of the result, useful for debugging.

func (*EagerResult) Warnings

func (e *EagerResult) Warnings() []decode.Notification

Warnings returns only the notifications with severity "WARNING".

type Error

type Error = api.Error

Error represents an HTTP error response from the Neo4j Query API.

type ErrorDetail

type ErrorDetail = api.ErrorDetail

ErrorDetail represents individual error details within an Error.

type Node

type Node = decode.Node

Node represents a Neo4j NODE value returned by a Cypher query.

type Notification

type Notification = decode.Notification

Notification is an advisory message from Neo4j about the executed statement. Notifications are not errors — the query succeeded and rows are returned alongside them.

type Option

type Option func(*options) error

Option is a functional option for configuring the AuraAPIClient.

func WithAPIFlavor added in v0.2.0

func WithAPIFlavor(flavor APIFlavor) Option

WithAPIFlavor selects which Neo4j HTTP API endpoint the client targets. Use FlavorLegacyHTTP to target the older Cypher HTTP Transaction API (/db/{db}/tx/commit) for comparison testing. Defaults to FlavorQueryV2.

func WithAccessMode added in v0.3.0

func WithAccessMode(mode AccessMode) Option

WithAccessMode sets the accessMode header sent with every API request. Use AccessModeRead for read-only workloads to allow the server to route the request to a read replica. Defaults to AccessModeWrite.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL overrides the default base URL of the Neo4j server. The SDK does not enforce HTTPS — it is the caller's responsibility to use an appropriate scheme for their deployment. Use HTTPS for any server that is not on a trusted loopback interface.

func WithBasicAuth

func WithBasicAuth(username, password string) Option

func WithBearerToken

func WithBearerToken(token string) Option

func WithDatabase

func WithDatabase(database string) Option

WithDatabase

func WithDefaultHeaders

func WithDefaultHeaders(headers map[string]string) Option

WithDefaultHeaders adds the given headers to every API request. It is a no-op when headers is nil or empty. Keys matching Authorization, Content-Type, Accept, or User-Agent (case-insensitive) are rejected with an error to prevent callers from inadvertently overriding security-sensitive or protocol-critical headers.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom *http.Client to use for all API requests. This lets callers inject a custom transport (e.g. for mTLS, proxies, or testing). Returns an error if client is nil.

Note: when a custom client is supplied, WithTimeout has no effect. The caller is responsible for setting a Timeout on the provided *http.Client directly.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom slog.Logger. Defaults to warn-level logging to stderr.

func WithMaxResponseSize

func WithMaxResponseSize(maxResponse int) Option

WithMaxResponseSize sets the maximum size for response. Default is 10mb

func WithMaxRetry

func WithMaxRetry(maxRetry int) Option

WithMaxRetry sets the maximum number of retries for failed requests. Defaults to 3.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets a custom API timeout. Defaults to 120 seconds.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent overrides the default User-Agent header sent with every request. Returns an error if ua is empty.

type Path

type Path = decode.Path

Path represents a Neo4j PATH value — an alternating sequence of nodes and relationships.

type PlanOperator

type PlanOperator = decode.PlanOperator

PlanOperator is one node in an EXPLAIN or PROFILE query plan tree.

type Point

type Point = decode.Point

Point represents a Neo4j POINT value with an SRID identifying the coordinate reference system. Common SRIDs: 4326 (WGS-84 2D), 4979 (WGS-84 3D), 7203 (Cartesian 2D), 9157 (Cartesian 3D).

type QueryAPIClient

type QueryAPIClient struct {

	// Grouped services — using interface types for testability.
	Query QueryService
	// contains filtered or unexported fields
}

QueryAPIClient is the main client for the Neo4j Query API.

func NewClient

func NewClient(opts ...Option) (*QueryAPIClient, error)

NewClient creates a new Query API client with functional options.

func (*QueryAPIClient) CheckVersion

func (c *QueryAPIClient) CheckVersion(ctx context.Context) error

CheckVersion calls the Neo4j discovery endpoint and returns a *VersionError if the server version is older than 2026.04.0. Call this after NewClient when you want to validate the connected server before executing queries.

client, err := query.NewClient(query.WithBasicAuth("neo4j", "password"))
if err != nil {
    log.Fatal(err)
}
defer client.Close()

if err := client.CheckVersion(ctx); err != nil {
    var verErr *query.VersionError
    if errors.As(err, &verErr) {
        log.Fatalf("server too old: got %s, need %s", verErr.Got, verErr.Required)
    }
    log.Fatal(err)
}

func (*QueryAPIClient) Close

func (c *QueryAPIClient) Close()

Close drains idle HTTP connections held by the underlying transport. It should be called via defer when the client is no longer needed to avoid leaking file descriptors.

client, err := query.NewClient(query.WithBasicAuth("neo4j", "password"))
if err != nil {
    log.Fatal(err)
}
defer client.Close()

type QueryError

type QueryError = decode.QueryError

QueryError is a single Neo4j error within a QueryErrors batch. Use Classification(), Category(), and Title() to branch on the error code.

type QueryErrors

type QueryErrors = decode.QueryErrors

QueryErrors is returned when Neo4j responds with one or more query errors. Inspect with errors.As:

var qErr *query.QueryErrors
if errors.As(err, &qErr) {
    for _, e := range qErr.Errors {
        log.Printf("[%s] %s", e.Title(), e.Message)
    }
}

type QueryService

type QueryService interface {
	// Execute runs a Cypher statement and returns the raw decoded response.
	// Use WithTransformer to map the result to a typed value.
	Execute(ctx context.Context, qry string, qryParams map[string]any) (*Response, error)
}

QueryService defines operations for using the Query API

type Record

type Record struct {
	// contains filtered or unexported fields
}

Record represents a single row in a query result with named field access. It mirrors neo4j.Record in the Go driver for a consistent developer experience.

func (*Record) AsMap

func (r *Record) AsMap() map[string]any

AsMap returns all fields as a map. Mirrors neo4j.Record.AsMap().

func (*Record) Get

func (r *Record) Get(field string) (any, bool)

Get returns the value for the named field and whether the field exists. A field that is present but Null returns (nil, true). The returned value is one of the types documented on decode.DecodeValue.

func (*Record) GetBool

func (r *Record) GetBool(field string) (bool, bool)

func (*Record) GetBytes

func (r *Record) GetBytes(field string) ([]byte, bool)

func (*Record) GetDuration

func (r *Record) GetDuration(field string) (decode.Duration, bool)

func (*Record) GetFloat64

func (r *Record) GetFloat64(field string) (float64, bool)

func (*Record) GetInt64

func (r *Record) GetInt64(field string) (int64, bool)

func (*Record) GetList

func (r *Record) GetList(field string) ([]any, bool)

func (*Record) GetMap

func (r *Record) GetMap(field string) (map[string]any, bool)

func (*Record) GetNode

func (r *Record) GetNode(field string) (*decode.Node, bool)

func (*Record) GetPath

func (r *Record) GetPath(field string) (decode.Path, bool)

func (*Record) GetPoint

func (r *Record) GetPoint(field string) (decode.Point, bool)

func (*Record) GetRelationship

func (r *Record) GetRelationship(field string) (*decode.Relationship, bool)

func (*Record) GetString

func (r *Record) GetString(field string) (string, bool)

func (*Record) GetTime

func (r *Record) GetTime(field string) (time.Time, bool)

func (*Record) IsNull

func (r *Record) IsNull(field string) bool

IsNull reports whether the named field exists and has a Null value. Use this to distinguish "field is null" from "field not in result".

func (*Record) Keys

func (r *Record) Keys() []string

Keys returns the ordered list of field names.

func (*Record) String

func (r *Record) String() string

String returns a human-readable representation, useful for debugging.

func (*Record) Values

func (r *Record) Values() []any

Values returns the ordered list of decoded values.

type Relationship

type Relationship = decode.Relationship

Relationship represents a Neo4j RELATIONSHIP value.

type Response

type Response = decode.Response

Response is the decoded result of a Cypher query execution. Fields holds the ordered column names; Rows holds one []any per row where each element is a fully decoded Go value (see DecodeValue for the type mapping).

type ResultTransformer

type ResultTransformer[T any] func(*decode.Response) (T, error)

ResultTransformer converts a raw *decode.Response into a typed result T. Mirrors the transformer pattern in the Neo4j Go driver:

neo4j.ExecuteQuery(ctx, driver, cypher, params, neo4j.EagerResultTransformer, ...)

Usage with this SDK:

result, err := query.WithTransformer(svc, ctx, cypher, params, query.EagerResultTransformer)
var EagerResultTransformer ResultTransformer[*EagerResult] = func(resp *decode.Response) (*EagerResult, error) {
	records := make([]*Record, len(resp.Rows))
	for i, row := range resp.Rows {
		records[i] = newRecord(resp.Fields, row)
	}
	return &EagerResult{
		Keys:          resp.Fields,
		Records:       records,
		Notifications: resp.Notifications,
		QueryPlan:     resp.QueryPlan,
		Bookmarks:     resp.Bookmarks,
	}, nil
}

EagerResultTransformer collects all rows into an EagerResult. Use for typical queries where the full result set fits comfortably in memory.

result, err := query.WithTransformer(svc, ctx, cypher, params, query.EagerResultTransformer)
for _, rec := range result.Records {
    name, _ := rec.GetString("p.name")
}

func Collect

func Collect[T any](fn func(*Record) (T, error)) ResultTransformer[[]T]

Collect returns a ResultTransformer that maps each Record to a value of type T using fn, collecting the results into []T.

type Actor struct {
    Name  string
    Roles []string
}

actors, err := query.WithTransformer(svc, ctx, cypher, params,
    query.Collect(func(rec *query.Record) (Actor, error) {
        name, _  := rec.GetString("p.name")
        raw, _   := rec.GetList("a.roles")
        roles, _ := query.StringList(raw)
        return Actor{Name: name, Roles: roles}, nil
    }),
)

func Single

func Single[T any](fn func(*Record) (T, error)) ResultTransformer[T]

Single returns a ResultTransformer that expects exactly one row and maps it to T using fn. Returns an error if the result has zero or more than one row.

movie, err := query.WithTransformer(svc, ctx, cypher, params,
    query.Single(func(rec *query.Record) (Movie, error) {
        title, _    := rec.GetString("m.title")
        released, _ := rec.GetInt64("m.released")
        return Movie{Title: title, Released: int(released)}, nil
    }),
)

type VersionError

type VersionError struct {
	// Required is the minimum acceptable CalVer version (e.g. "2026.04.0").
	Required string
	// Got is the version reported by the connected server.
	Got string
}

VersionError is returned by CheckVersion when the connected Neo4j server is older than the minimum supported version.

func (*VersionError) Error

func (e *VersionError) Error() string

Directories

Path Synopsis
examples
basicQuery command
Package main demonstrates using the Neo4j Query API Go SDK.
Package main demonstrates using the Neo4j Query API Go SDK.
internal
api
Package api implements the authenticated HTTP request layer for the Neo4j Query API.
Package api implements the authenticated HTTP request layer for the Neo4j Query API.
decode
Package decode handles decoding of Neo4j Query API typed JSON responses.
Package decode handles decoding of Neo4j Query API typed JSON responses.
httpclient
Package httpclient provides a low-level HTTP client with configurable retry behaviour.
Package httpclient provides a low-level HTTP client with configurable retry behaviour.
testutil
Package testutil provides mock implementations for use in tests across the query-go-sdk module.
Package testutil provides mock implementations for use in tests across the query-go-sdk module.
utils
Package utils provides shared internal helpers for the query-go-sdk module.
Package utils provides shared internal helpers for the query-go-sdk module.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL