Go SDK
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 current Go SDK release from your module:
go get github.com/scopedb/scopedb-sdk/go@v0.6.0
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:
RawRowsreturns unconverted wire cell strings and nulls.ToValuesreturns positional rows with typed Go values.ToObjectskeys each typed row by column name.Firstreturns an optional first keyed row.
ToValues, ToObjects, and First convert non-null cells according to the
result metadata:
| ScopeDB result type | Go value | Notes |
|---|---|---|
int | int64 | |
uint | uint64 | |
float | float64 | Includes NaN and infinities. |
binary | []byte | Decoded bytes. |
string | string | |
boolean | bool | |
timestamp | time.Time | Preserves nanosecond precision. |
interval | time.Duration | Fixed-duration intervals only. |
array | string | JSON text; decode with encoding/json when needed. |
object | string | JSON text; decode with encoding/json when needed. |
any | string | Raw text; the SDK does not infer its dynamic Go type. |
null | nil | A 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 when rows already match an existing destination table. The
stream uses encoding/json, so standard JSON tags, omitempty, and custom
MarshalJSON methods apply. Each row must encode as one top-level JSON object.
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("committed %d of %d accepted rows\n", report.CommittedRows, report.AcceptedRows)
Send waits for bounded local admission. A nil error means the row entered the
local stream; it does not confirm schema compatibility or a remote commit.
Flush settles the prefix accepted before its barrier and keeps the stream
open. Shutdown permanently closes admission and settles all accepted rows.
Send and TrySend are safe for concurrent producers. Use a fixed worker pool
instead of starting one goroutine per row. Concurrent HTTP batches have no
defined commit order; set MaxConcurrentBatches: 1 when request submission must
be serial.
The default AppendFailureStop policy stops after the first failed batch. For
latency-sensitive logs or telemetry, opt into AppendFailureContinue, use
TrySend, and monitor every delivery report plus Stats().DroppedByReason and
Stats().LastFailure.
A timeout, transport failure, or malformed response can leave a batch outcome
unknown. The SDK never automatically retries an unknown batch because it may
already have committed. Only an exact temporary batch explicitly reported as
rejected is retried. An in-memory stream is not a durable queue; use an
application-owned outbox and reconciliation when rows must survive process
failure or an unknown outcome.
Send one raw NDJSON request
Use AppendNDJSON only when the caller already owns one exact NDJSON request
body. Each non-empty line must be one JSON object, not a JSON array:
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 16 MiB and 200,000 rows. AppendStream is the simpler
path when the SDK should own encoding and request boundaries.
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
- Learn the language with the ScopeQL quickstart.
- Build application queries with Query data.
- Use the ScopeQL reference for exact language syntax.
- Review statement states and HTTP errors in the HTTP API.
- Browse the Go SDK examples for statement, catalog, direct append, streaming write, bulk, and telemetry workflows.