Skip to content
ScopeDB Docs
ScopeDB Docs

Go SDK

On this page

Use the Go SDK from trusted server-side applications. For application writes, start with Table.AppendStream: it accepts typed Go rows and owns bounded encoding, batching, backpressure, and request concurrency.

Before you start

Use Go 1.24 or later. Follow Connect an application, then set the endpoint and API key used by the examples:

export SCOPEDB_ENDPOINT="https://<endpoint>"
export SCOPEDB_API_KEY="<api-key>"

Keep the API key in a trusted server-side process. Do not put it in browser, mobile, or client-visible configuration.

Install

Install the maintained Go SDK module from your application module, then commit the selected version in go.mod and go.sum:

go get github.com/scopedb/goscopedb@latest

Create a client

endpoint := os.Getenv("SCOPEDB_ENDPOINT")
apiKey := os.Getenv("SCOPEDB_API_KEY")
if endpoint == "" || apiKey == "" {
	return errors.New("SCOPEDB_ENDPOINT and SCOPEDB_API_KEY are required")
}

client, err := scopedb.NewClient(scopedb.Config{
	Endpoint: endpoint,
	APIKey:   apiKey,
})
if err != nil {
	return err
}
defer client.Close()

Create one client and reuse it. NewClient validates the endpoint. Set Config.HTTPClient when the application needs to own HTTP timeouts, proxies, TLS, or connection pooling; the SDK never closes a caller-provided client.

Query data

Query is the short path for submitting a ScopeQL statement and waiting for its result:

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

result, err := client.Query(ctx, `
	FROM events
	WHERE service = 'checkout'
	ORDER BY time DESC
	LIMIT 10
`)
if err != nil {
	return err
}

rows, err := result.ToObjects()
if err != nil {
	return err
}
fmt.Println(rows)

SDK documentation covers client behavior, not the ScopeQL language. Use the quickstart, Query data, and ScopeQL reference for language syntax.

Work with results

Choose the representation needed by the application:

  • RawRows returns unconverted wire cell strings and nulls.
  • ToValues returns positional rows with typed Go values.
  • ToObjects keys each typed row by column name.
  • First returns an optional first keyed row.

ToValues, ToObjects, and First convert non-null cells according to the result metadata:

ScopeDB result typeGo valueNotes
intint64
uintuint64
floatfloat64Includes NaN and infinities.
binary[]byteDecoded bytes.
stringstring
booleanbool
timestamptime.TimePreserves nanosecond precision.
intervaltime.DurationFixed-duration intervals only.
arraystringJSON text; decode with encoding/json when needed.
objectstringJSON text; decode with encoding/json when needed.
anystringRaw text; the SDK does not infer its dynamic Go type.
nullnilA null cell is nil regardless of the declared type.

The null result type can appear in query metadata, such as for a literal NULL; it is not a table column type. See the data types reference for ScopeDB type semantics.

Conversions return an error for invalid cells, numeric overflow, duplicate column names in an object conversion, or an unrecognized data type. Structured array and object values remain JSON text; decoding them does not recursively apply SDK conversions to nested values.

Control statement execution

Use Submit when the application needs the statement ID, a local status snapshot, one explicit remote status request, or a separate wait:

handle, err := client.Statement("SELECT 1 AS ready").Submit(ctx)
if err != nil {
	return err
}

fmt.Println("statement ID:", handle.ID())
if cached := handle.LastStatus(); cached != nil {
	fmt.Println("local status:", *cached) // No network request.
}

latest, err := handle.Status(ctx) // At most one remote status request.
if err != nil {
	return err
}
fmt.Println("latest status:", latest)

result, err := handle.Wait(ctx)
if err != nil {
	return err
}

Once a terminal status is cached, Status returns it without another request. Persist handle.ID() and use client.StatementHandle(id) to resume the lifecycle in another process. Cancel requests server-side cancellation and returns the statement ID, creation time, status, and message.

