gel

package module
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2025 License: Apache-2.0 Imports: 9 Imported by: 1

README

The Go driver for Gel

Build Status Join GitHub discussions

Installation

In your module directory, run the following command.

$ go get github.com/geldata/gel-go

Basic Usage

Follow the Gel tutorial to get Gel installed and minimally configured.

package main

import (
	"context"
	"fmt"
	"log"

	gel "github.com/geldata/gel-go"
	"github.com/geldata/gel-go/gelcfg"
)

func main() {
	ctx := context.Background()
	client, err := gel.CreateClient(gelcfg.Options{})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	var result string
	err = client.QuerySingle(ctx, "SELECT 'hello Gel!'", &result)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(result)
}

Development

A local installation of Gel is required to run tests. Download Gel from here or build it manually.

To run the test suite run make test. To run lints make lint.

License

gel-go is developed and distributed under the Apache 2.0 license.

Documentation

Overview

Package gel is the official Go driver for Gel. Additionally, github.com/geldata/gel-go/cmd/edgeql-go is a code generator that generates go functions from edgeql files.

See Client for an example of typical usage.

We recommend using environment variables for connection parameters. See the client connection docs for more information.

Errors

gel never returns underlying errors directly. If you are checking for things like context expiration use errors.Is or errors.As.

err := client.Query(...)
if errors.Is(err, context.Canceled) { ... }

Most errors returned by the gel package will satisfy the gelerr.Error interface which has methods for introspecting.

err := client.Query(...)

var gelErr gelerr.Error
if errors.As(err, &gelErr) && gelErr.Category(gelcfg.NoDataError){
    ...
}

Datatypes

The following list shows the marshal/unmarshal mapping between Gel types and go types. See also geltypes:

Gel                      Go
---------                ---------
Set                      []anytype
array<anytype>           []anytype
tuple                    struct
named tuple              struct
Object                   struct
bool                     bool, geltypes.OptionalBool
bytes                    []byte, geltypes.OptionalBytes
str                      string, geltypes.OptionalStr
anyenum                  string, geltypes.OptionalStr
datetime                 time.Time, geltypes.OptionalDateTime
cal::local_datetime      geltypes.LocalDateTime,
                         geltypes.OptionalLocalDateTime
cal::local_date          geltypes.LocalDate, geltypes.OptionalLocalDate
cal::local_time          geltypes.LocalTime, geltypes.OptionalLocalTime
duration                 geltypes.Duration, geltypes.OptionalDuration
cal::relative_duration   geltypes.RelativeDuration,
                         geltypes.OptionalRelativeDuration
float32                  float32, geltypes.OptionalFloat32
float64                  float64, geltypes.OptionalFloat64
int16                    int16, geltypes.OptionalFloat16
int32                    int32, geltypes.OptionalInt16
int64                    int64, geltypes.OptionalInt64
uuid                     geltypes.UUID, geltypes.OptionalUUID
json                     []byte, geltypes.OptionalBytes
bigint                   *big.Int, geltypes.OptionalBigInt

decimal                  user defined (see Custom Marshalers)

Note that Gel's std::duration type is represented in int64 microseconds while go's time.Duration type is int64 nanoseconds. It is incorrect to cast one directly to the other.

Shape fields that are not required must use optional types for receiving query results. geltypes.Optional can be embedded to make structs optional.

Not all types listed above are valid query parameters. To pass a slice of scalar values use array in your query. Gel doesn't currently support using sets as parameters.

query := `select User filter .id in array_unpack(<array<uuid>>$1)`
client.QuerySingle(ctx, query, &user, []geltypes.UUID{...})

Nested structures are also not directly allowed but you can use json instead.

By default Gel will ignore embedded structs when marshaling/unmarshaling. To treat an embedded struct's fields as part of the parent struct's fields, tag the embedded struct with `gel:"$inline"`.

type Object struct {
    ID geltypes.UUID
}

type User struct {
    Object `gel:"$inline"`
    Name string
}

Custom Marshalers

Interfaces for user defined marshaler/unmarshalers are documented in the github.com/geldata/gel-go/internal/marshal package.

Example (LinkProperty)

Link properties are treated as fields in the linked to struct, and the @ is omitted from the field's tag.

var result []struct {
	Name    string `gel:"name"`
	Friends []struct {
		Name     string                   `gel:"name"`
		Strength geltypes.OptionalFloat64 `gel:"strength"`
	} `gel:"friends"`
}

