surrealdb

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2025 License: Apache-2.0 Imports: 12 Imported by: 51

README


 

The official SurrealDB SDK for Go


     

     

How to install

go get github.com/surrealdb/surrealdb.go

"Getting started" example

To connect to SurrealDB and perform data operations, please refer to example.

Using the SDK

To use the SDK, please refer to the documentation.

Contributing

You can run the Makefile commands to run and build the project:

make lint test

You also need to be running SurrealDB alongside the tests. We recommend using the nightly build, as development may rely on the latest functionality.

Documentation

Overview

The surrealdb package implements [SurrealDB RPC Protocol] in the Go way.

Connection Engines

There are 2 different connection engines, WebSocket and HTTP, you can use to connect to SurrealDB backend.

Provide a proper SurrealDB endpoint URL to FromEndpointURLString so that it chooses the right backend for you.

Data Models

The surrealdb package facilitates communication between client and the backend service using the Concise Binary Object Representation (CBOR) format.

For more information on CBOR and how it relates to SurrealDB's data models, please refer to the github.com/surrealdb/surrealdb.go/pkg/models package.

Use Query for most use cases

For most use cases, you can use the Query function to execute SurrealQL statements.

Query is recommended for both simple and complex queries, transactions, and when you need full control over your database operations.

To ease writing queries for Query with more type-safety, you can use the github.com/surrealdb/surrealdb.go/contrib/surrealql package.

Use Send for low-level control

Send is used internally by all data manipulation methods.

Use it directly when you want to create requests yourself.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Create added in v0.3.0