Statement.ID and Statement.ExecTimeout are the only optional statement settings. Omit ID to let ScopeDB generate one. Cancelling the Go context stops the current client request or wait; it does not itself cancel the remote statement.

Browse the catalog

Use iterators for normal discovery. They request REST catalog pages lazily and keep continuation tokens opaque:

for table, err := range client.IterateTables(
	ctx,
	"scopedb",
	"public",
	scopedb.CatalogListOptions{PageSize: 100},
) {
	if err != nil {
		return err
	}
	fmt.Println(table.Name)
}

ListDatabases, ListSchemas, and ListTables expose one page when the application owns pagination. FetchDatabase, FetchSchema, and FetchTable return complete resources.

The table helper defaults to database scopedb and schema public:

table := client.Table("events")
description, err := table.Describe(ctx)
if err != nil {
	return err
}
fmt.Println(description.Columns)

Set table.Database and table.Schema explicitly when the destination comes from application configuration.

Stream typed rows to a table

Use AppendStream to write rows to an existing table. Send each row with Send, then call Shutdown when you have finished. The SDK groups rows into batches automatically.

Rows are encoded with encoding/json, so standard JSON tags and omitempty work as usual. Use struct tags to choose the JSON field names.

type Event struct {
	ID         int               `json:"id"`
	Name       string            `json:"name"`
	OccurredAt time.Time         `json:"occurred_at"`
	Attributes map[string]string `json:"attributes,omitempty"`
}

table := client.Table("events")
stream, err := table.AppendStream(scopedb.AppendStreamOptions{})
if err != nil {
	return err
}

for _, event := range []Event{
	{ID: 1, Name: "checkout.started", OccurredAt: time.Now().UTC()},
	{ID: 2, Name: "checkout.completed", OccurredAt: time.Now().UTC()},
} {
	if err := stream.Send(ctx, event); err != nil {
		_, _ = stream.Shutdown(ctx)
		return err
	}
}

report, err := stream.Shutdown(ctx)
if err != nil {
	return err
}
fmt.Printf("wrote %d rows\n", report.CommittedRows)

Send adds a row to the SDK's buffer. Shutdown sends the remaining rows, waits for the writes to finish, and closes the stream. Check its returned error before using the row count.

Use Flush when you want to wait for pending writes and then keep using the same stream. See the Go SDK examples for bulk writes and logging.

Send one raw NDJSON request

Use AppendNDJSON if your data is already in NDJSON format: one JSON object per line.

ndjson := []byte("{\"id\":1,\"name\":\"first\"}\n{\"id\":2,\"name\":\"second\"}\n")
result, err := table.AppendNDJSON(ctx, ndjson)
if err != nil {
	return err
}
fmt.Println("committed rows:", result.NumRowsInserted)

One request is limited to 8 MiB of uncompressed NDJSON and 200,000 rows. Use AppendStream to have the SDK encode and batch rows for you.

Handle structured errors

Server messages pass through unchanged. Use errors.As for operation metadata without parsing the message:

var scopeErr *scopedb.Error
if errors.As(err, &scopeErr) {
	fmt.Printf("kind=%s status=%d request_id=%s retryable=%t\n",
		scopeErr.Kind,
		scopeErr.HTTPStatus,
		scopeErr.RequestID,
		scopeErr.Retryable,
	)
}

StatementDetails preserves a failed statement's structured code, message, and code-specific JSON details. AppendDetails preserves whether a table append was rejected or has an unknown commit outcome, plus any structured row errors.

Advanced: transform before writing

Use Client.IngestStream only when source JSON specifically needs a server-side ScopeQL transformation before it can match the destination table. For normal typed events, shape the row in the producer and use Table.AppendStream.

This advanced path is sequential and fail-fast. An ingest error can follow a remote commit, so reconcile the failing batch before replaying it. See Ingest data for the transform-oriented HTTP workflow.

Next steps