err := client.Query(
	ctx,
	`select Person {
		name,
		friends: {
			name,
			@strength,
		}
	}`,
	&result,
)
if err != nil {
	log.Fatal(err)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client is a connection pool and is safe for concurrent use.

Example
package main

import (
	"fmt"
	"log"

	gel "github.com/geldata/gel-go"
	"github.com/geldata/gel-go/geltypes"
)

type User struct {
	ID   geltypes.UUID `gel:"id"`
	Name string        `gel:"name"`
}

func main() {
	db, err := gel.CreateClient(opts)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Insert a new user.
	var inserted struct{ id geltypes.UUID }
	err = db.QuerySingle(
		ctx,
		`INSERT User { name := <str>$0 }`,
		&inserted,
		"Bob",
	)
	if err != nil {
		log.Fatal(err)
	}

	// Select user.
	var user User
	err = db.QuerySingle(
		ctx,
		`
	SELECT User { name }
	FILTER .id = <uuid>$id
	`,
		&user,
		map[string]any{"id": inserted.id},
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(user.Name)
}
Output:

Bob

func CreateClient

func CreateClient(opts gelcfg.Options) (*Client, error)

CreateClient returns a new client. The client connects lazily. Call Client.EnsureConnected to force a connection.

Instead of providing connection details directly, we recommend connecting using projects locally, and environment variables for remote instances, providing an empty gelcfg.Options struct here. Read more about the recommended ways to configure client connections.

Example
client, err := gel.CreateClient(opts)
if err != nil {
	log.Fatal(err)
}

var result int64
err = client.QuerySingle(ctx, `SELECT 1`, &result)
if err != nil {
	fmt.Println(err)
}

fmt.Println(result)
Output:

1

func CreateClientDSN

func CreateClientDSN(dsn string, opts gelcfg.Options) (*Client, error)

CreateClientDSN returns a new Client. We recommend using CreateClient for most use cases.

dsn is either an instance name or a DSN.

dsn := "gel://admin@localhost/main"
client, err := gel.CreateClientDSN(dsn, opts)

func (*Client) Close

func (c *Client) Close() error

Close closes all connections in the client. Calling Close() blocks until all acquired connections have been released, and returns an error if called more than once.

func (*Client) EnsureConnected

func (c *Client) EnsureConnected(ctx context.Context) error

EnsureConnected forces the client to connect if it hasn't already. This can be used to ensure that your program will fail early in the case that the configured connection parameters are not correct.

func (*Client) Execute

func (c *Client) Execute(ctx context.Context, cmd string, args ...interface{}) error

Execute an EdgeQL command (or commands).

Example
err := client.Execute(ctx, "INSERT Product")
if err != nil {
	log.Fatal(err)
}
Output:

func (*Client) ExecuteSQL

func (c *Client) ExecuteSQL(ctx context.Context, cmd string, args ...interface{}) error

ExecuteSQL executes a SQL command (or commands).

Example
err := client.ExecuteSQL(ctx, `INSERT INTO "Product" DEFAULT VALUES`)
if err != nil {
	log.Fatal(err)
}
Output:

func (*Client) Query

func (c *Client) Query(ctx context.Context, cmd string, out interface{}, args ...interface{}) error

Query runs a query and returns the results.

Example
var output []struct {
	Result int64 `gel:"result"`
}

err := client.Query(ctx, `SELECT {result := 2 + 2}`, &output)
if err != nil {
	log.Fatal(err)
}

fmt.Print(output)
Output:

[{4}]

func (*Client) QueryJSON

func (c *Client) QueryJSON(ctx context.Context, cmd string, out *[]byte, args ...interface{}) error

QueryJSON runs a query and returns the results as JSON.

Example
var output []byte
err := client.QueryJSON(ctx, `SELECT {result := 2 + 2}`, &output)
if err != nil {
	log.Fatal(err)
}

fmt.Println(string(output))
Output:

[{"result" : 4}]

func (*Client) QuerySQL

func (c *Client) QuerySQL(ctx context.Context, cmd string, out interface{}, args ...interface{}) error

QuerySQL runs a SQL query and returns the results.

Example
var output []struct {
	Result int32 `gel:"result"`
}

err := client.QuerySQL(ctx, `SELECT 2 + 2 AS result`, &output)
if err != nil {
	log.Fatal(err)
}

fmt.Println(output)
Output:

[{4}]

func (*Client) QuerySingle

func (c *Client) QuerySingle(ctx context.Context, cmd string, out interface{}, args ...interface{}) error

QuerySingle runs a singleton-returning query and returns its element. If the query executes successfully but doesn't return a result a gelerr.NoDataError is returned. If the out argument is an optional type the out argument will be set to missing instead of returning a NoDataError.

Example
var output struct {
	Result int64 `gel:"result"`
}

err := client.QuerySingle(ctx, `SELECT {result := 2 + 2}`, &output)
if err != nil {
	log.Fatal(err)
}

fmt.Print(output)
Output:

{4}

func (*Client) QuerySingleJSON

func (c *Client) QuerySingleJSON(ctx context.Context, cmd string, out interface{}, args ...interface{}) error

QuerySingleJSON runs a singleton-returning query. If the query executes successfully but doesn't have a result a gelerr.NoDataError is returned.

Example
var output []byte
err := client.QuerySingleJSON(ctx, `SELECT {result := 2 + 2}`, &output)
if err != nil {
	log.Fatal(err)
}

fmt.Println(string(output))
Output:

{"result" : 4}

func (*Client) Tx

func (c *Client) Tx(ctx context.Context, action geltypes.TxBlock) error

Tx runs action in a transaction retrying failed attempts. Queries must be executed on the geltypes.Tx that is passed to action. Queries executed on the client in a geltypes.TxBlock will not run in the transaction and will be applied immediately.

The geltypes.TxBlock may be re-run if any of the queries fail in a way that might succeed on subsequent attempts. Retries are governed by gelcfg.RetryOptions and gelcfg.RetryRule. Retry options can be set using Client.WithRetryOptions. See gelcfg.RetryRule for more details on how they work.

Example
err := client.Tx(ctx, func(ctx context.Context, tx geltypes.Tx) error {
	return tx.Execute(ctx, "INSERT User { name := 'Don' }")
})
if err != nil {
	log.Println(err)
}
Output:

func (Client) WithConfig

func (c Client) WithConfig(cfg map[string]interface{}) *Client

WithConfig returns a copy of c with configuration values set to cfg. This is equivalent to using the edgeql configure session command. For available configuration parameters refer to the config documentation.

Example
configured := client.WithConfig(map[string]any{
	"allow_user_specified_id": true,
})

err := configured.Execute(ctx, `INSERT USER { id := <uuid>$0 }`, id)
if err != nil {
	log.Fatal(err)
}
Output:

func (Client) WithGlobals

func (c Client) WithGlobals(globals map[string]interface{}) *Client

WithGlobals returns a copy of c with its global variables updated from globals.

WithGlobals does not remove variables that are not mentioned in globals. Instead use Client.WithoutGlobals.

Example
configured := client.WithGlobals(map[string]any{
	"used_everywhere": int64(42),
})

var result int64
err := configured.QuerySingle(
	ctx,
	"SELECT GLOBAL used_everywhere",
	&result,
)
if err != nil {
	log.Fatal(err)
}

fmt.Println(result)
Output:

42

func (Client) WithModuleAliases

func (c Client) WithModuleAliases(aliases ...gelcfg.ModuleAlias) *Client

WithModuleAliases returns a copy of c with module name aliases set to aliases.

Example
configured := client.WithModuleAliases(
	gelcfg.ModuleAlias{
		Module: "math",
		Alias:  "m",
	},
)

var result int64
err := configured.QuerySingle(ctx, "SELECT m::abs(-42)", &result)
if err != nil {
	log.Fatal(err)
}

fmt.Println(result)
Output:

42

func (Client) WithQueryOptions

func (c Client) WithQueryOptions(opts gelcfg.QueryOptions) *Client

WithQueryOptions returns a copy of c with its gelcfg.Queryoptions set to opts.

Example
opts := gelcfg.NewQueryOptions().WithReadOnly(true)
configured := client.WithQueryOptions(opts)

err := configured.Execute(ctx, "INSERT User")
fmt.Println(err)
Output:

gel.DisabledCapabilityError: cannot execute data modification queries: disabled by the client

func (Client) WithQueryTag added in v1.1.0

func (c Client) WithQueryTag(tag string) (*Client, error)

WithQueryTag returns a copy of c with the sys::QueryStats tag set.

sys::QueryStats only records the tag from the first time a query is run. Running the query again with a different tag will not change the tag in the sys::QueryStats entry.

Example
tag := "my-app/backend"
configured, err := client.WithQueryTag(tag)
if err != nil {
	log.Fatal(err)
}

err = configured.Execute(ctx, "SELECT User { ** }")
if err != nil {
	log.Fatal(err)
}

var query string
err = client.QuerySingle(
	ctx,
	`
	SELECT assert_single((
		SELECT sys::QueryStats
		FILTER .tag = <str>$0
	)).query
	`,
	&query,
	tag,
)
if err != nil {
	log.Fatal(err)
}

// sys::QueryStats reformats queries
fmt.Println(query)
Output:

select
    User {
        **
    }

func (Client) WithRetryOptions

func (c Client) WithRetryOptions(opts gelcfg.RetryOptions) *Client

WithRetryOptions returns a copy of c with the RetryOptions set to opts.

Example
linearBackoff := func(n int) time.Duration { return time.Second }

rule := gelcfg.NewRetryRule().
	WithAttempts(5).
	WithBackoff(linearBackoff)
opts := gelcfg.NewRetryOptions().WithDefault(rule)
configured := client.WithRetryOptions(opts)

err := configured.Execute(
	ctx,
	"INSERT Product { name := 'shiny and new' }",
)
if err != nil {
	log.Fatal(err)
}
Output:

func (Client) WithTxOptions

func (c Client) WithTxOptions(opts gelcfg.TxOptions) *Client

WithTxOptions returns a copy of c with the gelcfg.TxOptions set to opts.

Example
opts := gelcfg.NewTxOptions().WithReadOnly(true)
configured := client.WithTxOptions(opts)

err := configured.Tx(ctx, func(ctx context.Context, tx geltypes.Tx) error {
	return tx.Execute(ctx, "INSERT User")
})
fmt.Println(err)
Output:

gel.TransactionError: cannot execute SELECT in a read-only transaction

func (Client) WithWarningHandler

func (c Client) WithWarningHandler(handler gelcfg.WarningHandler) *Client

WithWarningHandler returns a copy of c with its gelcfg.WarningHandler set to handler. If handler is nil, gelcfg.LogWarnings is used.

Example
handler := func(warnings []error) error {
	for _, warning := range warnings {
		fmt.Println(warning)
	}
	return nil
}

configured := client.WithWarningHandler(handler)
err := configured.Execute(ctx, `SELECT _warn_on_call()`)
if err != nil {
	log.Fatal(err)
}
Output:

gel.QueryError: Test warning please ignore
query:1:8

SELECT _warn_on_call()
       ^ error

func (Client) WithoutConfig

func (c Client) WithoutConfig(key ...string) *Client

WithoutConfig returns a copy of c with keys unset from the configuration.

Example
configured := client.WithConfig(map[string]any{
	"allow_user_specified_id": true,
})

unconfigured := configured.WithoutConfig("allow_user_specified_id")

err := unconfigured.Execute(ctx, `INSERT User { id := <uuid>$0 }`, id)
fmt.Println(err)
Output:

gel.QueryError: cannot assign to property 'id'
query:1:15

INSERT User { id := <uuid>$0 }
              ^ consider enabling the "allow_user_specified_id" configuration parameter to allow setting custom object ids

func (Client) WithoutGlobals

func (c Client) WithoutGlobals(globals ...string) *Client

WithoutGlobals returns a copy of c with the specified global names unset.

Example
configured := client.WithGlobals(map[string]any{
	"used_everywhere": int64(42),
})

unconfigured := configured.WithoutGlobals("used_everywhere")

var result int64
err := unconfigured.QuerySingle(
	ctx,
	`SELECT GLOBAL used_everywhere`,
	&result,
)
fmt.Println(err)
Output:

gel.NoDataError: zero results

func (Client) WithoutModuleAliases

func (c Client) WithoutModuleAliases(aliases ...string) *Client

WithoutModuleAliases returns a copy of c with aliases unset.

Example
configured := client.WithModuleAliases(
	gelcfg.ModuleAlias{
		Module: "math",
		Alias:  "m",
	},
)

unconfigured := configured.WithoutModuleAliases("m")

var result int64
err := unconfigured.QuerySingle(ctx, "SELECT m::abs(-42)", &result)
fmt.Println(err)
Output:

gel.InvalidReferenceError: function 'm::abs' does not exist
query:1:8

SELECT m::abs(-42)
       ^ error

func (Client) WithoutQueryTag added in v1.1.0

func (c Client) WithoutQueryTag() *Client

WithoutQueryTag returns a copy of c with the sys::QueryStats tag removed.

Example
tag := "my-app/api"
configured, err := client.WithQueryTag(tag)
if err != nil {
	log.Fatal(err)
}

unconfigured := configured.WithoutQueryTag()

err = unconfigured.Execute(ctx, "SELECT Product { ** }")
if err != nil {
	log.Fatal(err)
}

var queries []string
err = client.Query(
	ctx,
	`
	SELECT (
		SELECT sys::QueryStats
		FILTER .tag = <str>$0
	).query
	`,
	&queries,
	tag,
)
if err != nil {
	log.Fatal(err)
}

fmt.Println(queries)
Output:

[]

Directories

Path Synopsis
cmd
edgeql-go
edgeql-go is a tool to generate go functions from edgeql queries.
edgeql-go is a tool to generate go functions from edgeql queries.
marshal
Package marshal documents marshaling interfaces.
Package marshal documents marshaling interfaces.
snc
soc
Package soc has utilities for working with sockets.
Package soc has utilities for working with sockets.

Jump to

Keyboard shortcuts

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