func Create[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat, data any) (*TResult, error)
Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "example_create", "persons")

	type Person struct {
		Name string `json:"name"`
		// Note that you must use CustomDateTime instead of time.Time.
		CreatedAt models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	// Unlike Insert which returns a pointer to the array of inserted records,
	// Create returns a pointer to the record itself.
	var inserted *Person
	inserted, err = surrealdb.Create[Person](
		context.Background(),
		db,
		"persons",
		map[string]any{
			"name":       "First",
			"created_at": createdAt,
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Create result: %v\n", *inserted)

	// You can throw away the result if you don't need it,
	// by specifying an empty struct as the type parameter.
	_, err = surrealdb.Create[struct{}](
		context.Background(),
		db,
		"persons",
		map[string]any{
			"name":       "Second",
			"created_at": createdAt,
		},
	)
	if err != nil {
		panic(err)
	}

	// You can also create a record by passing a struct directly.
	_, err = surrealdb.Create[struct{}](
		context.Background(),
		db,
		"persons",
		Person{
			Name: "Third",
			CreatedAt: models.CustomDateTime{
				Time: createdAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}

	// You can also receive the result as a map[string]any.
	// It should be handy when you don't want to define a struct type,
	// in other words, when the schema is not known upfront.
	var fourthAsMap *map[string]any
	fourthAsMap, err = surrealdb.Create[map[string]any](
		context.Background(),
		db,
		"persons",
		map[string]any{
			"name": "Fourth",
			"created_at": models.CustomDateTime{
				Time: createdAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}
	if _, ok := (*fourthAsMap)["id"].(models.RecordID); ok {
		delete((*fourthAsMap), "id")
	}
	fmt.Printf("Create result: %v\n", *fourthAsMap)

	selected, err := surrealdb.Select[[]Person](
		context.Background(),
		db,
		"persons",
	)
	if err != nil {
		panic(err)
	}
	for _, person := range *selected {
		fmt.Printf("Selected person: %v\n", person)
	}

}
Output:

Create result: {First {2023-10-01 12:00:00 +0000 UTC} <nil>}
Create result: map[created_at:{2023-10-01 12:00:00 +0000 UTC} name:Fourth]
Selected person: {First {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Second {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Third {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Fourth {2023-10-01 12:00:00 +0000 UTC} <nil>}
Example (Server_unmarshal_error)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person")

	type Person struct {
		ID   models.RecordID `json:"id,omitempty"`
		Name string          `json:"name"`
	}

	_, err := surrealdb.Create[Person](
		context.Background(),
		db,
		"persons",
		Person{
			Name: "Test",
		},
	)
	if err != nil {
		fmt.Printf("Expected error: %v\n", err)
	}

}
Output:

Expected error: cannot marshal RecordID with empty table or ID: want <table>:<identifier> but got :<nil>

func Delete added in v0.3.0

func Delete[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat) (*TResult, error)

func Insert added in v0.3.0

func Insert[TResult any](ctx context.Context, db *DB, what models.Table, data any) (*[]TResult, error)

Insert creates records with either specified IDs or generated IDs.

Insert cannot create a relationship. If you want to create a relationship, use InsertRelation if you need to specify the ID of the relationship, or use Relate if you want to create a relationship with a generated ID.

Example (Bulk_insert_record)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person")

	type Person struct {
		ID models.RecordID `json:"id"`
	}

	persons := []Person{
		{ID: models.NewRecordID("person", "a")},
		{ID: models.NewRecordID("person", "b")},
		{ID: models.NewRecordID("person", "c")},
	}

	var inserted *[]Person
	inserted, err := surrealdb.Insert[Person](
		context.Background(),
		db,
		"person",
		persons,
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Inserted: %+s\n", *inserted)

	selected, err := surrealdb.Select[[]Person](
		context.Background(),
		db,
		"person",
	)
	if err != nil {
		panic(err)
	}
	for _, person := range *selected {
		fmt.Printf("Selected person: %+s\n", person)
	}

}
Output:

Inserted: [{{person a}} {{person b}} {{person c}}]
Selected person: {{person a}}
Selected person: {{person b}}
Selected person: {{person c}}
Example (Bulk_insert_relation_workaround_for_rpcv1)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person", "follow")

	type Person struct {
		ID models.RecordID `json:"id"`
	}

	type Follow struct {
		ID  models.RecordID `json:"id"`
		In  models.RecordID `json:"in"`
		Out models.RecordID `json:"out"`
	}

	persons := []Person{
		{ID: models.NewRecordID("person", "a")},
		{ID: models.NewRecordID("person", "b")},
		{ID: models.NewRecordID("person", "c")},
	}

	follows := []Follow{
		{ID: models.NewRecordID("follow", "person:a:person:b"), In: persons[0].ID, Out: persons[1].ID},
		{ID: models.NewRecordID("follow", "person:b:person:c"), In: persons[1].ID, Out: persons[2].ID},
		{ID: models.NewRecordID("follow", "person:c:person:a"), In: persons[2].ID, Out: persons[0].ID},
	}

	var err error

	var insertedPersons *[]Person
	insertedPersons, err = surrealdb.Insert[Person](
		context.Background(),
		db,
		"person",
		persons,
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Inserted: %+s\n", *insertedPersons)

	var selectedPersons *[]Person
	selectedPersons, err = surrealdb.Select[[]Person](
		context.Background(),
		db,
		"person",
	)
	if err != nil {
		panic(err)
	}
	for _, person := range *selectedPersons {
		fmt.Printf("Selected person: %+s\n", person)
	}

	/// Once the RPC v2 becomes mature, we could update this SDK to speak
	/// the RPC v2 protocol and use the `relation` parameter to insert
	/// the follows as relations.
	///
	/// But as of now, it will fail like SurrealDB responding with:
	///
	///   There was a problem with the database: The database encountered unreachable logic: /surrealdb/crates/core/src/expr/statements/insert.rs:123: Unknown data clause type in INSERT statement: ContentExpression(Array(Array([Object(Object({"id": Thing(Thing { tb: "follow", id: String("person:a:person:b") }), "in": Thing(Thing { tb: "person", id: String("a") }), "out": Thing(Thing { tb: "person", id: String("b") })})), Object(Object({"id": Thing(Thing { tb: "follow", id: String("person:b:person:c") }), "in": Thing(Thing { tb: "person", id: String("b") }), "out": Thing(Thing { tb: "person", id: String("c") })})), Object(Object({"id": Thing(Thing { tb: "follow", id: String("person:c:person:a") }), "in": Thing(Thing { tb: "person", id: String("c") }), "out": Thing(Thing { tb: "person", id: String("a") })}))])))
	///
	// var insertedFollows *[]Follow
	// insertedFollows, err = surrealdb.Insert[Follow](
	// 	db,
	// 	"follow",
	// 	follows,
	// 	map[string]any{
	// 		// The optional `relation` parameter is a boolean indicating whether the inserted records are relations.
	// 		// See https://surrealdb.com/docs/surrealdb/integration/rpc#parameters-7
	// 		"relation": true,
	// 	},
	// )
	// if err != nil {
	// 	panic(err)
	// }
	// fmt.Printf("Inserted: %+s\n", *insertedFollows)

	/// You can also use `InsertRelation`.
	/// But refer to ExampleInsertRelation for that.
	// for _, follow := range follows {
	// 	err = surrealdb.InsertRelation(
	// 		db,
	// 		&surrealdb.Relationship{
	// 			Relation: "follow",
	// 			ID:       &follow.ID,
	// 			In:       follow.In,
	// 			Out:      follow.Out,
	// 		},
	// 	)
	// 	if err != nil {
	// 		panic(err)
	// 	}
	// }

	// Here, we focus on what you could do the equivalent of
	// batch insert relation in RPC v2, using the RPC v1 query RPC.
	_, err = surrealdb.Query[any](
		context.Background(),
		db,
		"INSERT RELATION INTO follow $content",
		map[string]any{
			"content": follows,
		},
	)
	if err != nil {
		panic(err)
	}

	var selectedFollows *[]Follow
	selectedFollows, err = surrealdb.Select[[]Follow](
		context.Background(),
		db,
		"follow",
	)
	if err != nil {
		panic(err)
	}
	for _, follow := range *selectedFollows {
		fmt.Printf("Selected follow: %+s\n", follow)
	}

	type PersonWithFollows struct {
		ID     models.RecordID   `json:"id"`
		Follow []models.RecordID `json:"follows"`
	}

	var followedByA *[]surrealdb.QueryResult[[]PersonWithFollows]
	followedByA, err = surrealdb.Query[[]PersonWithFollows](
		context.Background(),
		db,
		"SELECT id, <->follow<->person AS follows FROM person ORDER BY id",
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, person := range (*followedByA)[0].Result {
		fmt.Printf("PersonWithFollows: %+s\n", person)
	}

}
Output:

Inserted: [{{person a}} {{person b}} {{person c}}]
Selected person: {{person a}}
Selected person: {{person b}}
Selected person: {{person c}}
Selected follow: {{follow person:a:person:b} {person a} {person b}}
Selected follow: {{follow person:b:person:c} {person b} {person c}}
Selected follow: {{follow person:c:person:a} {person c} {person a}}
PersonWithFollows: {{person a} [{person c} {person a} {person a} {person b}]}
PersonWithFollows: {{person b} [{person a} {person b} {person b} {person c}]}
PersonWithFollows: {{person c} [{person b} {person c} {person c} {person a}]}
Example (Table)
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	type Person struct {
		Name string `json:"name"`
		// Note that you must use CustomDateTime instead of time.Time.
		CreatedAt models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	// Unlike Create which returns a pointer to the record itself,
	// Insert returns a pointer to the array of inserted records.
	var inserted *[]Person
	inserted, err = surrealdb.Insert[Person](
		context.Background(),
		db,
		"persons",
		map[string]any{
			"name":       "First",
			"created_at": createdAt,
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Insert result: %v\n", *inserted)

	_, err = surrealdb.Insert[struct{}](
		context.Background(),
		db,
		"persons",
		map[string]any{
			"name":       "Second",
			"created_at": createdAt,
		},
	)
	if err != nil {
		panic(err)
	}

	_, err = surrealdb.Insert[struct{}](
		context.Background(),
		db,
		"persons",
		Person{
			Name: "Third",
			CreatedAt: models.CustomDateTime{
				Time: createdAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}

	fourthAsMap, err := surrealdb.Insert[map[string]any](
		context.Background(),
		db,
		"persons",
		Person{
			Name: "Fourth",
			CreatedAt: models.CustomDateTime{
				Time: createdAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}
	if _, ok := (*fourthAsMap)[0]["id"].(models.RecordID); ok {
		delete((*fourthAsMap)[0], "id")
	}
	fmt.Printf("Insert result: %v\n", *fourthAsMap)

	selected, err := surrealdb.Select[[]Person](
		context.Background(),
		db,
		"persons",
	)
	if err != nil {
		panic(err)
	}
	for _, person := range *selected {
		fmt.Printf("Selected person: %v\n", person)
	}

}
Output:

Insert result: [{First {2023-10-01 12:00:00 +0000 UTC} <nil>}]
Insert result: [map[created_at:{2023-10-01 12:00:00 +0000 UTC} name:Fourth]]
Selected person: {First {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Second {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Third {2023-10-01 12:00:00 +0000 UTC} <nil>}
Selected person: {Fourth {2023-10-01 12:00:00 +0000 UTC} <nil>}

func InsertRelation added in v0.3.0

func InsertRelation[TResult any](ctx context.Context, db *DB, relationship *Relationship) (*TResult, error)

InsertRelation inserts a relation between two records in the database.

It creates a relationship from relationship.In to relationship.Out.

The resulting relationship will have an autogenerated ID in case the Relationship.ID is nil, or the ID specified in the Relationship.ID field.

In case you only care about the returned relationship's ID, use `connection.ResponseID[models.RecordID]` for the TResult type parameter.

Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person", "follow")

	type Person struct {
		ID models.RecordID `json:"id,omitempty"`
	}

	type Follow struct {
		In    *models.RecordID      `json:"in,omitempty"`
		Out   *models.RecordID      `json:"out,omitempty"`
		Since models.CustomDateTime `json:"since"`
	}

	first, err := surrealdb.Create[Person](
		context.Background(),
		db,
		"person",
		map[string]any{
			"id": models.NewRecordID("person", "first"),
		})
	if err != nil {
		panic(err)
	}

	second, err := surrealdb.Create[Person](
		context.Background(),
		db,
		"person",
		map[string]any{
			"id": models.NewRecordID("person", "second"),
		})
	if err != nil {
		panic(err)
	}

	since, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	persons, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		"SELECT * FROM person ORDER BY id.id",
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, person := range (*persons)[0].Result {
		fmt.Printf("Person: %+v\n", person)
	}

	res, relateErr := surrealdb.InsertRelation[[]connection.ResponseID[models.RecordID]](
		context.Background(),
		db,
		&surrealdb.Relationship{
			ID:       &models.RecordID{Table: "follow", ID: "first_second"},
			In:       first.ID,
			Out:      second.ID,
			Relation: "follow",
			Data: map[string]any{
				"since": models.CustomDateTime{
					Time: since,
				},
			},
		},
	)
	if relateErr != nil {
		panic(relateErr)
	}
	if res == nil {
		panic("relation response is nil")
	}
	if (*res)[0].ID.ID != "first_second" {
		panic("relation ID must be set to 'first_second'")
	}

	//nolint:lll
	/// Here's an alternative way to insert a relation using a query.
	//
	// if res, err := surrealdb.Query[any](
	// 	db,
	// 	"INSERT RELATION INTO follow $content",
	// 	map[string]any{
	// 		"content": map[string]any{
	// 			"id":    "first_second",
	// 			"in":    first.ID,
	// 			"out":   second.ID,
	// 			"since": models.CustomDateTime{Time: since},
	// 		},
	// 	},
	// ); err != nil {
	// 	panic(err)
	// } else {
	// 	fmt.Printf("Relation: %+v\n", (*res)[0].Result)
	// }
	// The output will be:
	// Relation: [map[id:{Table:follow ID:first_second} in:{Table:person ID:first} out:{Table:person ID:second} since:{Time:2023-10-01 12:00:00 +0000 UTC}]]

	type PersonWithFollows struct {
		Person
		Follows []models.RecordID `json:"follows,omitempty"`
	}
	selected, err := surrealdb.Query[[]PersonWithFollows](
		context.Background(),
		db,
		"SELECT id, name, ->follow->person AS follows FROM $id",
		map[string]any{
			"id": first.ID,
		},
	)
	if err != nil {
		panic(err)
	}

	for _, person := range (*selected)[0].Result {
		fmt.Printf("PersonWithFollows: %+v\n", person)
	}

	// Note we can select the relationships themselves because
	// RELATE creates a record in the relation table.
	follows, err := surrealdb.Query[[]Follow](
		context.Background(),
		db,
		"SELECT * from follow",
		nil,
	)
	if err != nil {
		panic(err)
	}

	for _, follow := range (*follows)[0].Result {
		fmt.Printf("Follow: %+v\n", follow)
	}

}
Output:

Person: {ID:{Table:person ID:first}}
Person: {ID:{Table:person ID:second}}
PersonWithFollows: {Person:{ID:{Table:person ID:first}} Follows:[{Table:person ID:second}]}
Follow: {In:person:first Out:person:second Since:{Time:2023-10-01 12:00:00 +0000 UTC}}

func Kill added in v0.3.0

func Kill(ctx context.Context, db *DB, id string) error

func Live added in v0.3.0

func Live(ctx context.Context, db *DB, table models.Table, diff bool) (*models.UUID, error)
Example

ExampleLive demonstrates using the Live RPC method to receive notifications. Live queries without diff return the full record as map[string]any in notification.Result. The notification channel is automatically closed when Kill is called.

package main

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

// formatRecordResult formats a record result (map[string]any) for testing.
// This is used for regular live query results (without diff) and DELETE operations.
// It handles the id field specially, formatting RecordID as table:⟨UUID⟩.
func formatRecordResult(record map[string]any) string {
	keys := make([]string, 0, len(record))
	for k := range record {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		val := record[k]
		if k == "id" {

			recordID := val.(models.RecordID)
			parts = append(parts, fmt.Sprintf("id=%s:⟨UUID⟩", recordID.Table))
		} else {
			parts = append(parts, fmt.Sprintf("%s=%v", k, val))
		}
	}
	return "{" + strings.Join(parts, " ") + "}"
}

func main() {
	config := testenv.MustNewConfig("surrealdbexamples", "livequery_rpc", "users")
	config.Endpoint = testenv.GetSurrealDBWSURL()

	db := config.MustNew()

	type User struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Username string           `json:"username"`
		Email    string           `json:"email"`
	}

	ctx := context.Background()

	live, err := surrealdb.Live(ctx, db, "users", false)
	if err != nil {
		panic(fmt.Sprintf("Failed to start live query: %v", err))
	}

	fmt.Println("Started live query")

	notifications, err := db.LiveNotifications(live.String())
	if err != nil {
		panic(fmt.Sprintf("Failed to get live notifications channel: %v", err))
	}

	received := make(chan struct{})
	done := make(chan bool)
	go func() {
		for notification := range notifications {
			// Live queries without diff return the record as map[string]any
			record, ok := notification.Result.(map[string]any)
			if !ok {
				panic(fmt.Sprintf("Expected map[string]any, got %T", notification.Result))
			}

			fmt.Printf("Received notification - Action: %s, Result: %s\n", notification.Action, formatRecordResult(record))

			switch notification.Action {
			case connection.CreateAction:
				fmt.Println("New user created")
			case connection.UpdateAction:
				fmt.Println("User updated")
			case connection.DeleteAction:
				fmt.Println("User deleted")
				close(received)
			}
		}
		// Channel was closed
		fmt.Println("Notification channel closed")
		done <- true
	}()

	createdUser, err := surrealdb.Create[User](ctx, db, "users", map[string]any{
		"username": "alice",
		"email":    "alice@example.com",
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create user: %v", err))
	}

	_, err = surrealdb.Update[User](ctx, db, *createdUser.ID, map[string]any{
		"email": "alice.updated@example.com",
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to update user: %v", err))
	}

	_, err = surrealdb.Delete[User](ctx, db, *createdUser.ID)
	if err != nil {
		panic(fmt.Sprintf("Failed to delete user: %v", err))
	}

	// Wait for all expected notifications to be received
	select {
	case <-received:
		// All notifications received
	case <-time.After(2 * time.Second):
		panic("Timeout waiting for all notifications")
	}

	fmt.Println("Live query being terminated")

	err = surrealdb.Kill(ctx, db, live.String())
	if err != nil {
		panic(fmt.Sprintf("Failed to kill live query: %v", err))
	}

	select {
	case <-done:
		fmt.Println("Goroutine exited after channel closed")
	case <-time.After(2 * time.Second):
		panic("Timeout: notification channel was not closed after Kill")
	}

}
Output:

Started live query
Received notification - Action: CREATE, Result: {email=alice@example.com id=users:⟨UUID⟩ username=alice}
New user created
Received notification - Action: UPDATE, Result: {email=alice.updated@example.com id=users:⟨UUID⟩}
User updated
Received notification - Action: DELETE, Result: {email=alice.updated@example.com id=users:⟨UUID⟩}
User deleted
Live query being terminated
Notification channel closed
Goroutine exited after channel closed
Example (WithDiff)

ExampleLive_withDiff demonstrates using live queries with diff enabled. With diff=true, CREATE and UPDATE return diff operations as []any, while DELETE still returns the deleted record as map[string]any. The notification channel is automatically closed when Kill is called.

package main

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

// formatRecordResult formats a record result (map[string]any) for testing.
// This is used for regular live query results (without diff) and DELETE operations.
// It handles the id field specially, formatting RecordID as table:⟨UUID⟩.
func formatRecordResult(record map[string]any) string {
	keys := make([]string, 0, len(record))
	for k := range record {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		val := record[k]
		if k == "id" {

			recordID := val.(models.RecordID)
			parts = append(parts, fmt.Sprintf("id=%s:⟨UUID⟩", recordID.Table))
		} else {
			parts = append(parts, fmt.Sprintf("%s=%v", k, val))
		}
	}
	return "{" + strings.Join(parts, " ") + "}"
}

// formatDiffResult formats a diff result ([]any) for testing.
// Each item in the array is a diff operation (map[string]any).
func formatDiffResult(diffs []any) string {
	var items []string
	for _, item := range diffs {
		diffOp, ok := item.(map[string]any)
		if !ok {
			panic(fmt.Sprintf("Expected diff operation to be map[string]any, got %T", item))
		}
		items = append(items, formatDiffOperation(diffOp))
	}
	return "[" + strings.Join(items, " ") + "]"
}

// formatPatchDataMap formats a map representation of PatchData.
// This is the data contained in the "value" field of a diff operation.
func formatPatchDataMap(data map[string]any) string {
	keys := make([]string, 0, len(data))
	for k := range data {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		val := data[k]
		if k == "id" {

			recordID := val.(models.RecordID)
			parts = append(parts, fmt.Sprintf("id=%s:⟨UUID⟩", recordID.Table))
		} else {
			parts = append(parts, fmt.Sprintf("%s=%v", k, val))
		}
	}
	return "{" + strings.Join(parts, " ") + "}"
}

// formatDiffOperation formats a single diff operation.
// A diff operation contains fields like "op", "path", and optionally "value".
func formatDiffOperation(op map[string]any) string {
	keys := make([]string, 0, len(op))
	for k := range op {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		val := op[k]
		if k == "value" {

			if patchData, ok := val.(map[string]any); ok {
				parts = append(parts, fmt.Sprintf("value=%s", formatPatchDataMap(patchData)))
			} else {

				parts = append(parts, fmt.Sprintf("value=%v", val))
			}
		} else {
			parts = append(parts, fmt.Sprintf("%s=%v", k, val))
		}
	}
	return "{" + strings.Join(parts, " ") + "}"
}

func main() {
	config := testenv.MustNewConfig("surrealdbexamples", "livequery_diff", "inventory")
	config.Endpoint = testenv.GetSurrealDBWSURL()

	db := config.MustNew()

	type Item struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Name     string           `json:"name"`
		Quantity int              `json:"quantity"`
	}

	ctx := context.Background()

	live, err := surrealdb.Live(ctx, db, "inventory", true)
	if err != nil {
		panic(fmt.Sprintf("Failed to start live query with diff: %v", err))
	}

	fmt.Println("Started live query with diff enabled")

	notifications, err := db.LiveNotifications(live.String())
	if err != nil {
		panic(fmt.Sprintf("Failed to get live notifications channel: %v", err))
	}

	received := make(chan struct{})
	done := make(chan bool)
	go func() {
		for notification := range notifications {
			var resultStr string

			// With diff=true:
			// - CREATE and UPDATE return diff operations as []any
			// - DELETE returns the full deleted record as map[string]any (same as without diff)
			if notification.Action == connection.DeleteAction {
				// DELETE always returns a regular record, even with diff=true
				record, ok := notification.Result.(map[string]any)
				if !ok {
					panic(fmt.Sprintf("Expected map[string]any for DELETE result, got %T", notification.Result))
				}
				resultStr = formatRecordResult(record)
				close(received)
			} else {
				// CREATE and UPDATE return an array of diff operations
				diffs, ok := notification.Result.([]any)
				if !ok {
					panic(fmt.Sprintf("Expected []any for diff result, got %T", notification.Result))
				}
				resultStr = formatDiffResult(diffs)
			}

			fmt.Printf("Action: %s, Result: %s\n", notification.Action, resultStr)
		}
		// Channel was closed
		fmt.Println("Notification channel closed")
		done <- true
	}()

	item, err := surrealdb.Create[Item](ctx, db, "inventory", map[string]any{
		"name":     "Screwdriver",
		"quantity": 50,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create item: %v", err))
	}

	_, err = surrealdb.Update[Item](ctx, db, *item.ID, map[string]any{
		"quantity": 45,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to update item: %v", err))
	}

	_, err = surrealdb.Delete[Item](ctx, db, *item.ID)
	if err != nil {
		panic(fmt.Sprintf("Failed to delete item: %v", err))
	}

	// Wait for all expected notifications to be received
	select {
	case <-received:
		// All notifications received
	case <-time.After(2 * time.Second):
		panic("Timeout waiting for all notifications")
	}

	fmt.Println("Live query with diff being terminated")

	err = surrealdb.Kill(ctx, db, live.String())
	if err != nil {
		panic(fmt.Sprintf("Failed to kill live query: %v", err))
	}

	select {
	case <-done:
		fmt.Println("Goroutine exited after channel closed")
	case <-time.After(2 * time.Second):
		panic("Timeout: notification channel was not closed after Kill")
	}

}
Output:

Started live query with diff enabled
Action: CREATE, Result: [{op=replace path=/ value={id=inventory:⟨UUID⟩ name=Screwdriver quantity=50}}]
Action: UPDATE, Result: [{op=remove path=/name} {op=replace path=/quantity value=45}]
Action: DELETE, Result: {id=inventory:⟨UUID⟩ quantity=45}
Live query with diff being terminated
Notification channel closed
Goroutine exited after channel closed

func Merge added in v0.3.0

func Merge[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat, data any) (*TResult, error)

Merge a table or record in the database like a PATCH request.

func Patch added in v0.2.0

func Patch[TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat, patches []PatchData) (*[]PatchData, error)

Patches either all records in a table or a single record with specified patches.

func Query added in v0.3.0

func Query[TResult any](ctx context.Context, db *DB, sql string, vars map[string]any) (*[]QueryResult[TResult], error)

Query executes a query against the SurrealDB database.

Query supports:

  • Full SurrealQL syntax including transactions
  • Parameterized queries for security
  • Typed results with generics
  • Multiple statements in a single call

It takes a SurrealQL query to be executed, and the variables to parameterize the query, and returns a slice of QueryResult whose type parameter is the result type.

Examples

Execute a SurrealQL query with typed results:

results, err := surrealdb.Query[[]Person](
  context.Background(),
  db,
  "SELECT * FROM persons WHERE age > $minAge",
  map[string]any{
      "minAge": 18,
  },
)

You can also use Query for transactions with variables:

transactionResults, err := surrealdb.Query[[]any](
  context.Background(),
  db,
  `
  BEGIN TRANSACTION;
  CREATE person:$johnId SET name = $johnName, age = $johnAge;
  CREATE person:$janeId SET name = $janeName, age = $janeAge;
  COMMIT TRANSACTION;
  `,
  map[string]any{
      "johnId": "john",
      "johnName": "John",
      "johnAge": 30,
      "janeId": "jane",
      "janeName": "Jane",
      "janeAge": 25,
  },
)

Or use a single CREATE with content variable:

createResult, err := surrealdb.Query[[]Person](
    context.Background(),
    db,
    "CREATE person:$id CONTENT $content",
    map[string]any{
		"id": "alice",
		"content": map[string]any{
			"name": "Alice",
			"age": 28,
			"city": "New York",
		},
	},
)

Handling errors

If the query fails, the returned error will be a `joinError` created by the errors.Join function, which contains all the errors that occurred during the query execution. The caller can check the Error field of each QueryResult to see if the query failed, or check the returned error from the Query function to see if the query failed.

If the caller wants to handle the query errors, if any, it can check the Error field of each QueryResult, or call:

errors.Is(err, &surrealdb.QueryError{})

on the returned error to see if it is (or contains) a QueryError.

Query errors are non-retriable

If the error is a QueryError, the caller should NOT retry the query, because the query is already executed and the error is not recoverable, and often times the error is caused by a bug in the query itself.

When can you safely retry the query when this function returns an error?

Generally speaking, automatic retries make sense only when the error is transient, such as a network error, a timeout, or a server error that is not related to the query itself. In such cases, the caller can retry the query by calling the Query function again.

For this function, the caller may retry when the error is:

  • RPCError: because we should get a RPC error only when the RPC failed due to anything other than the query error
  • constants.ErrTimeout: This means we send the HTTP request or a WebSocket message to SurrealDB in timely manner, which is often due to temporary network issues or server overload.

What non-retriable errors will Query return?

However, if the error is any of the following, the caller should NOT retry the query:

  • QueryError: This means the query failed due to a syntax error, a type error, or a logical error in the query itself.
  • Unmarshal error: This means the response from the server could not be unmarshaled into the expected type, which is often due to a bug in the code or a mismatch between the expected type and the actual response type.
  • Marshal error: This means the request could not be marshaled using CBOR, which is often due to a bug in the code that tries to send something that cannot be marshaled or understood by SurrealDB, such as a struct with unsupported types.
  • Anything else: It's just safer to not retry when we aren't sure if the error is whether transient or permanent.

RPCError is retriable only for Query

Note that RPCError is retriable only for the Query RPC method, because in other cases, the RPCError may also indicate a query error. For example, if you tried to insert a duplicate record using the Insert RPC, you may get an RPCError saying so, which is not retriable.

If you tried to insert the same duplicate record using the Query RPC method with `INSERT` statement, you may get no RPCError, but a QueryError saying so, enabling you to easily diferentiate between retriable and non-retriable errors.

Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	type NestedStruct struct {
		City string `json:"city"`
	}

	type Person struct {
		ID           *models.RecordID `json:"id,omitempty"`
		Name         string           `json:"name"`
		NestedMap    map[string]any   `json:"nested_map,omitempty"`
		NestedStruct `json:"nested_struct,omitempty"`
		CreatedAt    models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt    *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	createQueryResults, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`CREATE type::thing($tb, $id) CONTENT $content`,
		map[string]any{
			"tb": "persons",
			"id": "yusuke",
			"content": map[string]any{
				"name": "Yusuke",
				"nested_struct": NestedStruct{
					City: "Tokyo",
				},
				"created_at": models.CustomDateTime{
					Time: createdAt,
				},
			},
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of query results: %d\n", len(*createQueryResults))
	fmt.Printf("First query result's status: %+s\n", (*createQueryResults)[0].Status)
	fmt.Printf("Persons contained in the first query result: %+v\n", (*createQueryResults)[0].Result)

}
Output:

Number of query results: 1
First query result's status: OK
Persons contained in the first query result: [{ID:persons:yusuke Name:Yusuke NestedMap:map[] NestedStruct:{City:Tokyo} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:<nil>}]
Example (Bulk_insert_upsert)

This example demonstrates how you can batch insert and upsert records, with specifying RETURN NONE to avoid unnecessary data transfer and decoding.

package main

import (
	"context"
	"fmt"
	"strings"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	/// You can make it a schemaful table by defining fields like this:
	//
	// _, err := surrealdb.Query[any](
	// 	db,
	// 	`DEFINE TABLE persons SCHEMAFULL;
	// 	DEFINE FIELD note ON persons TYPE string;
	// 	DEFINE FIELD num ON persons TYPE int;
	// 	DEFINE FIELD loc ON persons TYPE geometry<point>;
	// `,
	// 	nil,
	// )
	// if err != nil {
	// 	panic(err)
	// }
	//
	/// If you do that, ensure that fields do not have `omitempty` json tags!
	///
	/// Why?
	/// Our cbor library reuses `json` tags for CBOR encoding/decoding,
	/// and `omitempty` skips the encoding of the field if it is empty.
	///
	/// For example, if you define an `int` field with `omitempty` tag,
	/// a value of `0` will not be encoded, resulting in an query error due:
	///   Found NONE for field `num`, with record `persons:p0`, but expected a int

	type Person struct {
		ID   *models.RecordID `json:"id"`
		Note string           `json:"note"`
		// As writte nabove whether it is `json:"num,omitempty"` or `json:"num"` is important,.
		// depending on what you want to achieve.
		Num int                  `json:"num"`
		Loc models.GeometryPoint `json:"loc"`
	}

	nthPerson := func(i int) Person {
		return Person{
			ID:   &models.RecordID{Table: "persons", ID: fmt.Sprintf("p%d", i)},
			Note: fmt.Sprintf("inserted%d", i),
			Num:  i,
			Loc: models.GeometryPoint{
				Longitude: 12.34 + float64(i),
				Latitude:  45.65 + float64(i),
			},
		}
	}

	var persons []Person
	for i := 0; i < 2; i++ {
		persons = append(persons, nthPerson(i))
	}

	insert, err := surrealdb.Query[any](
		context.Background(),
		db,
		`INSERT INTO persons $persons RETURN NONE`,
		map[string]any{
			"persons": persons,
		})
	if err != nil {
		panic(err)
	}
	fmt.Println("# INSERT INTO")
	fmt.Printf("Count   : %d\n", len(*insert))
	fmt.Printf("Status  : %+s\n", (*insert)[0].Status)
	fmt.Printf("Result  : %+v\n", (*insert)[0].Result)

	select1, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`SELECT * FROM persons ORDER BY id.id`,
		nil)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Selected: %+v\n", (*select1)[0].Result)

	persons = append(persons, nthPerson(2))

	insertIgnore, err := surrealdb.Query[any](
		context.Background(),
		db,
		`INSERT IGNORE INTO persons $persons RETURN NONE`,
		map[string]any{
			"persons": persons,
		})
	if err != nil {
		panic(err)
	}
	fmt.Println("# INSERT IGNORE INTO")
	fmt.Printf("Count   : %d\n", len(*insertIgnore))
	fmt.Printf("Status  : %+s\n", (*insertIgnore)[0].Status)
	fmt.Printf("Result  : %+v\n", (*insertIgnore)[0].Result)

	select2, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`SELECT * FROM persons ORDER BY id.id`,
		nil)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Selected: %+v\n", (*select2)[0].Result)

	for i := 0; i < 3; i++ {
		persons[i].Note = fmt.Sprintf("updated%d", i)
	}
	persons = append(persons, nthPerson(3))
	var upsertQueries []string
	vars := make(map[string]any)
	for i, p := range persons {
		upsertQueries = append(upsertQueries,
			fmt.Sprintf(`UPSERT persons CONTENT $content%d RETURN NONE`, i),
		)
		vars[fmt.Sprintf("content%d", i)] = p
	}
	upsert, err := surrealdb.Query[any](
		context.Background(),
		db,
		strings.Join(upsertQueries, ";"),
		vars,
	)
	if err != nil {
		panic(err)
	}
	fmt.Println("# UPSERT CONTENT")
	fmt.Printf("Count   : %d\n", len(*upsert))
	fmt.Printf("Status  : %+s\n", (*upsert)[0].Status)
	fmt.Printf("Result  : %+v\n", (*upsert)[0].Result)

	select3, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`SELECT * FROM persons ORDER BY id.id`,
		nil)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Selected: %+v\n", (*select3)[0].Result)

}
Output:

# INSERT INTO
Count   : 1
Status  : OK
Result  : []
Selected: [{ID:persons:p0 Note:inserted0 Num:0 Loc:{Latitude:45.65 Longitude:12.34}} {ID:persons:p1 Note:inserted1 Num:1 Loc:{Latitude:46.65 Longitude:13.34}}]
# INSERT IGNORE INTO
Count   : 1
Status  : OK
Result  : []
Selected: [{ID:persons:p0 Note:inserted0 Num:0 Loc:{Latitude:45.65 Longitude:12.34}} {ID:persons:p1 Note:inserted1 Num:1 Loc:{Latitude:46.65 Longitude:13.34}} {ID:persons:p2 Note:inserted2 Num:2 Loc:{Latitude:47.65 Longitude:14.34}}]
# UPSERT CONTENT
Count   : 4
Status  : OK
Result  : []
Selected: [{ID:persons:p0 Note:updated0 Num:0 Loc:{Latitude:45.65 Longitude:12.34}} {ID:persons:p1 Note:updated1 Num:1 Loc:{Latitude:46.65 Longitude:13.34}} {ID:persons:p2 Note:updated2 Num:2 Loc:{Latitude:47.65 Longitude:14.34}} {ID:persons:p3 Note:inserted3 Num:3 Loc:{Latitude:48.65 Longitude:15.34}}]
Example (ChangeFeedSchemafull)

ExampleQuery_changeFeedSchemafull demonstrates how to use Change Feeds with a schemaful table in SurrealDB. This example shows how to define a table with schema enforcement, define required fields, and track changes made to records that must conform to the schema.

// Connect to database
db := testenv.MustNew("surrealdbexamples", "changefeed_schemaful", "inventory")

ctx := context.Background()

// Define a schemaful table with change feed enabled
// SCHEMAFULL enforces that all records must have the defined fields
_, err := surrealdb.Query[any](ctx, db, `
		DEFINE TABLE inventory SCHEMAFULL CHANGEFEED 1h;
	`, nil)
if err != nil {
	panic(err)
}

// Note that DEFINE FIELD statements are not tracked by the change feed,
// although DEFINE TABLE statements are tracked.
//
// In schemaful tables:
// - TYPE string means the field is required (cannot be none or null)
// - TYPE option<string> means the field can be none
// - TYPE string | null means the field can be null
//
// In change feeds:
// - `null` fields appear as `null`
// - `none` fields do not appear at all
_, err = surrealdb.Query[any](ctx, db, `
		DEFINE FIELD sku ON TABLE inventory TYPE string;
		DEFINE FIELD name ON TABLE inventory TYPE string;
		DEFINE FIELD price ON TABLE inventory TYPE number ASSERT $value >= 0;
		DEFINE FIELD quantity ON TABLE inventory TYPE int ASSERT $value >= 0;
		DEFINE FIELD active ON TABLE inventory TYPE bool DEFAULT true;
		DEFINE FIELD notes ON TABLE inventory TYPE option<string>;
	`, nil)
if err != nil {
	panic(err)
}

// Make some changes to generate change feed entries
// All operations must comply with the schema
_, err = surrealdb.Query[any](ctx, db, `
		CREATE inventory:item1 SET
			sku = "SKU001",
			name = "Wireless Mouse",
			price = 29.99,
			quantity = 100,
			active = true,
			notes = "Best seller";

		UPDATE inventory:item1 SET quantity = 95;

		CREATE inventory:item2 SET
			sku = "SKU002",
			name = "USB Cable",
			price = 9.99,
			quantity = 250,
			active = true;
		-- notes is optional, so we can omit it when
		-- creating and updating records

		UPDATE inventory:item2 SET active = false;

		DELETE inventory:item2;
	`, nil)
if err != nil {
	panic(err)
}

type ChangeDefineTable struct {
	Name string `json:"name"`
}

type ChangeDefineField struct {
	Name  string `json:"name"`
	What  string `json:"what"`
	Table string `json:"table"`
}

// Change represents a change to a table in the database.
type Change struct {
	// DefineTable represents the definition of a new table in the database.
	DefineTable *ChangeDefineTable `json:"define_table"`
	// DefineField represents the definition of a new field in the table.
	DefineField *ChangeDefineField `json:"define_field"`
	// Update represents an update to a table in the database.
	// Note that in schemaful tables, all defined fields are present.
	Update map[string]any `json:"update"`
	// Delete represents a deletion from a table in the database.
	Delete map[string]any `json:"delete"`
}

// ChangeSet represents a set of changes in the database.
type ChangeSet struct {
	// Versionstamp is a unique identifier for the change set.
	Versionstamp uint64 `json:"versionstamp"`
	// Changes is a list of changes made in the database.
	Changes []Change `json:"changes"`
}

result, err := surrealdb.Query[[]ChangeSet](ctx, db, "SHOW CHANGES FOR TABLE inventory SINCE 0", nil)
if err != nil {
	panic(err)
}

showChangesResult := (*result)[0].Result

// Verify versionstamps are monotonic
monotonic := true
for i := 1; i < len(showChangesResult); i++ {
	if showChangesResult[i].Versionstamp <= showChangesResult[i-1].Versionstamp {
		monotonic = false
		break
	}
}
fmt.Printf("Versionstamps are monotonic: %v\n", monotonic)

// Count different types of changes
var defineTableCount, defineFieldCount, updateCount, deleteCount int
for _, changeSet := range showChangesResult {
	for _, change := range changeSet.Changes {
		if change.DefineTable != nil {
			defineTableCount++
		}
		if change.DefineField != nil {
			defineFieldCount++
		}
		if change.Update != nil {
			updateCount++
		}
		if change.Delete != nil {
			deleteCount++
		}
	}
}

if defineTableCount > 0 && updateCount > 0 && deleteCount > 0 {
	fmt.Println("Found change entries: table definitions, updates, and deletes")
}
if defineFieldCount > 0 {
	fmt.Printf("Field definitions tracked: %d\n", defineFieldCount)
}

// Show the last few changes with actual data
// Find the last table definition to show only recent changes
lastTableDefIndex := -1
for i := len(showChangesResult) - 1; i >= 0; i-- {
	for _, change := range showChangesResult[i].Changes {
		if change.DefineTable != nil {
			lastTableDefIndex = i
			break
		}
	}
	if lastTableDefIndex >= 0 {
		break
	}
}

if lastTableDefIndex >= 0 {
	// Show changes from the last table definition onwards
	recentChanges := showChangesResult[lastTableDefIndex:]

	// Limit to last 10 changes for readability
	startIndex := 0
	if len(recentChanges) > 10 {
		startIndex = len(recentChanges) - 10
	}

	fmt.Printf("Last %d changes:\n", len(recentChanges[startIndex:]))
	for _, changeSet := range recentChanges[startIndex:] {
		for _, change := range changeSet.Changes {
			if change.DefineTable != nil {
				fmt.Printf("  DefineTable: %s\n", change.DefineTable.Name)
			}
			if change.DefineField != nil {
				fmt.Printf("  DefineField: %s on %s\n", change.DefineField.Name, change.DefineField.Table)
			}
			if change.Update != nil {
				// Extract fields from the update (all fields present in schemaful table)
				if id, ok := change.Update["id"]; ok {
					sku := change.Update["sku"]
					name := change.Update["name"]
					price := change.Update["price"]
					quantity := change.Update["quantity"]
					active := change.Update["active"]
					notes, ok := change.Update["notes"]
					var notesStr string
					if !ok {
						notesStr = "<none>"
					} else if notes == nil {
						notesStr = "<null>"
					} else {
						notesStr = fmt.Sprintf("%v", notes)
					}
					fmt.Printf("  Update: id=%v, sku=%v, name=%v, price=%v, quantity=%v, active=%v, notes=%v\n",
						id, sku, name, price, quantity, active, notesStr)
				} else {
					fmt.Printf("  Update: %v\n", change.Update)
				}
			}
			if change.Delete != nil {
				// Extract id from the delete
				if id, ok := change.Delete["id"]; ok {
					fmt.Printf("  Delete: id=%v\n", id)
				} else {
					fmt.Printf("  Delete: %v\n", change.Delete)
				}
			}
		}
	}
}
Output:

Versionstamps are monotonic: true
Found change entries: table definitions, updates, and deletes
Last 6 changes:
  DefineTable: inventory
  Update: id={inventory item1}, sku=SKU001, name=Wireless Mouse, price=29.99, quantity=100, active=true, notes=Best seller
  Update: id={inventory item1}, sku=SKU001, name=Wireless Mouse, price=29.99, quantity=95, active=true, notes=Best seller
  Update: id={inventory item2}, sku=SKU002, name=USB Cable, price=9.99, quantity=250, active=true, notes=<none>
  Update: id={inventory item2}, sku=SKU002, name=USB Cable, price=9.99, quantity=250, active=false, notes=<none>
  Delete: id={inventory item2}
Example (ChangeFeedSchemaless)

ExampleQuery_changeFeedSchemaless demonstrates how to use Change Feeds in SurrealDB to track changes made to database records.

// Connect to database
db := testenv.MustNew("surrealdbexamples", "changefeed", "product")

ctx := context.Background()

// Enable change feed on the database
_, err := surrealdb.Query[any](ctx, db, "DEFINE TABLE product CHANGEFEED 1h", nil)
if err != nil {
	panic(err)
}

// Make some changes to generate change feed entries
_, err = surrealdb.Query[any](ctx, db, `
		CREATE product:1 SET name = "Laptop", price = 999.99;
		UPDATE product:1 SET price = 899.99;
		CREATE product:2 SET name = "Mouse", price = 29.99;
		DELETE product:2;
	`, nil)
if err != nil {
	panic(err)
}

type ChangeDefineTable struct {
	Name string `json:"name"`
}

// Change represents a change to a table in the database.
type Change struct {
	// DefineTable represents the definition of a new table in the database.
	// It has Name and nothing else.
	DefineTable *ChangeDefineTable `json:"define_table"`
	// Update represents an update to a table in the database.
	// Note that this may represent a new record being created.
	// In case of an update, the "id" field must be present,
	// and all other fields including unchanged fields must be included.
	Update map[string]any `json:"update"`
	// Delete represents a deletion from a table in the database.
	Delete map[string]any `json:"delete"`
}

// ChangeSet represents a set of changes in the database.
type ChangeSet struct {
	// Versionstamp is a unique identifier for the change set.
	// It is unique per database.
	Versionstamp uint64 `json:"versionstamp"`
	// Changes is a list of changes made in the database.
	// It may contain one or more table changes,
	// each represented as a map of field names to their new values.
	Changes []Change `json:"changes"`
}

result, err := surrealdb.Query[[]ChangeSet](ctx, db, "SHOW CHANGES FOR TABLE product SINCE 0", nil)
if err != nil {
	panic(err)
}

showChangesResult := (*result)[0].Result

// Verify versionstamps are monotonic
monotonic := true
for i := 1; i < len(showChangesResult); i++ {
	if showChangesResult[i].Versionstamp <= showChangesResult[i-1].Versionstamp {
		monotonic = false
		break
	}
}
fmt.Printf("Versionstamps are monotonic: %v\n", monotonic)

// Count different types of changes
var defineCount, updateCount, deleteCount int
for _, changeSet := range showChangesResult {
	for _, change := range changeSet.Changes {
		if change.DefineTable != nil {
			defineCount++
		}
		if change.Update != nil {
			updateCount++
		}
		if change.Delete != nil {
			deleteCount++
		}
	}
}

if defineCount > 0 && updateCount > 0 && deleteCount > 0 {
	fmt.Println("Found change entries: defines, updates, and deletes")
}

// Show the pattern of the last few changes with actual data
if len(showChangesResult) >= 5 {
	lastFive := showChangesResult[len(showChangesResult)-5:]
	fmt.Println("Last 5 changes:")
	for _, changeSet := range lastFive {
		for _, change := range changeSet.Changes {
			if change.DefineTable != nil {
				fmt.Printf("  DefineTable: %s\n", change.DefineTable.Name)
			}
			if change.Update != nil {
				// Extract key fields from the update
				if id, ok := change.Update["id"]; ok {
					if name, hasName := change.Update["name"]; hasName {
						if price, hasPrice := change.Update["price"]; hasPrice {
							fmt.Printf("  Update: id=%v, name=%v, price=%v\n", id, name, price)
						} else {
							fmt.Printf("  Update: id=%v, name=%v\n", id, name)
						}
					} else if price, hasPrice := change.Update["price"]; hasPrice {
						fmt.Printf("  Update: id=%v, price=%v\n", id, price)
					} else {
						fmt.Printf("  Update: id=%v\n", id)
					}
				} else {
					fmt.Printf("  Update: %v\n", change.Update)
				}
			}
			if change.Delete != nil {
				// Extract id from the delete
				if id, ok := change.Delete["id"]; ok {
					fmt.Printf("  Delete: id=%v\n", id)
				} else {
					fmt.Printf("  Delete: %v\n", change.Delete)
				}
			}
		}
	}
}
Output:

Versionstamps are monotonic: true
Found change entries: defines, updates, and deletes
Last 5 changes:
  DefineTable: product
  Update: id={product 1}, name=Laptop, price=999.99
  Update: id={product 1}, name=Laptop, price=899.99
  Update: id={product 2}, name=Mouse, price=29.99
  Delete: id={product 2}
Example (Count_groupAll)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "querytest", "product")

	type Product struct {
		ID       models.RecordID `json:"id,omitempty"`
		Name     string          `json:"name,omitempty"`
		Category string          `json:"category,omitempty"`
	}

	a := Product{
		ID:       models.NewRecordID("product", "a"),
		Name:     "A",
		Category: "One",
	}
	b := Product{
		ID:       models.NewRecordID("product", "b"),
		Name:     "B",
		Category: "One",
	}
	c := Product{
		ID:       models.NewRecordID("product", "c"),
		Name:     "C",
		Category: "Two",
	}

	for _, p := range []Product{a, b, c} {
		created, err := surrealdb.Create[Product](
			context.Background(),
			db,
			p.ID,
			p,
		)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Created product: %+v\n", *created)
	}

	type CountResult struct {
		C int `json:"c,omitempty"`
	}

	res, err := surrealdb.Query[[]CountResult](
		context.Background(),
		db,
		"SELECT COUNT() as c FROM product GROUP ALL",
		map[string]any{},
	)
	if err != nil {
		panic(err)
	}

	countResult := (*res)[0].Result[0]

	fmt.Printf("Count: %d\n", countResult.C)

}
Output:

Created product: {ID:{Table:product ID:a} Name:A Category:One}
Created product: {ID:{Table:product ID:b} Name:B Category:One}
Created product: {ID:{Table:product ID:c} Name:C Category:Two}
Count: 3
Example (Count_groupBy)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "querytest", "product")

	type Product struct {
		ID       models.RecordID `json:"id,omitempty"`
		Name     string          `json:"name,omitempty"`
		Category string          `json:"category,omitempty"`
	}

	a := Product{
		ID:       models.NewRecordID("product", "a"),
		Name:     "A",
		Category: "One",
	}
	b := Product{
		ID:       models.NewRecordID("product", "b"),
		Name:     "B",
		Category: "One",
	}
	c := Product{
		ID:       models.NewRecordID("product", "c"),
		Name:     "C",
		Category: "Two",
	}

	for _, p := range []Product{a, b, c} {
		created, err := surrealdb.Create[Product](
			context.Background(),
			db,
			p.ID,
			p,
		)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Created product: %+v\n", *created)
	}

	type ProductCategorySummary struct {
		Category string `json:"category,omitempty"`
		Count    int    `json:"count,omitempty"`
	}

	res, err := surrealdb.Query[[]ProductCategorySummary](
		context.Background(),
		db,
		// Note that there's no `COUNT(*)` in SurrealDB.
		// When counting, you use either `COUNT()` or `COUNT(field)`,
		// with either GROUP BY or GROUP ALL.
		"SELECT category, COUNT() AS count FROM product GROUP BY category",
		map[string]any{},
	)
	if err != nil {
		panic(err)
	}

	summaries := (*res)[0].Result

	for i, summary := range summaries {
		fmt.Printf("Category %d: %s, Count: %d\n", i+1, summary.Category, summary.Count)
	}

}
Output:

Created product: {ID:{Table:product ID:a} Name:A Category:One}
Created product: {ID:{Table:product ID:b} Name:B Category:One}
Created product: {ID:{Table:product ID:c} Name:C Category:Two}
Category 1: One, Count: 2
Category 2: Two, Count: 1
Example (Create_none_null_fields)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const (
	NullString = "null"
	NoneString = "none"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = false

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE bool | null;
		 DEFINE FIELD option ON t TYPE option<bool>;
		 CREATE t:a SET nullable = $nil;
		 CREATE t:b SET nullable = true;
		 CREATE t:c SET nullable = true, option = false;
		 CREATE t:d SET nullable = false, option = true;
		 CREATE t:e SET nullable = false;
		 CREATE t:f SET nullable = false, option = $none;
		`,
		map[string]any{
			"id":   models.NewRecordID("t", 1),
			"nil":  nil,
			"none": models.None,
		})
	if err != nil {
		panic(err)
	}

	fmt.Println("Created records with none and null fields successfully")

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *bool            `json:"nullable"`
		Option   *bool            `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NullString
		} else {
			nullable = fmt.Sprintf("%t", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NoneString
		} else {
			option = fmt.Sprintf("%t", *t.Option)
		}

		fmt.Printf("ID: %s, Nullable: %s, Option: %s\n", id, nullable, option)
	}
}
Output:

Created records with none and null fields successfully
ID: t:a, Nullable: null, Option: false
ID: t:b, Nullable: true, Option: false
ID: t:c, Nullable: true, Option: false
ID: t:d, Nullable: false, Option: true
ID: t:e, Nullable: false, Option: false
ID: t:f, Nullable: false, Option: false
Example (Create_none_null_fields_surrealcbor)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const NilString = "<nil>"

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = true

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE bool | null;
		 DEFINE FIELD option ON t TYPE option<bool>;
		 CREATE t:a SET nullable = $nil;
		 CREATE t:b SET nullable = true;
		 CREATE t:c SET nullable = true, option = false;
		 CREATE t:d SET nullable = false, option = true;
		 CREATE t:e SET nullable = false;
		 CREATE t:f SET nullable = false, option = $none;
		`,
		map[string]any{
			"id":   models.NewRecordID("t", 1),
			"nil":  nil,
			"none": models.None,
		})
	if err != nil {
		panic(err)
	}

	fmt.Println("Created records with none and null fields successfully")

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *bool            `json:"nullable"`
		Option   *bool            `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NilString
		} else {
			nullable = fmt.Sprintf("%t", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NilString
		} else {
			option = fmt.Sprintf("%t", *t.Option)
		}

		fmt.Printf("ID: %s, Nullable: %s, Option: %s\n", id, nullable, option)
	}
}
Output:

Created records with none and null fields successfully
ID: t:a, Nullable: <nil>, Option: <nil>
ID: t:b, Nullable: true, Option: <nil>
ID: t:c, Nullable: true, Option: false
ID: t:d, Nullable: false, Option: true
ID: t:e, Nullable: false, Option: <nil>
ID: t:f, Nullable: false, Option: <nil>
Example (Embedded_struct)
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	type Base struct {
		ID *models.RecordID `json:"id,omitempty"`
	}

	type Profile struct {
		Base
		City string `json:"city"`
	}

	type Person struct {
		Base
		Name      string `json:"name"`
		Profile   Profile
		CreatedAt models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	createQueryResults, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`CREATE type::thing($tb, $id) CONTENT $content`,
		map[string]any{
			"tb": "persons",
			"id": "yusuke",
			"content": map[string]any{
				"name": "Yusuke",
				"created_at": models.CustomDateTime{
					Time: createdAt,
				},
				"profile": map[string]any{
					"id":   models.NewRecordID("profiles", "yusuke"),
					"city": "Tokyo",
				},
			},
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of query results: %d\n", len(*createQueryResults))
	fmt.Printf("First query result's status: %+s\n", (*createQueryResults)[0].Status)
	fmt.Printf("Persons contained in the first query result: %+v\n", (*createQueryResults)[0].Result)

	updatedAt, err := time.Parse(time.RFC3339, "2023-10-02T12:00:00Z")
	if err != nil {
		panic(err)
	}
	updateQueryResults, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`UPDATE $id CONTENT $content`,
		map[string]any{
			"id": models.NewRecordID("persons", "yusuke"),
			"content": map[string]any{
				"name":       "Yusuke Updated Last",
				"created_at": createdAt,
				"updated_at": updatedAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of update query results: %d\n", len(*updateQueryResults))
	fmt.Printf("Update query result's status: %+s\n", (*updateQueryResults)[0].Status)
	fmt.Printf("Persons contained in the update query result: %+v\n", (*updateQueryResults)[0].Result)

	selectQueryResults, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`SELECT * FROM $id`,
		map[string]any{
			"id": models.NewRecordID("persons", "yusuke"),
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of select query results: %d\n", len(*selectQueryResults))
	fmt.Printf("Select query result's status: %+s\n", (*selectQueryResults)[0].Status)
	fmt.Printf("Persons contained in the select query result: %+v\n", (*selectQueryResults)[0].Result)

}
Output:

Number of query results: 1
First query result's status: OK
Persons contained in the first query result: [{Base:{ID:persons:yusuke} Name:Yusuke Profile:{Base:{ID:profiles:yusuke} City:Tokyo} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:<nil>}]
Number of update query results: 1
Update query result's status: OK
Persons contained in the update query result: [{Base:{ID:persons:yusuke} Name:Yusuke Updated Last Profile:{Base:{ID:<nil>} City:} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:2023-10-02T12:00:00Z}]
Number of select query results: 1
Select query result's status: OK
Persons contained in the select query result: [{Base:{ID:persons:yusuke} Name:Yusuke Updated Last Profile:{Base:{ID:<nil>} City:} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:2023-10-02T12:00:00Z}]
Example (Issue192)

See https://github.com/surrealdb/surrealdb.go/issues/292

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query_issue192", "t")

	_, err := surrealdb.Query[any](
		context.Background(),
		db,
		`DEFINE TABLE IF NOT EXISTS t SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS modified_at2 ON TABLE t TYPE option<datetime>;
CREATE t:s`,
		map[string]any{"name": "John Doe"},
	)
	if err != nil {
		panic(err)
	}

	type ReturnData1 struct {
		ID         *models.RecordID      `json:"id,omitempty"`
		ModifiedAt models.CustomDateTime `json:"modified_at,omitempty"`
	}

	data, err := surrealdb.Query[[]ReturnData1](
		context.Background(),
		db,
		"SELECT id, modified_at FROM t",
		nil)
	if err != nil {
		panic(err)
	}

	got := (*data)[0].Result[0]

	fmt.Printf("ID: %s\n", got.ID)
	fmt.Printf("ModifiedAt: %v\n", got.ModifiedAt)
	fmt.Printf("ModifiedAt.IsZero(): %v\n", got.ModifiedAt.IsZero())

	type ReturnData2 struct {
		ID         *models.RecordID       `json:"id,omitempty"`
		ModifiedAt *models.CustomDateTime `json:"modified_at,omitempty"`
	}

	data2, err := surrealdb.Query[[]ReturnData2](
		context.Background(),
		db,
		"SELECT id, modified_at FROM t",
		nil)
	if err != nil {
		panic(err)
	}

	got2 := (*data2)[0].Result[0]

	fmt.Printf("ID: %s\n", got2.ID)
	// With fxamacker/cbor: returns zero-value struct (not nil)
	// With surrealcbor: returns nil
	// Both should print the same format for consistency
	if got2.ModifiedAt == nil || got2.ModifiedAt.IsZero() {
		fmt.Printf("ModifiedAt: <nil or zero>\n")
	}
	fmt.Printf("ModifiedAt.IsZero(): %v\n", got2.ModifiedAt.IsZero())

}
Output:

ID: t:s
ModifiedAt: {0001-01-01 00:00:00 +0000 UTC}
ModifiedAt.IsZero(): true
ID: t:s
ModifiedAt: <nil or zero>
ModifiedAt.IsZero(): true
Example (Issue291)

See https://github.com/surrealdb/surrealdb.go/issues/291

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query_issue291", "t")

	_, err := surrealdb.Query[any](
		context.Background(),
		db,
		`DEFINE TABLE IF NOT EXISTS t SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS i ON TABLE t TYPE option<int>;
DEFINE FIELD IF NOT EXISTS j ON TABLE t TYPE option<string>;
CREATE t:s;`,
		map[string]any{"name": "John Doe"},
	)
	if err != nil {
		panic(err)
	}

	type ReturnData struct {
		I *int    `json:"i"`
		J *string `json:"j"`
	}

	dataNones, err := surrealdb.Query[[]ReturnData](
		context.Background(),
		db,
		"SELECT i, j FROM t",
		nil)
	if err != nil {
		panic(err)
	}

	got := (*dataNones)[0].Result[0]

	// With fxamacker/cbor: returns zero values (0, "")
	// With surrealcbor: returns nil
	// We need to handle both cases
	if got.I == nil || *got.I == 0 {
		fmt.Printf("I: <nil or zero>\n")
	} else {
		fmt.Printf("I: %+v\n", *got.I)
	}

	if got.J == nil || *got.J == "" {
		fmt.Printf("J: <nil or zero>\n")
	} else {
		fmt.Printf("J: %q\n", *got.J)
	}

	dataAll, err := surrealdb.Query[[]ReturnData](
		context.Background(),
		db,
		"SELECT * FROM t",
		nil)
	if err != nil {
		panic(err)
	}

	gotAll := (*dataAll)[0].Result[0]

	fmt.Printf("I: %+v\n", gotAll.I)
	fmt.Printf("J: %+v\n", gotAll.J)

}
Output:

I: <nil or zero>
J: <nil or zero>
I: <nil>
J: <nil>
Example (Live)

ExampleQuery_live demonstrates using LIVE SELECT via the Query RPC. LIVE SELECT returns matching records as map[string]any in notification.Result. The notification channel is automatically closed when Kill is called.

package main

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

// formatRecordResult formats a record result (map[string]any) for testing.
// This is used for regular live query results (without diff) and DELETE operations.
// It handles the id field specially, formatting RecordID as table:⟨UUID⟩.
func formatRecordResult(record map[string]any) string {
	keys := make([]string, 0, len(record))
	for k := range record {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var parts []string
	for _, k := range keys {
		val := record[k]
		if k == "id" {

			recordID := val.(models.RecordID)
			parts = append(parts, fmt.Sprintf("id=%s:⟨UUID⟩", recordID.Table))
		} else {
			parts = append(parts, fmt.Sprintf("%s=%v", k, val))
		}
	}
	return "{" + strings.Join(parts, " ") + "}"
}

func main() {
	config := testenv.MustNewConfig("surrealdbexamples", "livequery_query", "products")
	config.Endpoint = testenv.GetSurrealDBWSURL()

	db := config.MustNew()

	type Product struct {
		ID    *models.RecordID `json:"id,omitempty"`
		Name  string           `json:"name"`
		Price float64          `json:"price"`
		Stock int              `json:"stock"`
	}

	ctx := context.Background()

	result, err := surrealdb.Query[models.UUID](ctx, db, "LIVE SELECT * FROM products WHERE stock < 10", map[string]any{})
	if err != nil {
		panic(fmt.Sprintf("Failed to start live query: %v", err))
	}

	liveID := (*result)[0].Result.String()
	fmt.Println("Started live query")

	notifications, err := db.LiveNotifications(liveID)
	if err != nil {
		panic(fmt.Sprintf("Failed to get live notifications channel: %v", err))
	}

	received := make(chan struct{})
	done := make(chan bool)
	notificationCount := 0
	go func() {
		for notification := range notifications {
			notificationCount++

			// LIVE SELECT returns matching records as map[string]any
			record, ok := notification.Result.(map[string]any)
			if !ok {
				panic(fmt.Sprintf("Expected map[string]any for LIVE SELECT result, got %T", notification.Result))
			}

			fmt.Printf("Notification %d - Action: %s, Result: %s\n", notificationCount, notification.Action, formatRecordResult(record))

			if notificationCount >= 3 {
				close(received)
			}
		}
		// Channel was closed
		fmt.Println("Notification channel closed")
		done <- true
	}()

	_, err = surrealdb.Create[Product](ctx, db, "products", map[string]any{
		"name":  "Widget",
		"price": 9.99,
		"stock": 5,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create product: %v", err))
	}

	_, err = surrealdb.Create[Product](ctx, db, "products", map[string]any{
		"name":  "Gadget",
		"price": 19.99,
		"stock": 3,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create second product: %v", err))
	}

	_, err = surrealdb.Create[Product](ctx, db, "products", map[string]any{
		"name":  "Abundant Item",
		"price": 5.99,
		"stock": 100,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create third product: %v", err))
	}

	_, err = surrealdb.Create[Product](ctx, db, "products", map[string]any{
		"name":  "Rare Item",
		"price": 99.99,
		"stock": 1,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create fourth product: %v", err))
	}

	// Wait for all expected notifications to be received
	select {
	case <-received:
		// All notifications received
	case <-time.After(2 * time.Second):
		panic("Timeout waiting for all notifications")
	}

	err = surrealdb.Kill(ctx, db, liveID)
	if err != nil {
		panic(fmt.Sprintf("Failed to kill live query: %v", err))
	}

	fmt.Println("Live query terminated")

	select {
	case <-done:
		fmt.Println("Goroutine exited after channel closed")
	case <-time.After(2 * time.Second):
		panic("Timeout: notification channel was not closed after Kill")
	}

}
Output:

Started live query
Notification 1 - Action: CREATE, Result: {id=products:⟨UUID⟩ name=Widget price=9.99 stock=5}
Notification 2 - Action: CREATE, Result: {id=products:⟨UUID⟩ name=Gadget price=19.99 stock=3}
Notification 3 - Action: CREATE, Result: {id=products:⟨UUID⟩ name=Rare Item price=99.99 stock=1}
Live query terminated
Notification channel closed
Goroutine exited after channel closed
Example (None_and_null_handling_allExistingFields)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const (
	NullString = "null"
	NoneString = "none"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "t")

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE bool | null;
		 DEFINE FIELD option ON t TYPE option<bool>;
		 CREATE t:a SET nullable = null;
		 CREATE t:b SET nullable = true;
		 CREATE t:c SET nullable = true, option = false;
		 CREATE t:d SET nullable = false, option = true;
		 CREATE t:e SET nullable = false;
		 CREATE t:f SET nullable = false, option = NONE;
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *bool            `json:"nullable"`
		Option   *bool            `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT * FROM t ORDER BY id.id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NullString
		} else {
			nullable = fmt.Sprintf("%t", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NoneString
		} else {
			option = fmt.Sprintf("%t", *t.Option)
		}

		fmt.Printf("ID: %s, Nullable: %s, Option: %s\n", id, nullable, option)
	}

}
Output:

ID: t:a, Nullable: null, Option: none
ID: t:b, Nullable: true, Option: none
ID: t:c, Nullable: true, Option: false
ID: t:d, Nullable: false, Option: true
ID: t:e, Nullable: false, Option: none
ID: t:f, Nullable: false, Option: none
Example (None_and_null_handling_explicitFields)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const (
	NullString = "null"
	NoneString = "none"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = false

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE bool | null;
		 DEFINE FIELD option ON t TYPE option<bool>;
		 CREATE t:a SET nullable = null;
		 CREATE t:b SET nullable = true;
		 CREATE t:c SET nullable = true, option = false;
		 CREATE t:d SET nullable = false, option = true;
		 CREATE t:e SET nullable = false;
		 CREATE t:f SET nullable = false, option = NONE;
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *bool            `json:"nullable"`
		Option   *bool            `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NullString
		} else {
			nullable = fmt.Sprintf("%t", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NoneString
		} else {
			option = fmt.Sprintf("%t", *t.Option)
		}

		fmt.Printf("ID: %s, Nullable: %s, Option: %s\n", id, nullable, option)
	}

}
Output:

ID: t:a, Nullable: null, Option: false
ID: t:b, Nullable: true, Option: false
ID: t:c, Nullable: true, Option: false
ID: t:d, Nullable: false, Option: true
ID: t:e, Nullable: false, Option: false
ID: t:f, Nullable: false, Option: false
Example (None_and_null_handling_explicitFields_ints)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const (
	NullString = "null"
	NoneString = "none"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = false

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE int | null;
		 DEFINE FIELD option ON t TYPE option<int>;
		 CREATE t:a SET nullable = null;
		 CREATE t:b SET nullable = 2;
		 CREATE t:c SET nullable = 2, option = 1;
		 CREATE t:d SET nullable = 1, option = 2;
		 CREATE t:e SET nullable = 1;
		 CREATE t:f SET nullable = 1, option = NONE;
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *int             `json:"nullable"`
		Option   *int             `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NullString
		} else {
			nullable = fmt.Sprintf("%v", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NoneString
		} else {
			option = fmt.Sprintf("%v", *t.Option)
		}

		fmt.Printf("ID: %v, Nullable: %v, Option: %s\n", id, nullable, option)
	}

}
Output:

ID: t:a, Nullable: null, Option: 0
ID: t:b, Nullable: 2, Option: 0
ID: t:c, Nullable: 2, Option: 1
ID: t:d, Nullable: 1, Option: 2
ID: t:e, Nullable: 1, Option: 0
ID: t:f, Nullable: 1, Option: 0
Example (None_and_null_handling_explicitFields_ints_surrealcbor)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const NilString = "<nil>"

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = true

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE int | null;
		 DEFINE FIELD option ON t TYPE option<int>;
		 CREATE t:a SET nullable = null;
		 CREATE t:b SET nullable = 2;
		 CREATE t:c SET nullable = 2, option = 1;
		 CREATE t:d SET nullable = 1, option = 2;
		 CREATE t:e SET nullable = 1;
		 CREATE t:f SET nullable = 1, option = NONE;
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *int             `json:"nullable"`
		Option   *int             `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NilString
		} else {
			nullable = fmt.Sprintf("%v", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NilString
		} else {
			option = fmt.Sprintf("%v", *t.Option)
		}

		fmt.Printf("ID: %v, Nullable: %v, Option: %s\n", id, nullable, option)
	}

}
Output:

ID: t:a, Nullable: <nil>, Option: <nil>
ID: t:b, Nullable: 2, Option: <nil>
ID: t:c, Nullable: 2, Option: 1
ID: t:d, Nullable: 1, Option: 2
ID: t:e, Nullable: 1, Option: <nil>
ID: t:f, Nullable: 1, Option: <nil>
Example (None_and_null_handling_explicitFields_surrealcbor)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

const NilString = "<nil>"

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = true

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		 DEFINE FIELD nullable ON t TYPE bool | null;
		 DEFINE FIELD option ON t TYPE option<bool>;
		 CREATE t:a SET nullable = null;
		 CREATE t:b SET nullable = true;
		 CREATE t:c SET nullable = true, option = false;
		 CREATE t:d SET nullable = false, option = true;
		 CREATE t:e SET nullable = false;
		 CREATE t:f SET nullable = false, option = NONE;
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}

	type T struct {
		ID       *models.RecordID `json:"id,omitempty"`
		Nulabble *bool            `json:"nullable"`
		Option   *bool            `json:"option"`
	}

	selected, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id, nullable, option FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, t := range (*selected)[0].Result {
		id := t.ID

		var nullable string
		if t.Nulabble == nil {
			nullable = NilString
		} else {
			nullable = fmt.Sprintf("%t", *t.Nulabble)
		}

		var option string
		if t.Option == nil {
			option = NilString
		} else {
			option = fmt.Sprintf("%t", *t.Option)
		}

		fmt.Printf("ID: %s, Nullable: %v, Option: %v\n", id, nullable, option)
	}

}
Output:

ID: t:a, Nullable: <nil>, Option: <nil>
ID: t:b, Nullable: true, Option: <nil>
ID: t:c, Nullable: true, Option: false
ID: t:d, Nullable: false, Option: true
ID: t:e, Nullable: false, Option: <nil>
ID: t:f, Nullable: false, Option: <nil>
Example (Null_none_customdatetime_roundtrip)
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = false

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		//  DEFINE FIELD nullable_zero ON t TYPE datetime | null;
		 DEFINE FIELD nullable_nil ON t TYPE datetime | null;
		 DEFINE FIELD option_zero ON t TYPE option<datetime>;
		 DEFINE FIELD option_zero_omitempty ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_zero ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_nil ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_nil_omitempty ON t TYPE option<datetime>;
		`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	type T struct {
		ID *models.RecordID `json:"id,omitempty"`
		// NullableZero tests how a Zero value of CustomDateTime is marshaled into a nullable field
		//
		// This fails like this:
		//   Found NONE for field `nullable_zero`, with record `t:1`, but expected a datetime | null
		// NullableZero  models.CustomDateTime  `json:"nullable_zero"`

		// NullableNil tests how a Go pointer to nil is marshaled into a nullable field
		NullableNil *models.CustomDateTime `json:"nullable_nil"`

		OptionZero models.CustomDateTime `json:"option_zero"`

		// OptionZeroOmitEmpty tests how a Zero value of CustomDateTime is marshaled into an option field
		OptionZeroOmitEmpty models.CustomDateTime `json:"option_zero_omitempty,omitempty"`

		// OptionZeroOmitZero tests how a Zero value of CustomDateTime is marshaled into an option field
		// when the field is omitzero
		OptionZeroOmitZero models.CustomDateTime `json:"option_zero_omitzero,omitzero"`

		// OptionPtrZero tests how a Go pointer to a Zero value of CustomDateTime is marshaled
		OptionPtrZero *models.CustomDateTime `json:"option_ptr_zero"`

		// OptionNil tests how a Go pointer to nil is marshaled
		//
		// This fails like this:
		//   Found NULL for field `option_ptr_nil`, with record `t:1`, but expected a option<datetime>
		// OptionPtrNil *models.CustomDateTime `json:"option_ptr_nil"`

		// OptionPtrNilOmitEmpty tests how a Go pointer to nil is marshaled into an option field
		// when the field is omitted if empty.
		OptionPtrNilOmitEmpty *models.CustomDateTime `json:"option_ptr_nil_omitempty,omitempty"`
	}

	// Marshaling rule:
	//
	// m1. Go `nil` w/o omitempty is SurrealDB `null`
	// m2. Go `nil` w/  omitempty is SurrealDB `none`
	// m3. Zero value of CustomDateTime is SurrealDB `none`

	// Unmarshaling rule:
	//
	// u1. SurrealDB `null`                       is Go `nil`
	// u2. SurrealDB `none` + Go       `any` type is Go `models.CustomNil{}`
	// u3. SurrealDB `none` + Go non-pointer type is Go zero value (no primitive type is supported yet, but CustomDateTime is supported)
	// u4. SurrealDB `none` + Go     pointer type is Go `nil`                     when `SELECT *` is used
	// u5. SurrealDB `none` + Go     pointer type is a pointer to a Go zero value when explicit fields are selected
	//     (e.g., you cannot unmarshal `none` into `nil` when the field is explicitly selected)

	// Future plans:
	//
	// Our plan is to fix u4 and u5 in the future, by unifying the two into:
	//
	// u4. SurrealDB `none` will unmarshal into Go `nil` if the field IS a pointer type,
	//   REGARDLESS of whether the field is explicitly selected or not

	_, err = surrealdb.Query[[]T](
		context.Background(),
		db,
		`CREATE $id CONTENT $value`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
			"value": T{
				// NullableZero:  models.CustomDateTime{Time: time.Time{}},
				NullableNil:         nil,
				OptionZero:          models.CustomDateTime{Time: time.Time{}},
				OptionZeroOmitEmpty: models.CustomDateTime{Time: time.Time{}},
				OptionZeroOmitZero:  models.CustomDateTime{Time: time.Time{}},
				OptionPtrZero:       &models.CustomDateTime{Time: time.Time{}},
				// OptionPtrNil:  nil,
				OptionPtrNilOmitEmpty: nil,
			},
		},
	)
	if err != nil {
		panic(err)
	}

	selectExplicit, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id,
		        nullable_nil,
				option_zero,
				option_zero_omitempty,
				option_zero_omitzero,
				option_ptr_zero,
				option_ptr_nil_omitempty
		FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with explicit fields")
	fmt.Println()

	for _, t := range (*selectExplicit)[0].Result {
		fmt.Printf("ID: %v\n", t.ID)
		// fmt.Printf("NullableZero: %v\n", t.NullableZero)
		fmt.Printf("NullableNil: %v\n", t.NullableNil)
		fmt.Printf("OptionZero: %v\n", t.OptionZero)
		fmt.Printf("OptionZeroOmitEmpty: %v\n", t.OptionZeroOmitEmpty)
		fmt.Printf("OptionZeroOmitZero: %v\n", t.OptionZeroOmitZero)
		fmt.Printf("OptionPtrZero: %v\n", t.OptionPtrZero)
		// fmt.Printf("OptionPtrNil: %v\n", t.OptionPtrNil)
		fmt.Printf("OptionPtrNilOmitEmpty: %v\n", t.OptionPtrNilOmitEmpty)
	}

	selectAll, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT * FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with all fields")
	fmt.Println()

	for _, t := range (*selectAll)[0].Result {
		fmt.Printf("ID: %v\n", t.ID)
		// fmt.Printf("NullableZero: %v\n", t.NullableZero)
		fmt.Printf("NullableNil: %v\n", t.NullableNil)
		fmt.Printf("OptionZero: %v\n", t.OptionZero)
		fmt.Printf("OptionZeroOmitEmpty: %v\n", t.OptionZeroOmitEmpty)
		fmt.Printf("OptionZeroOmitZero: %v\n", t.OptionZeroOmitZero)
		fmt.Printf("OptionPtrZero: %v\n", t.OptionPtrZero)
		// fmt.Printf("OptionPtrNil: %v\n", t.OptionPtrNil)
		fmt.Printf("OptionPtrNilOmitEmpty: %v\n", t.OptionPtrNilOmitEmpty)
	}

	selectExplicitMap, err := surrealdb.Query[[]map[string]any](
		context.Background(),
		db,
		`SELECT id,
		        nullable_nil,
				option_zero,
				option_zero_omitempty,
				option_zero_omitzero,
				option_ptr_zero,
				option_ptr_nil_omitempty
		FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with explicit fields into map[string]any")
	fmt.Println()

	for _, t := range (*selectExplicitMap)[0].Result {
		fmt.Printf("ID: %v\n", t["id"])
		// fmt.Printf("NullableZero: %+v\n", t["nullable_zero"])
		fmt.Printf("NullableNil: %T%+v\n", t["nullable_nil"], t["nullable_nil"])
		fmt.Printf("OptionZero: %T%+v\n", t["option_zero"], t["option_zero"])
		fmt.Printf("OptionZeroOmitEmpty: %T%+v\n", t["option_zero_omitempty"], t["option_zero_omitempty"])
		fmt.Printf("OptionZeroOmitZero: %T%+v\n", t["option_zero_omitzero"], t["option_zero_omitzero"])
		fmt.Printf("OptionPtrZero: %T%+v\n", t["option_ptr_zero"], t["option_ptr_zero"])
		// fmt.Printf("OptionPtrNil: %T%+v\n", t["option_ptr_nil"], t["option_ptr_nil"])
		fmt.Printf("OptionPtrNilOmitEmpty: %T%+v\n", t["option_ptr_nil_omitempty"], t["option_ptr_nil_omitempty"])
	}

}
Output:


# SELECT with explicit fields

ID: t:1
NullableNil: <nil>
OptionZero: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitEmpty: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitZero: {0001-01-01 00:00:00 +0000 UTC}
OptionPtrZero: 0001-01-01T00:00:00Z
OptionPtrNilOmitEmpty: 0001-01-01T00:00:00Z

# SELECT with all fields

ID: t:1
NullableNil: <nil>
OptionZero: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitEmpty: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitZero: {0001-01-01 00:00:00 +0000 UTC}
OptionPtrZero: <nil>
OptionPtrNilOmitEmpty: <nil>

# SELECT with explicit fields into map[string]any

ID: {t 1}
NullableNil: <nil><nil>
OptionZero: models.CustomNil{}
OptionZeroOmitEmpty: models.CustomNil{}
OptionZeroOmitZero: models.CustomNil{}
OptionPtrZero: models.CustomNil{}
OptionPtrNilOmitEmpty: models.CustomNil{}
Example (Null_none_customdatetime_roundtrip_surrealcbor)
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "t")
	c.UseSurrealCBOR = true

	db := c.MustNew()

	_, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`DEFINE TABLE t SCHEMAFULL;
		//  DEFINE FIELD nullable_zero ON t TYPE datetime | null;
		 DEFINE FIELD nullable_nil ON t TYPE datetime | null;
		 DEFINE FIELD option_zero ON t TYPE option<datetime>;
		 DEFINE FIELD option_zero_omitempty ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_zero ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_nil ON t TYPE option<datetime>;
		 DEFINE FIELD option_ptr_nil_omitempty ON t TYPE option<datetime>;
		`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	type T struct {
		ID *models.RecordID `json:"id,omitempty"`
		// NullableZero tests how a Zero value of CustomDateTime is marshaled into a nullable field
		//
		// This fails like this:
		//   Found NONE for field `nullable_zero`, with record `t:1`, but expected a datetime | null
		// NullableZero  models.CustomDateTime  `json:"nullable_zero"`

		// NullableNil tests how a Go pointer to nil is marshaled into a nullable field
		NullableNil *models.CustomDateTime `json:"nullable_nil"`

		OptionZero models.CustomDateTime `json:"option_zero"`

		// OptionZeroOmitEmpty tests how a Zero value of CustomDateTime is marshaled into an option field
		OptionZeroOmitEmpty models.CustomDateTime `json:"option_zero_omitempty,omitempty"`

		// OptionZeroOmitZero tests how a Zero value of CustomDateTime is marshaled into an option field
		// when the field is omitzero
		OptionZeroOmitZero models.CustomDateTime `json:"option_zero_omitzero,omitzero"`

		// OptionPtrZero tests how a Go pointer to a Zero value of CustomDateTime is marshaled
		OptionPtrZero *models.CustomDateTime `json:"option_ptr_zero"`

		// OptionNil tests how a Go pointer to nil is marshaled
		//
		// This fails like this:
		//   Found NULL for field `option_ptr_nil`, with record `t:1`, but expected a option<datetime>
		// OptionPtrNil *models.CustomDateTime `json:"option_ptr_nil"`

		// OptionPtrNilOmitEmpty tests how a Go pointer to nil is marshaled into an option field
		// when the field is omitted if empty.
		OptionPtrNilOmitEmpty *models.CustomDateTime `json:"option_ptr_nil_omitempty,omitempty"`
	}

	_, err = surrealdb.Query[[]T](
		context.Background(),
		db,
		`CREATE $id CONTENT $value`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
			"value": T{
				// NullableZero:  models.CustomDateTime{Time: time.Time{}},
				NullableNil:         nil,
				OptionZero:          models.CustomDateTime{Time: time.Time{}},
				OptionZeroOmitEmpty: models.CustomDateTime{Time: time.Time{}},
				OptionZeroOmitZero:  models.CustomDateTime{Time: time.Time{}},
				OptionPtrZero:       &models.CustomDateTime{Time: time.Time{}},
				// OptionPtrNil:  nil,
				OptionPtrNilOmitEmpty: nil,
			},
		},
	)
	if err != nil {
		panic(err)
	}

	selectExplicit, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT id,
		        nullable_nil,
				option_zero,
				option_zero_omitempty,
				option_zero_omitzero,
				option_ptr_zero,
				option_ptr_nil_omitempty
		FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with explicit fields")
	fmt.Println()

	for _, t := range (*selectExplicit)[0].Result {
		fmt.Printf("ID: %v\n", t.ID)
		// fmt.Printf("NullableZero: %v\n", t.NullableZero)
		fmt.Printf("NullableNil: %v\n", t.NullableNil)
		fmt.Printf("OptionZero: %v\n", t.OptionZero)
		fmt.Printf("OptionZeroOmitEmpty: %v\n", t.OptionZeroOmitEmpty)
		fmt.Printf("OptionZeroOmitZero: %v\n", t.OptionZeroOmitZero)
		fmt.Printf("OptionPtrZero: %v\n", t.OptionPtrZero)
		// fmt.Printf("OptionPtrNil: %v\n", t.OptionPtrNil)
		fmt.Printf("OptionPtrNilOmitEmpty: %v\n", t.OptionPtrNilOmitEmpty)
	}

	selectAll, err := surrealdb.Query[[]T](
		context.Background(),
		db,
		`SELECT * FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with all fields")
	fmt.Println()

	for _, t := range (*selectAll)[0].Result {
		fmt.Printf("ID: %v\n", t.ID)
		// fmt.Printf("NullableZero: %v\n", t.NullableZero)
		fmt.Printf("NullableNil: %v\n", t.NullableNil)
		fmt.Printf("OptionZero: %v\n", t.OptionZero)
		fmt.Printf("OptionZeroOmitEmpty: %v\n", t.OptionZeroOmitEmpty)
		fmt.Printf("OptionZeroOmitZero: %v\n", t.OptionZeroOmitZero)
		fmt.Printf("OptionPtrZero: %v\n", t.OptionPtrZero)
		// fmt.Printf("OptionPtrNil: %v\n", t.OptionPtrNil)
		fmt.Printf("OptionPtrNilOmitEmpty: %v\n", t.OptionPtrNilOmitEmpty)
	}

	selectExplicitMap, err := surrealdb.Query[[]map[string]any](
		context.Background(),
		db,
		`SELECT id,
		        nullable_nil,
				option_zero,
				option_zero_omitempty,
				option_zero_omitzero,
				option_ptr_zero,
				option_ptr_nil_omitempty
		FROM t ORDER BY id`,
		nil,
	)
	if err != nil {
		panic(err)
	}

	fmt.Println()
	fmt.Println("# SELECT with explicit fields into map[string]any")
	fmt.Println()

	for _, t := range (*selectExplicitMap)[0].Result {
		fmt.Printf("ID: %v\n", t["id"])
		// fmt.Printf("NullableZero: %+v\n", t["nullable_zero"])
		fmt.Printf("NullableNil: %T%+v\n", t["nullable_nil"], t["nullable_nil"])
		fmt.Printf("OptionZero: %T%+v\n", t["option_zero"], t["option_zero"])
		fmt.Printf("OptionZeroOmitEmpty: %T%+v\n", t["option_zero_omitempty"], t["option_zero_omitempty"])
		fmt.Printf("OptionZeroOmitZero: %T%+v\n", t["option_zero_omitzero"], t["option_zero_omitzero"])
		fmt.Printf("OptionPtrZero: %T%+v\n", t["option_ptr_zero"], t["option_ptr_zero"])
		// fmt.Printf("OptionPtrNil: %T%+v\n", t["option_ptr_nil"], t["option_ptr_nil"])
		fmt.Printf("OptionPtrNilOmitEmpty: %T%+v\n", t["option_ptr_nil_omitempty"], t["option_ptr_nil_omitempty"])
	}

}
Output:


# SELECT with explicit fields

ID: t:1
NullableNil: <nil>
OptionZero: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitEmpty: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitZero: {0001-01-01 00:00:00 +0000 UTC}
OptionPtrZero: <nil>
OptionPtrNilOmitEmpty: <nil>

# SELECT with all fields

ID: t:1
NullableNil: <nil>
OptionZero: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitEmpty: {0001-01-01 00:00:00 +0000 UTC}
OptionZeroOmitZero: {0001-01-01 00:00:00 +0000 UTC}
OptionPtrZero: <nil>
OptionPtrNilOmitEmpty: <nil>

# SELECT with explicit fields into map[string]any

ID: {t 1}
NullableNil: <nil><nil>
OptionZero: <nil><nil>
OptionZeroOmitEmpty: <nil><nil>
OptionZeroOmitZero: <nil><nil>
OptionPtrZero: <nil><nil>
OptionPtrNilOmitEmpty: <nil><nil>
Example (Return)

ExampleQueryReturn demonstrates how to use the RETURN NONE clause in a query. See https://github.com/surrealdb/surrealdb.go/issues/203 for more context.

package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	type NestedStruct struct {
		City string `json:"city"`
	}

	type Person struct {
		ID           *models.RecordID `json:"id,omitempty"`
		Name         string           `json:"name"`
		NestedMap    map[string]any   `json:"nested_map,omitempty"`
		NestedStruct `json:"nested_struct,omitempty"`
		CreatedAt    models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt    *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	insertQueryResults, err := surrealdb.Query[any](
		context.Background(),
		db,
		`INSERT INTO persons [$content] RETURN NONE`,
		map[string]any{
			"content": map[string]any{
				"id":   "yusuke",
				"name": "Yusuke",
				"nested_struct": NestedStruct{
					City: "Tokyo",
				},
				"created_at": models.CustomDateTime{
					Time: createdAt,
				},
			},
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of insert query results: %d\n", len(*insertQueryResults))
	fmt.Printf("First insert query result's status: %+s\n", (*insertQueryResults)[0].Status)
	fmt.Printf("Results contained in the first query result: %+v\n", (*insertQueryResults)[0].Result)

	selectQueryResults, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		`SELECT * FROM $id`, map[string]any{
			"id": models.NewRecordID("persons", "yusuke"),
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of select query results: %d\n", len(*selectQueryResults))
	fmt.Printf("First select query result's status: %+s\n", (*selectQueryResults)[0].Status)
	fmt.Printf("Persons contained in the first select query result: %+v\n", (*selectQueryResults)[0].Result)

}
Output:

Number of insert query results: 1
First insert query result's status: OK
Results contained in the first query result: []
Number of select query results: 1
First select query result's status: OK
Persons contained in the first select query result: [{ID:persons:yusuke Name:Yusuke NestedMap:map[] NestedStruct:{City:Tokyo} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:<nil>}]
Example (TransactionRollback)

ExampleQuery_transactionRollback demonstrates that mutations within a rolled back transaction don't persist. The CANCEL statement rolls back all changes made within the transaction.

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	config := testenv.MustNewConfig("surrealdbexamples", "transaction_rollback", "accounts")
	config.Endpoint = testenv.GetSurrealDBURL()

	db := config.MustNew()

	type Account struct {
		ID      *models.RecordID `json:"id,omitempty"`
		Name    string           `json:"name"`
		Balance float64          `json:"balance"`
	}

	ctx := context.Background()

	// First, create an initial account outside of any transaction
	initialAccount, err := surrealdb.Create[Account](ctx, db, "accounts", map[string]any{
		"name":    "Savings Account",
		"balance": 1000.00,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to create initial account: %v", err))
	}

	fmt.Printf("Initial account created: %s with balance %.2f\n", initialAccount.Name, initialAccount.Balance)

	// Now start a transaction that will be rolled back
	transactionQuery := `
		BEGIN TRANSACTION;

		-- Create a new account within the transaction
		CREATE accounts SET name = $checkingName, balance = $checkingBalance;

		-- Update the existing account within the transaction
		UPDATE $accountID SET balance = $newBalance;

		-- Create another account
		CREATE accounts SET name = $investmentName, balance = $investmentBalance;

		-- Roll back all changes made in this transaction
		CANCEL TRANSACTION;
	`

	_, err = surrealdb.Query[any](ctx, db, transactionQuery, map[string]any{
		"accountID":         initialAccount.ID,
		"checkingName":      "Checking Account",
		"checkingBalance":   500.00,
		"newBalance":        2000.00,
		"investmentName":    "Investment Account",
		"investmentBalance": 5000.00,
	})
	// When a transaction is canceled, SurrealDB returns an error
	if err != nil {
		fmt.Println("Transaction was rolled back (as expected)")
	} else {
		panic("Expected an error from canceled transaction, but got none")
	}

	// Verify that no new accounts were created
	allAccounts, err := surrealdb.Select[[]Account](ctx, db, "accounts")
	if err != nil {
		panic(fmt.Sprintf("Failed to select accounts: %v", err))
	}

	fmt.Printf("Number of accounts after rollback: %d\n", len(*allAccounts))

	// Verify that the original account balance wasn't changed
	updatedAccount, err := surrealdb.Select[Account](ctx, db, *initialAccount.ID)
	if err != nil {
		panic(fmt.Sprintf("Failed to select account: %v", err))
	}

	fmt.Printf("Original account balance after rollback: %.2f\n", updatedAccount.Balance)

	// Now demonstrate a committed transaction for comparison
	committedTransactionQuery := `
		BEGIN TRANSACTION;

		-- Create a new account within the transaction
		CREATE accounts SET name = $businessName, balance = $businessBalance;

		-- Commit the transaction
		COMMIT TRANSACTION;
	`

	_, err = surrealdb.Query[any](ctx, db, committedTransactionQuery, map[string]any{
		"businessName":    "Business Account",
		"businessBalance": 3000.00,
	})
	if err != nil {
		panic(fmt.Sprintf("Failed to execute committed transaction: %v", err))
	}

	fmt.Println("Second transaction executed and committed")

	// Verify that the new account was created
	finalAccounts, err := surrealdb.Select[[]Account](ctx, db, "accounts")
	if err != nil {
		panic(fmt.Sprintf("Failed to select accounts: %v", err))
	}

	fmt.Printf("Number of accounts after commit: %d\n", len(*finalAccounts))

}
Output:

Initial account created: Savings Account with balance 1000.00
Transaction was rolled back (as expected)
Number of accounts after rollback: 1
Original account balance after rollback: 1000.00
Second transaction executed and committed
Number of accounts after commit: 2
Example (Transaction_issue_177_commit)

See https://github.com/surrealdb/surrealdb.go/issues/177

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "t")

	var err error

	queryResults, err := surrealdb.Query[any](context.Background(), db,
		`BEGIN;
		CREATE t:s SET name = 'test1';
		CREATE t:t SET name = 'test2';
		SELECT * FROM $id;
		COMMIT;`,
		map[string]any{
			"id": models.RecordID{Table: "t", ID: "s"},
		})
	if err != nil {
		panic(err)
	}

	fmt.Printf("Status: %v\n", (*queryResults)[0].Status)

	if len(*queryResults) != 3 {
		panic(fmt.Errorf("expected 3 query results, got %d", len(*queryResults)))
	}

	var records []map[string]any
	for i, result := range *queryResults {
		if result.Status != "OK" {
			panic(fmt.Errorf("expected OK status for query result %d, got %s", i, result.Status))
		}
		if result.Result == nil {
			panic(fmt.Errorf("expected non-nil result for query result %d", i))
		}
		if record, ok := result.Result.([]any); ok && len(record) > 0 {
			records = append(records, record[0].(map[string]any))
		} else {
			panic(fmt.Errorf("expected result to be a slice of maps, got %T", result.Result))
		}
	}

	fmt.Printf("result[0].id: %v\n", records[0]["id"])
	fmt.Printf("result[0].name: %v\n", records[0]["name"])
	fmt.Printf("result[1].id: %v\n", records[1]["id"])
	fmt.Printf("result[1].name: %v\n", records[1]["name"])
	if id := records[2]["id"]; id != nil && id != (models.RecordID{Table: "t", ID: "s"}) {
		panic(fmt.Errorf("expected id to be empty for SurrealDB v3.0.0-alpha.7, or 's' for v2.3.7, got %v", id))
	}
	fmt.Printf("result[2].name: %v\n", records[2]["name"])

}
Output:

Status: OK
result[0].id: {t s}
result[0].name: test1
result[1].id: {t t}
result[1].name: test2
result[2].name: test1
Example (Transaction_issue_177_return_before_commit)

See https://github.com/surrealdb/surrealdb.go/issues/177

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "t")

	var err error

	// Note that you are returning before committing the transaction.
	// In this case, you get the uncommitted result of the CREATE,
	// which lacks the ID field becase we aren't sure if the ID is committed or not
	// at that point.
	// SurrealDB may be enhanced to handle this, but for now,
	// you should commit the transaction before returning the result.
	// See the ExampleQuery_transaction_issue_177_commit function for the correct way to do this.
	queryResults, err := surrealdb.Query[any](context.Background(), db,
		`BEGIN;
		CREATE t:s SET name = 'test';
		LET $i = SELECT * FROM $id;
		RETURN $i;
		COMMIT;`,
		map[string]any{
			"id": models.RecordID{Table: "t", ID: "s"},
		})
	if err != nil {
		panic(err)
	}

	if len(*queryResults) != 1 {
		panic(fmt.Errorf("expected 1 query result, got %d", len(*queryResults)))
	}

	rs := (*queryResults)[0].Result.([]any)
	r := rs[0].(map[string]any)

	fmt.Printf("Status: %v\n", (*queryResults)[0].Status)
	fmt.Printf("r.name: %v\n", r["name"])
	if id := r["id"]; id != nil && id != (models.RecordID{Table: "t", ID: "s"}) {
		panic(fmt.Errorf("expected id to be empty for SurrealDB v3.0.0-alpha.7, or 's' for v2.3.7, got %v", id))
	}

}
Output:

Status: OK
r.name: test
Example (Transaction_let_return)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "t")

	createQueryResults, err := surrealdb.Query[[]any](
		context.Background(),
		db,
		`BEGIN;
		 CREATE t:1 SET name = 'test';
		 LET $i = SELECT * FROM $id;
		 RETURN $i.name;
		 COMMIT
		`,
		map[string]any{
			"id": models.NewRecordID("t", 1),
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Number of query results: %d\n", len(*createQueryResults))
	fmt.Printf("First query result's status: %+s\n", (*createQueryResults)[0].Status)
	fmt.Printf("Names contained in the first query result: %+v\n", (*createQueryResults)[0].Result)

}
Output:

Number of query results: 1
First query result's status: OK
Names contained in the first query result: [test]
Example (Transaction_return)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person")

	var err error

	var a *[]surrealdb.QueryResult[bool]
	a, err = surrealdb.Query[bool](
		context.Background(),
		db,
		`BEGIN; CREATE person:1; CREATE person:2; RETURN true; COMMIT;`,
		map[string]any{},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Status: %v\n", (*a)[0].Status)
	fmt.Printf("Result: %v\n", (*a)[0].Result)

}
Output:

Status: OK
Result: true
Example (Transaction_throw)
package main

import (
	"context"
	"errors"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person")

	var (
		queryResults *[]surrealdb.QueryResult[*int]
		err          error
	)

	// Up until v0.4.3, making QueryResult[T] parameterized with anything other than `any`
	// or `string` failed with:
	//   cannot unmarshal UTF-8 text string into Go struct field
	// in case the query was executed on the database but failed with an error.
	//
	// It was due to a mismatch between the expected type and the actual type-
	// The actual query result was a string, which provides the error message sent
	// from the database, regardless of the type parameter specified to the Query function.
	//
	// Since v0.4.4, the QueryResult was enhanced to set the Error field
	// to a QueryError if the query failed, allowing the caller to handle the error.
	// In that case, the Result field will be empty(or nil if it is a pointer type),
	// and the Status field will be set to "ERR".
	//
	// It's also worth noting that the returned error from the Query function
	// will be nil if the query was executed successfully, in which case all the results
	// have no Error field set.
	//
	// If the query failed, the returned error will be a `joinError` created by the `errors.Join` function,
	// which contains all the errors that occurred during the query execution.
	// The caller can check the Error field of each QueryResult to see if the query failed,
	// or check the returned error from the Query function to see if the query failed.
	queryResults, err = surrealdb.Query[*int](
		context.Background(),
		db,
		`BEGIN; THROW "test"; RETURN 1; COMMIT;`,
		nil,
	)
	fmt.Printf("# of results: %d\n", len(*queryResults))
	fmt.Println("=== Func error ===")
	fmt.Printf("Error: %v\n", err)
	fmt.Printf("Error is RPCError: %v\n", errors.Is(err, &surrealdb.RPCError{}))
	fmt.Printf("Error is QueryError: %v\n", errors.Is(err, &surrealdb.QueryError{}))
	for i, r := range *queryResults {
		fmt.Printf("=== QueryResult[%d] ===\n", i)
		fmt.Printf("Status: %v\n", r.Status)
		fmt.Printf("Result: %v\n", r.Result)
		fmt.Printf("Error: %v\n", r.Error)
		fmt.Printf("Error is RPCError: %v\n", errors.Is(r.Error, &surrealdb.RPCError{}))
		fmt.Printf("Error is QueryError: %v\n", errors.Is(r.Error, &surrealdb.QueryError{}))
	}

}
Output:

# of results: 2
=== Func error ===
Error: An error occurred: test
The query was not executed due to a failed transaction
Error is RPCError: false
Error is QueryError: true
=== QueryResult[0] ===
Status: ERR
Result: <nil>
Error: An error occurred: test
Error is RPCError: false
Error is QueryError: true
=== QueryResult[1] ===
Status: ERR
Result: <nil>
Error: The query was not executed due to a failed transaction
Error is RPCError: false
Error is QueryError: true

func QueryRaw added in v0.3.0

func QueryRaw(ctx context.Context, db *DB, queries *[]QueryStmt) error

QueryRaw composes a query from the provided QueryStmt objects, and execute it using the query RPC method.

You may want to use Query with github.com/surrealdb/surrealdb.go/contrib/surrealql instead.

func Relate added in v0.3.0

func Relate[TResult any](ctx context.Context, db *DB, rel *Relationship) (*TResult, error)

Relate creates a relationship between two records in the table with a generated relationship ID.

The relation needs to be specified via the `Relation` field of the Relationship struct.

A relation is basically a table, so you can query it directly using SELECT if needed.

Although the Relationship struct allows you to specify the ID, it is ignored when you use Relate, and the ID is generated by SurrealDB.

In other words, Relationship.ID is meant for unmarshaling the relation from the database to the Relationship struct, in which case the ID is set to the ID of the relation record generated by SurrealDB.

In case you only care about the returned relationship's ID, use `connection.ResponseID[models.RecordID]` for the TResult type parameter.

Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person", "follow")

	type Person struct {
		ID models.RecordID `json:"id,omitempty"`
	}

	type Follow struct {
		In    *models.RecordID      `json:"in,omitempty"`
		Out   *models.RecordID      `json:"out,omitempty"`
		Since models.CustomDateTime `json:"since"`
	}

	first, err := surrealdb.Create[Person](
		context.Background(),
		db,
		"person",
		map[string]any{
			"id": models.NewRecordID("person", "first"),
		})
	if err != nil {
		panic(err)
	}

	second, err := surrealdb.Create[Person](
		context.Background(),
		db,
		"person",
		map[string]any{
			"id": models.NewRecordID("person", "second"),
		})
	if err != nil {
		panic(err)
	}

	since, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	persons, err := surrealdb.Query[[]Person](
		context.Background(),
		db,
		"SELECT * FROM person ORDER BY id.id",
		nil,
	)
	if err != nil {
		panic(err)
	}
	for _, person := range (*persons)[0].Result {
		fmt.Printf("Person: %+v\n", person)
	}

	res, relateErr := surrealdb.Relate[connection.ResponseID[models.RecordID]](
		context.Background(),
		db,
		&surrealdb.Relationship{
			// ID is currently ignored, and the relation will have a generated ID.
			// If you want to set the ID, use InsertRelation, or use
			// Query with `RELATE` statement.
			ID:       &models.RecordID{Table: "follow", ID: "first_second"},
			In:       first.ID,
			Out:      second.ID,
			Relation: "follow",
			Data: map[string]any{
				"since": models.CustomDateTime{
					Time: since,
				},
			},
		},
	)
	if relateErr != nil {
		panic(relateErr)
	}
	if res == nil {
		panic("relation response is nil")
	}
	if res.ID.ID == "first_second" {
		panic("relation ID should not be set to 'first_second'")
	}

	//nolint:lll
	/// Here's an alternative way to create a relation using a query.
	//
	// if res, err := surrealdb.Query[any](
	// 	db,
	// 	"RELATE $in->follow:first_second->$out SET since = $since",
	// 	map[string]any{
	// 		// `RELATE $in->follow->$out` with "id" below is ignored,
	// 		// and the id becomes a generated one.
	// 		// If you want to set the id, use `RELATE $in->follow:the_id->$out` like above.
	// 		// "id":    models.NewRecordID("follow", "first_second"),
	// 		"in":    first.ID,
	// 		"out":   second.ID,
	// 		"since": models.CustomDateTime{Time: since},
	// 	},
	// ); err != nil {
	// 	panic(err)
	// } else {
	// 	fmt.Printf("Relation: %+v\n", (*res)[0].Result)
	// }
	// The output will be:
	// Relation: [map[id:{Table:follow ID:first_second} in:{Table:person ID:first} out:{Table:person ID:second} since:{Time:2023-10-01 12:00:00 +0000 UTC}]]

	type PersonWithFollows struct {
		Person
		Follows []models.RecordID `json:"follows,omitempty"`
	}
	selected, err := surrealdb.Query[[]PersonWithFollows](
		context.Background(),
		db,
		"SELECT id, name, ->follow->person AS follows FROM $id",
		map[string]any{
			"id": first.ID,
		},
	)
	if err != nil {
		panic(err)
	}

	for _, person := range (*selected)[0].Result {
		fmt.Printf("PersonWithFollows: %+v\n", person)
	}

	// Note we can select the relationships themselves because
	// RELATE creates a record in the relation table.
	follows, err := surrealdb.Query[[]Follow](
		context.Background(),
		db,
		"SELECT * from follow",
		nil,
	)
	if err != nil {
		panic(err)
	}

	for _, follow := range (*follows)[0].Result {
		fmt.Printf("Follow: %+v\n", follow)
	}

}
Output:

Person: {ID:{Table:person ID:first}}
Person: {ID:{Table:person ID:second}}
PersonWithFollows: {Person:{ID:{Table:person ID:first}} Follows:[{Table:person ID:second}]}
Follow: {In:person:first Out:person:second Since:{Time:2023-10-01 12:00:00 +0000 UTC}}

func Select added in v0.3.0

func Select[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat) (*TResult, error)
Example
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "update", "person")

	type Person struct {
		ID models.RecordID `json:"id,omitempty"`
	}

	a := Person{ID: models.NewRecordID("person", "a")}
	b := Person{ID: models.NewRecordID("person", "b")}

	for _, p := range []Person{a, b} {
		created, err := surrealdb.Create[Person](
			context.Background(),
			db,
			p.ID,
			map[string]any{},
		)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Created person: %+v\n", *created)
	}

	selectedOneUsingSelect, err := surrealdb.Select[Person](
		context.Background(),
		db,
		a.ID,
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("selectedOneUsingSelect: %+v\n", *selectedOneUsingSelect)

	selectedMultiUsingSelect, err := surrealdb.Select[[]Person](
		context.Background(),
		db,
		"person",
	)
	if err != nil {
		panic(err)
	}
	for _, p := range *selectedMultiUsingSelect {
		fmt.Printf("selectedMultiUsingSelect: %+v\n", p)
	}

}
Output:

Created person: {ID:{Table:person ID:a}}
Created person: {ID:{Table:person ID:b}}
selectedOneUsingSelect: {ID:{Table:person ID:a}}
selectedMultiUsingSelect: {ID:{Table:person ID:a}}
selectedMultiUsingSelect: {ID:{Table:person ID:b}}

func Send added in v0.7.0

func Send[Result any](ctx context.Context, db *DB, res *connection.RPCResponse[Result], method string, params ...any) error

Send sends a request to the SurrealDB server.

It is a wrapper around connection.Send, which is used by various RPC methods like Query, Insert and so on.

Compared to the original connection.Send, Send is smarter about methods that are allowed to be sent. You usually want to use this function than using connection.Send directly.

This function is limited to a selected set of RPC methods listed below:

- select - create - insert - insert_relation - kill - live - merge - relate - update - upsert - patch - delete - query

The `res` needs to be of type `*connection.RPCResponse[T]`.

It returns an error in the following cases: - Error if the method is not allowed to be sent, which means that the request was not even sent. - Transport error like WebSocket message write timeout, connection closed, etc. - Unmarshal error if the response cannot be unmarshaled into the provided res parameter. - RPCError if the request was processed by SurrealDB but it failed there.

Example (Select)

Send can be used to any SurrealDB RPC method including "select".

package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

// Send can be used to any SurrealDB RPC method including "select".
func main() {
	db := testenv.MustNew("surrealdbexamples", "update", "person")

	type Person struct {
		ID models.RecordID `json:"id,omitempty"`
	}

	a := Person{ID: models.NewRecordID("person", "a")}
	b := Person{ID: models.NewRecordID("person", "b")}

	for _, p := range []Person{a, b} {
		created, err := surrealdb.Create[Person](
			context.Background(),
			db,
			p.ID,
			map[string]any{},
		)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Created person: %+v\n", *created)
	}

	var selectedUsingSendSelect connection.RPCResponse[Person]
	err := surrealdb.Send(
		context.Background(),
		db,
		&selectedUsingSendSelect,
		"select",
		a.ID,
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("selectedUsingSendSelect: %+v\n", *selectedUsingSendSelect.Result)

	var selectedMultiUsingSendSelect connection.RPCResponse[[]Person]
	err = surrealdb.Send(
		context.Background(),
		db,
		&selectedMultiUsingSendSelect,
		"select",
		"person",
	)
	if err != nil {
		panic(err)
	}
	for _, p := range *selectedMultiUsingSendSelect.Result {
		fmt.Printf("selectedMultiUsingSendSelect: %+v\n", p)
	}

	var selectedOneUsingCustomSelect *Person
	selectedOneUsingCustomSelect, err = customSelect[Person](db, a.ID)
	if err != nil {
		panic(err)
	}
	fmt.Printf("selectedOneUsingCustomSelect: %+v\n", *selectedOneUsingCustomSelect)

	var selectedMultiUsingCustomSelect *[]Person
	selectedMultiUsingCustomSelect, err = customSelect[[]Person](db, "person")
	if err != nil {
		panic(err)
	}
	for _, p := range *selectedMultiUsingCustomSelect {
		fmt.Printf("selectedMultiUsingCustomSelect: %+v\n", p)
	}

}

func customSelect[TResult any, TWhat surrealdb.TableOrRecord](db *surrealdb.DB, what TWhat) (*TResult, error) {
	var res connection.RPCResponse[TResult]

	if err := surrealdb.Send(context.Background(), db, &res, "select", what); err != nil {
		return nil, err
	}

	return res.Result, nil
}
Output:

Created person: {ID:{Table:person ID:a}}
Created person: {ID:{Table:person ID:b}}
selectedUsingSendSelect: {ID:{Table:person ID:a}}
selectedMultiUsingSendSelect: {ID:{Table:person ID:a}}
selectedMultiUsingSendSelect: {ID:{Table:person ID:b}}
selectedOneUsingCustomSelect: {ID:{Table:person ID:a}}
selectedMultiUsingCustomSelect: {ID:{Table:person ID:a}}
selectedMultiUsingCustomSelect: {ID:{Table:person ID:b}}

func Update added in v0.3.0

func Update[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat, data any) (*TResult, error)

Update a table or record in the database like a PUT request.

Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "update", "persons")

	type NestedStruct struct {
		City string `json:"city"`
	}

	type Person struct {
		ID           *models.RecordID `json:"id,omitempty"`
		Name         string           `json:"name"`
		NestedMap    map[string]any   `json:"nested_map,omitempty"`
		NestedStruct `json:"nested_struct,omitempty"`
		CreatedAt    models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt    *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	recordID := models.NewRecordID("persons", "yusuke")
	created, err := surrealdb.Create[Person](context.Background(), db, recordID, map[string]any{
		"name": "Yusuke",
		"nested_struct": NestedStruct{
			City: "Tokyo",
		},
		"created_at": models.CustomDateTime{
			Time: createdAt,
		},
	})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Created persons: %+v\n", *created)

	updatedAt, err := time.Parse(time.RFC3339, "2023-10-02T12:00:00Z")
	if err != nil {
		panic(err)
	}

	updated, err := surrealdb.Update[Person](context.Background(), db, recordID, map[string]any{
		"name": "Yusuke",
		"nested_map": map[string]any{
			"key1": "value1",
		},
		"nested_struct": NestedStruct{
			City: "Kagawa",
		},
		"updated_at": models.CustomDateTime{
			Time: updatedAt,
		},
	})
	if err != nil {
		panic(err)
	}

	fmt.Printf("Updated persons: %+v\n", *updated)

}
Output:

Created persons: {ID:persons:yusuke Name:Yusuke NestedMap:map[] NestedStruct:{City:Tokyo} CreatedAt:{Time:2023-10-01 12:00:00 +0000 UTC} UpdatedAt:<nil>}
Updated persons: {ID:persons:yusuke Name:Yusuke NestedMap:map[key1:value1] NestedStruct:{City:Kagawa} CreatedAt:{Time:0001-01-01 00:00:00 +0000 UTC} UpdatedAt:2023-10-02T12:00:00Z}

func Upsert added in v0.3.0

func Upsert[TResult any, TWhat TableOrRecord](ctx context.Context, db *DB, what TWhat, data any) (*TResult, error)
Example
package main

import (
	"context"
	"fmt"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "persons")

	type Person struct {
		ID   *models.RecordID `json:"id,omitempty"`
		Name string           `json:"name"`
		// Note that you must use CustomDateTime instead of time.Time.
		// See
		CreatedAt models.CustomDateTime  `json:"created_at,omitempty"`
		UpdatedAt *models.CustomDateTime `json:"updated_at,omitempty"`
	}

	createdAt, err := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
	if err != nil {
		panic(err)
	}

	inserted, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.NewRecordID("persons", "yusuke"),
		map[string]any{
			"name":       "Yusuke",
			"created_at": createdAt,
		})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Insert via upsert result: %v\n", *inserted)

	updated, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.NewRecordID("persons", "yusuke"),
		map[string]any{
			"name": "Yusuke Updated",
			// because the upsert RPC is like UPSERT ~ CONTENT rather than UPSERT ~ MERGE,
			// the created_at field becomes None, which results in the returned created_at field being zero value.
			"updated_at": createdAt,
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Update via upsert result: %v\n", *updated)

	udpatedAt, err := time.Parse(time.RFC3339, "2023-10-02T12:00:00Z")
	if err != nil {
		panic(err)
	}
	updatedFurther, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.NewRecordID("persons", "yusuke"),
		map[string]any{
			"name":       "Yusuke Updated Further",
			"created_at": createdAt,
			"updated_at": models.CustomDateTime{
				Time: udpatedAt,
			},
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Update further via upsert result: %v\n", *updatedFurther)

	_, err = surrealdb.Upsert[struct{}](
		context.Background(),
		db,
		models.NewRecordID("persons", "yusuke"),
		map[string]any{
			"name": "Yusuke Updated Last",
		},
	)
	if err != nil {
		panic(err)
	}

	selected, err := surrealdb.Select[Person](
		context.Background(),
		db,
		models.NewRecordID("persons", "yusuke"),
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Selected person: %v\n", *selected)

}
Output:

Insert via upsert result: {persons:yusuke Yusuke {2023-10-01 12:00:00 +0000 UTC} <nil>}
Update via upsert result: {persons:yusuke Yusuke Updated {0001-01-01 00:00:00 +0000 UTC} 2023-10-01T12:00:00Z}
Update further via upsert result: {persons:yusuke Yusuke Updated Further {2023-10-01 12:00:00 +0000 UTC} 2023-10-02T12:00:00Z}
Selected person: {persons:yusuke Yusuke Updated Last {0001-01-01 00:00:00 +0000 UTC} <nil>}
Example (Rpc_error)
package main

import (
	"context"
	"errors"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	db := testenv.MustNew("surrealdbexamples", "query", "person")

	type Person struct {
		Name string `json:"name"`
	}

	// For this example, we will define a SCHEMAFUL table
	// with a name field that is a string.
	// Trying to set the name field to a number
	// will result in an error from the database.

	if _, err := surrealdb.Query[any](
		context.Background(),
		db,
		`DEFINE TABLE person SCHEMAFUL;
		 DEFINE FIELD name ON person TYPE string;`,
		nil,
	); err != nil {
		panic(err)
	}

	// This will fail because the record ID is not valid.
	_, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.Table("person"),
		map[string]any{
			"id": models.NewRecordID("person", "a"),
			// Unlike ExampleUpsert_unmarshal_error,
			// this will fail on the database side
			// because the name field is defined as a string,
			// and we are trying to set it to a number.
			"name": 123,
		},
	)
	if err != nil {
		switch err.Error() {
		// As of v3.0.0-alpha.7
		case "There was a problem with the database: Couldn't coerce value for field `name` of `person:a`: Expected `string` but found `123`":
			fmt.Println("Encountered expected error for either v3.0.0-alpha.7 or v2.3.7")
			// As of v2.3.7
		case "There was a problem with the database: Found 123 for field `name`, with record `person:a`, but expected a string":
			fmt.Println("Encountered expected error for either v3.0.0-alpha.7 or v2.3.7")
		default:
			fmt.Printf("Unknown Error: %v\n", err)
		}
		fmt.Printf("Error is RPCError: %v\n", errors.Is(err, &surrealdb.RPCError{}))
	}

}
Output:

Encountered expected error for either v3.0.0-alpha.7 or v2.3.7
Error is RPCError: true
Example (Unmarshal_error_fxamackercbor)
package main

import (
	"context"
	"errors"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "person")
	c.UseSurrealCBOR = false

	db := c.MustNew()

	type Person struct {
		Name string `json:"name"`
	}

	// This will fail because the record ID is not valid.
	_, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.Table("person"),
		map[string]any{
			// We are trying to set the name field to a number,
			// which is OK from the database's perspective,
			// because the table is schemaless for this example.
			//
			// However, we are trying to unmarshal the result into a struct
			// that expects the name field to be a string,
			// which will fail when the result is unmarshaled.
			"name": 123,
		},
	)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		fmt.Printf("Error is RPCError: %v\n", errors.Is(err, &surrealdb.RPCError{}))
	}

}
Output:

Error: Send: error unmarshaling result: cbor: cannot unmarshal array into Go value of type surrealdb_test.Person (cannot decode CBOR array to struct without toarray option)
Error is RPCError: false
Example (Unmarshal_error_surrealcbor)

ExampleUpsert_unmarshal_error_surrealcbor demonstrates that surrealcbor fails unmarshaling CBOR array into Go struct with similar but different error message compared to fxamacker/cbor

package main

import (
	"context"
	"errors"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/models"
)

func main() {
	c := testenv.MustNewConfig("example", "query", "person")
	c.UseSurrealCBOR = true

	db := c.MustNew()

	type Person struct {
		Name string `json:"name"`
	}

	// This will fail because the record ID is not valid.
	_, err := surrealdb.Upsert[Person](
		context.Background(),
		db,
		models.Table("person"),
		map[string]any{
			// We are trying to set the name field to a number,
			// which is OK from the database's perspective,
			// because the table is schemaless for this example.
			//
			// However, we are trying to unmarshal the result into a struct
			// that expects the name field to be a string,
			// which will fail when the result is unmarshaled.
			"name": 123,
		},
	)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		fmt.Printf("Error is RPCError: %v\n", errors.Is(err, &surrealdb.RPCError{}))
	}

}
Output:

Error: Send: error unmarshaling result: cannot decode array into surrealdb_test.Person
Error is RPCError: false

Types

type Auth added in v0.3.0

type Auth struct {
	Namespace string `json:"NS,omitempty"`
	Database  string `json:"DB,omitempty"`
	Scope     string `json:"SC,omitempty"`
	Access    string `json:"AC,omitempty"`
	Username  string `json:"user,omitempty"`
	Password  string `json:"pass,omitempty"`
}

Auth is a struct that holds surrealdb auth data for login.

type DB

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

DB is a client for the SurrealDB database that holds the connection.

Example (Record_user_auth_struct)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	ns := "surrealdbexamples"
	db := testenv.MustNew(ns, "record_auth_demo", "user")

	setupQuery := `
		-- Define the user table with schema
		DEFINE TABLE user SCHEMAFULL
			PERMISSIONS
				FOR select, update, delete WHERE id = $auth.id;

		-- Define fields
		DEFINE FIELD name ON user TYPE string;
		DEFINE FIELD password ON user TYPE string;

		-- Define unique index on email
		REMOVE INDEX IF EXISTS name ON user;
		DEFINE INDEX name ON user FIELDS name UNIQUE;

		-- Define access method for record authentication
		REMOVE ACCESS IF EXISTS user ON DATABASE;
		DEFINE ACCESS user ON DATABASE TYPE RECORD
			SIGNIN (
				SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(password, $pass)
			)
			SIGNUP (
				CREATE user CONTENT {
					name: $user,
					password: crypto::argon2::generate($pass)
				}
			);
	`

	if _, err := surrealdb.Query[any](context.Background(), db, setupQuery, nil); err != nil {
		panic(err)
	}

	fmt.Println("Database schema setup complete")

	// Refer to the next example, `ExampleDB_record_user_custom_struct`,
	// when you need to use fields other than `user` and `pass` in the query specified for SIGNUP.
	_, err := db.SignUp(context.Background(), &surrealdb.Auth{
		Namespace: ns,
		Database:  "record_auth_demo",
		Access:    "user",
		Username:  "yusuke",
		Password:  "VerySecurePassword123!",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("User signed up successfully")

	// Refer to the next example, `ExampleDB_record_user_custom_struct`,
	// when you need to use fields other than `user` and `pass` in the query specified for SIGNIN.
	//
	// For example, you might want to use `email` and `password` instead of `user` and `pass`.
	// In that case, you need to something that encodes to a cbor map containing those keys.
	_, err = db.SignIn(context.Background(), &surrealdb.Auth{
		Namespace: ns,
		Database:  "record_auth_demo",
		Access:    "user",
		Username:  "yusuke",
		Password:  "VerySecurePassword123!",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("User signed in successfully")

	info, err := db.Info(context.Background())
	if err != nil {
		panic(err)
	}
	fmt.Printf("Authenticated user name: %v\n", info["name"])

}
Output:

Database schema setup complete
User signed up successfully
User signed in successfully
Authenticated user name: yusuke
Example (Record_user_custom_struct)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	ns := "surrealdbexamples"
	db := testenv.MustNew(ns, "record_user_custom", "user")

	setupQuery := `
		-- Define the user table with schema
		DEFINE TABLE user SCHEMAFULL
			PERMISSIONS
				FOR select, update, delete WHERE id = $auth.id;

		-- Define fields
		DEFINE FIELD name ON user TYPE string;
		DEFINE FIELD email ON user TYPE string;
		DEFINE FIELD password ON user TYPE string;

		-- Define unique index on email
		REMOVE INDEX IF EXISTS email ON user;
		DEFINE INDEX email ON user FIELDS email UNIQUE;

		-- Define access method for record authentication
		REMOVE ACCESS IF EXISTS user ON DATABASE;
		DEFINE ACCESS user ON DATABASE TYPE RECORD
			SIGNIN (
				SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)
			)
			SIGNUP (
				CREATE user CONTENT {
					name: $name,
					email: $email,
					password: crypto::argon2::generate($password)
				}
			);
	`

	if _, err := surrealdb.Query[any](context.Background(), db, setupQuery, nil); err != nil {
		panic(err)
	}

	fmt.Println("Database schema setup complete")

	type User struct {
		Namespace string `json:"NS"`
		Database  string `json:"DB"`
		Access    string `json:"AC"`
		Name      string `json:"name"`
		Password  string `json:"password"`
		Email     string `json:"email"`
	}

	type LoginRequest struct {
		Namespace string `json:"NS"`
		Database  string `json:"DB"`
		Access    string `json:"AC"`
		Email     string `json:"email"`
		Password  string `json:"password"`
	}

	_, err := db.SignUp(context.Background(), &User{
		// Corresponds to the SurrealDB namespace
		Namespace: ns,
		// Corresponds to the SurrealDB database
		Database: "record_user_custom",
		// Corresponds to `user` in `DEFINE ACCESS USER ON ...`
		Access: "user",
		// Corresponds to the $name in the SIGNUP query and `name` in `DEFINE FIELD name ON user`
		Name: "yusuke",
		// Corresponds to the $password in the SIGNUP query and `password` in `DEFINE FIELD password ON user`
		Password: "VerySecurePassword123!",
		// Corresponds to the $email in the SIGNUP query and `email` in `DEFINE FIELD email ON user`
		Email: "yusuke@example.com",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("User signed up successfully")

	_, err = db.SignIn(context.Background(), &LoginRequest{
		Namespace: ns,
		Database:  "record_user_custom",
		Access:    "user",
		// Corresponds to the $email in the SIGNIN query and `email` in `DEFINE FIELD email ON user`
		Email: "yusuke@example.com",
		// Corresponds to the $password in the SIGNIN query and `password` in `DEFINE FIELD password ON user`
		Password: "VerySecurePassword123!",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("User signed in successfully")

	info, err := db.Info(context.Background())
	if err != nil {
		panic(err)
	}
	fmt.Printf("Authenticated user name: %v\n", info["name"])

}
Output:

Database schema setup complete
User signed up successfully
User signed in successfully
Authenticated user name: yusuke
Example (Signin_failure)
package main

import (
	"context"
	"fmt"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	db, err := surrealdb.FromEndpointURLString(
		context.Background(),
		testenv.GetSurrealDBWSURL(),
	)
	if err != nil {
		panic(err)
	}

	// Attempt to sign in without setting namespace or database
	// This should fail with an error, whose message will depend on the connection type.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "invalid",
	})
	switch err.Error() {
	case "namespace or database or both are not set":
		// In case the connection is over HTTP, this error is expected
	case "There was a problem with the database: There was a problem with authentication":
		// In case the connection is over WebSocket, this error is expected
	default:
		panic(fmt.Sprintf("Unexpected error: %v", err))
	}

	err = db.Use(context.Background(), "testNS", "testDB")
	if err != nil {
		fmt.Println("Use error:", err)
	}

	// Even though the ns/db is set, the SignIn should still fail
	// when the credentials are invalid.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "invalid",
	})
	fmt.Println("SignIn error:", err)

	// Now let's try with the correct credentials
	// This should succeed if the database is set up correctly.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "root",
	})
	if err != nil {
		panic(fmt.Sprintf("SignIn failed: %v", err))
	}

	if err := db.Close(context.Background()); err != nil {
		panic(fmt.Sprintf("Failed to close the database connection: %v", err))
	}

}
Output:

SignIn error: There was a problem with the database: There was a problem with authentication

func Connect deprecated added in v0.6.0

func Connect(ctx context.Context, connectionURL string) (*DB, error)

Deprecated: Use FromEndpointURLString instead.

func FromConnection added in v0.7.0

func FromConnection(ctx context.Context, conn connection.Connection) (*DB, error)

FromConnection creates a new SurrealDB client using the provided connection.

Note that this function calls `conn.Connect(ctx)` for you, so you don't need to call it manually.

Example (AlternativeCBORImpl_surrealCBOR)

FromConnection can take any connection.Connection implementation with a custom connection.Config that can be used to specify a CBOR marshaler and unmarshaler. This SDK has two built-in CBOR implementations: fxamacker/cbor-based one and the newer surrealcbor. surrealcbor is a more efficient and feature-rich implementation that is recommended for new projects.

conf := connection.NewConfig(testenv.MustParseSurrealDBWSURL())
// To enable surrealcbor, instantiate the codec
// and set it as the marshaler and unmarshaler.
codec := surrealcbor.New()
conf.Marshaler = codec
conf.Unmarshaler = codec
conn := gws.New(conf)
db, err := surrealdb.FromConnection(context.Background(), conn)
if err != nil {
	panic(err)
}

db, err = testenv.Init(db, "surrealdbexamples", "surrealcbor", "user")
if err != nil {
	panic(err)
}

// Define a sample struct
type User struct {
	ID    *models.RecordID `json:"id,omitempty"`
	Name  string           `json:"name"`
	Email string           `json:"email"`
	// Note that this had to be `CreatedAt models.CustomDateTime`
	// with the previous fxamacker/cbor-based implementation.
	CreatedAt time.Time `json:"created_at"`
}

// Create a user
createdAt, _ := time.Parse(time.RFC3339, "2023-10-01T12:00:00Z")
user := User{
	Name:      "Alice",
	Email:     "alice@example.com",
	CreatedAt: createdAt,
}

// Insert the user
created, err := surrealdb.Insert[User](context.Background(), db, "user", user)
if err != nil {
	panic(err)
}

if created != nil && len(*created) > 0 {
	fmt.Printf("Created user: %s with email: %s\n", (*created)[0].Name, (*created)[0].Email)
}
Output:

Created user: Alice with email: alice@example.com
Example (AlternativeWebSocketLibrary_gws)

FromConnection can take any connection.Connection implementation, including gws.Connection which is based on https://github.com/lxzan/gws.

package main

import (
	"context"
	"fmt"
	"net/url"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/connection/gws"
)

func main() {
	u, err := url.ParseRequestURI(testenv.GetSurrealDBWSURL())
	if err != nil {
		panic(fmt.Sprintf("Failed to parse URL: %v", err))
	}

	conf := connection.NewConfig(u)
	conf.Logger = nil // Disable logging for this example

	conn := gws.New(conf)

	db, err := surrealdb.FromConnection(context.Background(), conn)
	fmt.Println("FromConnection error:", err)

	// Attempt to sign in without setting namespace or database
	// This should fail with an error, whose message will depend on the connection type.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "invalid",
	})
	fmt.Println("SignIn error:", err)

	err = db.Use(context.Background(), "testNS", "testDB")
	fmt.Println("Use error:", err)

	// Even though the ns/db is set, the SignIn should still fail
	// when the credentials are invalid.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "invalid",
	})
	fmt.Println("SignIn error:", err)

	// Now let's try with the correct credentials
	// This should succeed if the database is set up correctly.
	_, err = db.SignIn(context.Background(), surrealdb.Auth{
		Username: "root",
		Password: "root",
	})
	fmt.Println("SignIn error:", err)

	err = db.Close(context.Background())
	fmt.Println("Close error:", err)

}
Output:

FromConnection error: <nil>
SignIn error: There was a problem with the database: There was a problem with authentication
Use error: <nil>
SignIn error: There was a problem with the database: There was a problem with authentication
SignIn error: <nil>
Close error: <nil>
Example (CborUnmarshaler_decOptions_customSmallLimit)

ExampleFromConnection_cborUnmarshaler_decOptions_customSmallLimit demonstrates what happens when a custom MaxArrayElements limit is set too low and the actual data exceeds that limit. The unmarshal operation fails with a clear error message.

// Parse the SurrealDB WebSocket URL
u, err := url.ParseRequestURI(testenv.GetSurrealDBWSURL())
if err != nil {
	panic(fmt.Sprintf("Failed to parse URL: %v", err))
}

// First, create the record using default connection settings
{
	conf := connection.NewConfig(u)
	conf.Logger = nil
	conn := gws.New(conf)

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	db, err := surrealdb.FromConnection(ctx, conn)
	if err != nil {
		panic(fmt.Sprintf("Failed to connect: %v", err))
	}
	defer db.Close(context.Background())

	err = db.Use(ctx, "example", "test")
	if err != nil {
		panic(fmt.Sprintf("Failed to use namespace/database: %v", err))
	}

	_, err = db.SignIn(ctx, surrealdb.Auth{
		Username: "root",
		Password: "root",
	})
	if err != nil {
		panic(fmt.Sprintf("SignIn failed: %v", err))
	}

	// Setup table and ensure it's clean before test
	tableName := "test_small_limit"
	setupTable(db, tableName)

	createRecords(db, tableName, 20)
}

// Now try to retrieve with a connection that has a small array limit
{
	conf := connection.NewConfig(u)
	// Use TestLogHandler to see unmarshal errors but ignore debug and close errors
	handler := testenv.NewTestLogHandlerWithOptions(
		testenv.WithIgnoreErrorPrefixes("failed to close"),
		testenv.WithIgnoreDebug(),
	)
	conf.Logger = logger.New(handler)
	// Set a custom small limit that will be exceeded
	// Note: fxamacker/cbor requires MaxArrayElements to be at least 16
	conf.Unmarshaler = &models.CborUnmarshaler{
		DecOptions: cbor.DecOptions{
			MaxArrayElements: 16, // Set to minimum allowed value
		},
	}
	conn := gws.New(conf)

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	db, err := surrealdb.FromConnection(ctx, conn)
	if err != nil {
		panic(fmt.Sprintf("Failed to connect: %v", err))
	}
	defer db.Close(context.Background())

	err = db.Use(ctx, "example", "test")
	if err != nil {
		panic(fmt.Sprintf("Failed to use namespace/database: %v", err))
	}

	_, err = db.SignIn(ctx, surrealdb.Auth{
		Username: "root",
		Password: "root",
	})
	if err != nil {
		panic(fmt.Sprintf("SignIn failed: %v", err))
	}

	// This should fail due to array limit
	tableName := "test_small_limit"
	selectRecords(db, tableName)
}
Output:

Table test_small_limit cleaned up
Successfully created record with 20 items
[0] ERROR: Failed to unmarshal response error=cbor: exceeded max number of elements 16 for CBOR array
Error retrieving record: context deadline exceeded
Example (CborUnmarshaler_decOptions_defaultLimit)

ExampleFromConnection_cborUnmarshaler_decOptions_defaultLimit demonstrates that the default CBOR decoder configuration works fine with small arrays that are well within the default limit of 131,072 elements.

package main

import (
	"context"
	"fmt"
	"net/url"
	"time"

	surrealdb "github.com/surrealdb/surrealdb.go"
	"github.com/surrealdb/surrealdb.go/contrib/testenv"
	"github.com/surrealdb/surrealdb.go/pkg/connection"
	"github.com/surrealdb/surrealdb.go/pkg/connection/gorillaws"
)

func main() {
	// Parse the SurrealDB WebSocket URL
	u, err := url.ParseRequestURI(testenv.GetSurrealDBWSURL())
	if err != nil {
		panic(fmt.Sprintf("Failed to parse URL: %v", err))
	}

	// Setup connection with default configuration
	conf := connection.NewConfig(u)
	conf.Logger = nil
	conn := gorillaws.New(conf)

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	db, err := surrealdb.FromConnection(ctx, conn)
	if err != nil {
		panic(fmt.Sprintf("Failed to connect: %v", err))
	}
	defer db.Close(context.Background())

	err = db.Use(ctx, "example", "test")
	if err != nil {
		panic(fmt.Sprintf("Failed to use namespace/database: %v", err))
	}

	_, err = db.SignIn(ctx, surrealdb.Auth{
		Username: "root",
		Password: "root",
	})
	if err != nil {
		panic(fmt.Sprintf("SignIn failed: %v", err))
	}

	// Setup table and ensure it's clean before test
	tableName := "test_default_limit"
	setupTable(db, tableName)

	// Default settings work with small arrays
	createRecords(db, tableName, 10)
	selectRecords(db, tableName)

}

// setupTable prepares a clean table for testing by deleting any existing records
func setupTable(db *surrealdb.DB, tableName string) {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	_, _ = surrealdb.Query[any](ctx, db, fmt.Sprintf("DELETE %s", tableName), nil)
	fmt.Printf("Table %s cleaned up\n", tableName)
}

// createRecords creates a test record in the specified table
func createRecords(db *surrealdb.DB, tableName string, arraySize int) {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	items := make([]string, arraySize)
	for i := range arraySize {
		items[i] = fmt.Sprintf("item_%d", i)
	}

	_, err := surrealdb.Query[any](ctx, db, fmt.Sprintf("CREATE %s SET items = $items", tableName), map[string]any{
		"items": items,
	})

	if err != nil {
		fmt.Printf("Error creating record: %v\n", err)
	} else {
		fmt.Printf("Successfully created record with %d items\n", arraySize)
	}
}

func selectRecords(db *surrealdb.DB, tableName string) {
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	type TestRecord struct {
		ID    any      `json:"id"`
		Items []string `json:"items"`
	}

	result, err := surrealdb.Query[[]TestRecord](ctx, db, fmt.Sprintf("SELECT * FROM %s", tableName), nil)

	if err != nil {

		fmt.Printf("Error retrieving record: %v\n", err)
		return
	}

	if result != nil && len(*result) > 0 && len((*result)[0].Result) > 0 {
		recordCount := len((*result)[0].Result)
		if recordCount > 0 && (*result)[0].Result[0].Items != nil {
			fmt.Printf("Successfully retrieved record with %d items\n", len((*result)[0].Result[0].Items))
		}
	}
}
Output:

Table test_default_limit cleaned up
Successfully created record with 10 items
Successfully retrieved record with 10 items

func FromEndpointURLString added in v0.7.0

func FromEndpointURLString(ctx context.Context, connectionURL string) (*DB, error)

FromEndpointURLString creates a new SurrealDB client and connects to the database.

This function incurs a network call (currently HTTP request) to the SurrealDB server to check the health of the connection in case of HTTP, or to establish a WebSocket connection in case of WebSocket.

The provided `ctx` is used to cancel the connection attempt if needed, so that you control how long you want to block in case the network is not reliable or any other issues like OS network stack issues/settings/etc.

Connection Engines

There are 2 different connection engines you can use to connect to SurrealDb backend. You can do so via Websocket or through HTTP connections

Via WebSocket

WebSocket is required when using live queries.

db, err := surrealdb.FromEndpointURLString(ctx, "ws://localhost:8000")

or for a secure connection

db, err := surrealdb.FromEndpointURLString(ctx, "wss://localhost:8000")

Via HTTP

There are some functions that are not available on RPC when using HTTP but on WebSocket.

All these except the "live" endpoint are effectively implemented in the HTTP library and provides the same result as though it is natively available on HTTP.

db, err := surrealdb.FromEndpointURLString(ctx, "http://localhost:8000")

or for a secure connection

db, err := surrealdb.FromEndpointURLString(ctx, "https://localhost:8000")

func New deprecated

func New(connectionURL string) (*DB, error)

New creates a new SurrealDB client.

Deprecated: New is deprecated. Use FromEndpointURLString instead.

func (*DB) Authenticate

func (db *DB) Authenticate(ctx context.Context, token string) error

func (*DB) Close

func (db *DB) Close(ctx context.Context) error

Close closes the underlying WebSocket connection.

func (*DB) CloseLiveNotifications added in v0.10.0

func (db *DB) CloseLiveNotifications(liveQueryID string) error

func (*DB) Info

func (db *DB) Info(ctx context.Context) (map[string]any, error)

func (*DB) Invalidate

func (db *DB) Invalidate(ctx context.Context) error

func (*DB) Let

func (db *DB) Let(ctx context.Context, key string, val any) error

func (*DB) LiveNotifications added in v0.3.0

func (db *DB) LiveNotifications(liveQueryID string) (chan connection.Notification, error)

func (*DB) SignIn added in v0.3.0

func (db *DB) SignIn(ctx context.Context, authData any) (string, error)

SignIn signs in an existing user.

The authData parameter can be either:

  • An Auth struct
  • A map[string]any with keys like: "namespace", "database", "scope", "user", "pass"

Example with struct:

db.SignIn(Auth{
  Namespace: "app",
  Database: "app",
  Access: "user",
  Username: "yusuke",
  Password: "VerySecurePassword123!",
})

Example with map:

db.SignIn(map[string]any{
  "NS": "app",
  "DB": "app",
  "AC": "user",
  "user": "yusuke",
  "pass": "VerySecurePassword123!",
})

func (*DB) SignUp added in v0.3.0

func (db *DB) SignUp(ctx context.Context, authData any) (string, error)

SignUp signs up a new user.

The authData parameter can be either:

  • An Auth struct
  • A map[string]any with keys like: "namespace", "database", "scope", "user", "pass"

Example with struct:

db.SignUp(Auth{
  Namespace: "app",
  Database: "app",
  Access: "user",
  Username: "yusuke",
  Password: "VerySecurePassword123!",
})

Example with map:

db.SignUp(map[string]any{
  "NS": "app",
  "DB": "app",
  "AC": "user",
  "user": "yusuke",
  "pass": "VerySecurePassword123!",
})

func (*DB) Unset added in v0.3.0

func (db *DB) Unset(ctx context.Context, key string) error

func (*DB) Use

func (db *DB) Use(ctx context.Context, ns, database string) error

Use is a method to select the namespace and table to use.

func (*DB) Version added in v0.3.0

func (db *DB) Version(ctx context.Context) (*VersionData, error)
Example
package main

import (
	"context"
	"fmt"

	"github.com/surrealdb/surrealdb.go/contrib/testenv"
)

func main() {
	ws := testenv.MustNew("surrealdbexamples", "version")
	v, err := ws.Version(context.Background())
	if err != nil {
		panic(err)
	}
	fmt.Printf("VersionData (WebSocket): %+v\n", v)

	http := testenv.MustNew("surrealdbexamples", "version")
	v, err = http.Version(context.Background())
	if err != nil {
		panic(err)
	}
	fmt.Printf("VersionData (HTTP): %+v\n", v)

	// You get something like below depending on your SurrealDB version:
	//
	// VersionData (WebSocket): &{Version:2.3.7 Build: Timestamp:}
	// VersionData (HTTP): &{Version:2.3.7 Build: Timestamp:}
}

func (*DB) WithContext added in v0.3.0

func (db *DB) WithContext(ctx context.Context) *DB

WithContext Deprecated: WithContext is deprecated and does nothing. Use context parameters in individual method calls instead.

type Obj deprecated added in v0.3.0

type Obj map[any]any

Deprecated: Use map[string]any instead

type PatchData added in v0.3.0

type PatchData struct {
	Op    string `json:"op"`
	Path  string `json:"path"`
	Value any    `json:"value"`
}

Patch represents a patch object set to MODIFY a record

type QueryError added in v0.5.0

type QueryError struct {
	Message string
}

QueryError represents an error that occurred during a query execution.

The caller can type-assert the return errror to QueryError to see if the error is a query error or not.

func (*QueryError) Error added in v0.5.0

func (e *QueryError) Error() string

func (*QueryError) Is added in v0.5.0

func (e *QueryError) Is(target error) bool

type QueryResult added in v0.3.0

type QueryResult[T any] struct {
	Status string      `json:"status"`
	Time   string      `json:"time"`
	Result T           `json:"result"`
	Error  *QueryError `json:"-"`
}

QueryResult is a struct that represents one of the results of a SurrealDB query RPC method call, made via Query, for example.

type QueryStmt added in v0.3.0

type QueryStmt struct {
	SQL    string
	Vars   map[string]any
	Result QueryResult[cbor.RawMessage]
	// contains filtered or unexported fields
}

func (*QueryStmt) GetResult added in v0.3.0

func (q *QueryStmt) GetResult(dest any) error

type RPCError

type RPCError = connection.RPCError

type Relationship added in v0.3.0

type Relationship struct {
	ID       *models.RecordID `json:"id"`
	In       models.RecordID  `json:"in"`
	Out      models.RecordID  `json:"out"`
	Relation models.Table     `json:"relation"`
	Data     map[string]any   `json:"data"`
}

type Result deprecated added in v0.3.0

type Result[T any] struct {
	T any
}

Deprecated: Use [RPCResponse] instead.

type TableOrRecord added in v0.3.0

type TableOrRecord interface {
	string | models.Table | models.RecordID | []models.Table | []models.RecordID
}

type VersionData added in v0.3.0

type VersionData struct {
	Version   string `json:"version"`
	Build     string `json:"build"`
	Timestamp string `json:"timestamp"`
}

Directories

Path Synopsis
Package contrib provides additional functionality and utilities for the SurrealDB Go SDK.
Package contrib provides additional functionality and utilities for the SurrealDB Go SDK.
surrealql
Package surrealql provides a type-safe query builder for SurrealDB's SurrealQL language.
Package surrealql provides a type-safe query builder for SurrealDB's SurrealQL language.
testenv
Package testenv provides utilities for testing the SurrealDB Go SDK and SurrealDB.
Package testenv provides utilities for testing the SurrealDB Go SDK and SurrealDB.
cmd
internal
fakesdb
Package fakesdb provides a fake SurrealDB WebSocket server for testing purposes.
Package fakesdb provides a fake SurrealDB WebSocket server for testing purposes.
pkg
Package surrealcbor provides CBOR (Concise Binary Object Representation) encoding and decoding for SurrealDB.
Package surrealcbor provides CBOR (Concise Binary Object Representation) encoding and decoding for SurrealDB.

Jump to

Keyboard shortcuts

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