# Go SDK

Install the gospice Go SDK, then query Spice Cloud or a local Spice runtime over Arrow Flight, with health checks and connection retries.

The [Go SDK](https://github.com/spiceai/gospice) `gospice` is the easiest way to query [Spice.ai](https://spice.ai/) from Go.

It uses [Apache Arrow Flight](https://arrow.apache.org/docs/format/Flight.html) to efficiently stream data to the client and [Apache Arrow](https://arrow.apache.org/) Records as data frames.

GoDocs are available at [pkg.go.dev/github.com/spiceai/gospice/v9](https://pkg.go.dev/github.com/spiceai/gospice/v9).

## Requirements

- [Go 1.26](https://go.dev/doc/go1.26) or later. gospice v9.1.0 requires Go 1.26. To build with Go 1.25, use gospice v9.0.0.

## Installation

Get the **gospice** package.

```bash
go get github.com/spiceai/gospice/v9@latest
```

> **Warning**
>
> The module path carries the major version. Requesting `gospice/v8` resolves an older major release, not the latest.

## Usage

1\. Import the package.

```go
import gospice "github.com/spiceai/gospice/v9"
```

2\. Create a `SpiceClient` by providing your API key. Get your free API key at [spice.ai](https://spice.ai/).

```go
spice := gospice.NewSpiceClient()
defer func() { _ = spice.Close() }()
```

`Close()` releases the Flight and HTTP connections and must be called.

3\. Initialize the `SpiceClient`.

```go
if err := spice.Init(
    gospice.WithApiKey(ApiKey),
    gospice.WithSpiceCloudAddress(),
); err != nil {
    panic(fmt.Errorf("error initializing SpiceClient: %w", err))
}
```

The options passed to `Init` are package-level functions on `gospice`, not methods on the client:

| Option | Description |
| - | - |
| `WithApiKey(key)` | API key. A Spice Cloud key has the form `appId\|secret`. From v9.1.0, any other key that is not empty works with a self-managed runtime that requires an API key. |
| `WithSpiceCloudAddress()` | Connect to Spice Cloud. |
| `WithFlightAddress(addr)` | Arrow Flight address. A `grpc://` prefix selects plaintext; otherwise TLS is used. |
| `WithHttpAddress(addr)` | HTTP address, used for health checks and dataset refreshes. |
| `WithUserAgent(ua)` | Prepends to the reported user agent. |

4\. Execute a query and get back an [Apache Arrow Record Reader](https://pkg.go.dev/github.com/apache/arrow-go/v18/arrow/array#RecordReader).

```go
reader, err := spice.Sql(context.Background(), "SELECT * FROM tpch.lineitem LIMIT 10")
if err != nil {
    panic(fmt.Errorf("error querying: %w", err))
}
defer reader.Release()
```

5\. Iterate through the reader to access the records.

```go
for reader.Next() {
    record := reader.RecordBatch()
    defer record.Release()
    fmt.Println(record)
}
```

## Parameterized queries

`SqlWithParams` binds positional `$1`, `$2` placeholders:

```go
reader, err := spice.SqlWithParams(
    context.Background(),
    "SELECT * FROM tpch.lineitem WHERE l_quantity > $1 LIMIT 10",
    10,
)
```

From v9.1.0, `SqlWithParams` also infers the type of these values:

| Value | Binds as |
| - | - |
| `time.Time` | `Timestamp(ns, "UTC")`, after conversion to UTC. A value outside 1677-09-21 to 2262-04-11 returns an error. |
| `time.Duration` | `Duration(ns)` |
| `*T` | The value that the pointer points to. A nil `*T` binds as a NULL of the type of `T`. |
| `sql.NullInt64`, `sql.NullString`, `sql.NullTime`, `sql.Null[T]` and the other `sql.Null*` types | The value, or a NULL of that type when `Valid` is `false` |
| Another `driver.Valuer` | The value that `Value()` returns |
| A named type, such as `type VendorID int64` | Its underlying type |

```go
type VendorID int64
var minFare *float64 // nil: no lower bound

reader, err := spice.SqlWithParams(
    context.Background(),
    "SELECT * FROM taxi_trips WHERE VendorID = $1 AND ($2 IS NULL OR fare_amount >= $2) LIMIT 10",
    VendorID(2), minFare,
)
```

## Asynchronous queries

`Query` and `QueryWithParams` submit a query for asynchronous execution and return an `*AsyncQuery` handle instead of a record reader. They require the runtime to be running in distributed (scheduler) mode.

```go
query, err := spice.Query(context.Background(), "SELECT * FROM taxi_trips")
if err != nil {
    panic(fmt.Errorf("error submitting query: %w", err))
}

reader, err := query.Results(context.Background()) // waits for completion
if err != nil {
    panic(fmt.Errorf("error reading results: %w", err))
}
defer reader.Release()
```

The handle also exposes `ID()`, `Status(ctx)`, `Wait(ctx)`, and `Cancel(ctx)`.

## Usage with local Spice runtime

Follow the [quickstart guide](https://github.com/spiceai/spiceai?tab=readme-ov-file#%EF%B8%8F-quickstart-local-machine) to install and run spice locally.

```go
spice := gospice.NewSpiceClient()
defer func() { _ = spice.Close() }()

if err := spice.Init(
    gospice.WithHttpAddress("http://127.0.0.1:8090"),
); err != nil {
    panic(fmt.Errorf("error initializing SpiceClient: %w", err))
}
```

> **Note**
>
> From v9.1.0, when you do not pass `WithHttpAddress`, the HTTP address depends on the Flight address:
>
> - With the local Flight default, health checks, dataset refreshes and the other HTTP calls go to `http://127.0.0.1:8090`. In v9.0.0 and earlier, these calls went to Spice Cloud.
> - With the Spice Cloud Flight address, they go to `https://data.spiceai.io`.
> - With any other Flight address, they also go to `https://data.spiceai.io`. Pass `WithHttpAddress` with your runtime's HTTP address.
>
> To keep HTTP calls on Spice Cloud while Flight stays local, pass `WithHttpAddress("https://data.spiceai.io")`.

Or using a custom flight address:

```go
spice := gospice.NewSpiceClient()
defer func() { _ = spice.Close() }()

if err := spice.Init(
    gospice.WithFlightAddress("grpc://localhost:50052"),
    gospice.WithHttpAddress("http://localhost:8090"),
); err != nil {
    panic(fmt.Errorf("error initializing SpiceClient: %w", err))
}
```

## Default endpoints

| Target | Arrow Flight | HTTP |
| - | - | - |
| Spice Cloud | `flight.spiceai.io:443` | `https://data.spiceai.io` |
| Local runtime | `grpc://127.0.0.1:50051` | `http://127.0.0.1:8090` |

These can also be set with the `SPICE_FLIGHT_URL`, `SPICE_HTTP_URL`, `SPICE_LOCAL_FLIGHT_URL`, and `SPICE_LOCAL_HTTP_URL` environment variables.

## Health checks

`IsSpiceHealthy(ctx)` and `IsSpiceReady(ctx)` check the HTTP endpoint's `/health` and `/v1/ready` routes and return a boolean.

## Example

Run `go run .` to execute a sample query and print the results to the console.

## Connection retry

The `SpiceClient` implements connection retry mechanism (3 attempts by default). The number of attempts can be configured via `SetMaxRetries`:

```go
spice := gospice.NewSpiceClient()
spice.SetMaxRetries(5) // Setting to 0 will disable retries
```

Retries are performed for connection and system internal errors. It is the SDK user's responsibility to properly handle other errors, for example `RESOURCE_EXHAUSTED (HTTP 429)`.

## Contributing

Contribute to or file an issue with the `gospice` library at: <https://github.com/spiceai/gospice>
