RelationalAI SDK for Go
This guide presents the main features of the RelationalAI SDK for Go, which is used to interact with RelationalAI’s Relational Knowledge Graph System (RKGS).

The rai-sdk-go
SDK is open source and is available in the GitHub repository:

It includes self-contained examples (opens in a new tab) of the main API functionality. Contributions and pull requests are welcome.
Note: This guide applies to rai-sdk-go
, the latest iteration of the RelationalAI SDK for Go.
The relationalai-sdk
package is deprecated.
Requirements
You can check the rai-sdk-go (opens in a new tab) repository in pkg.go.dev
for the latest version requirements to interact with the RKGS using the RelationalAI SDK for Go.
Installation
To use the RelationalAI SDK for Go, you should include it in your Go code as follows:
import "github.com/relationalai/rai-sdk-go/rai"
Then simply run go mod tidy
to update your go.mod
file:
go mod tidy
Configuration
The RelationalAI SDK for Go can access your RAI Server credentials using a configuration file. See SDK Configuration for more details.
The Go rai.NewClientFromConfig()
function allows you to specify a configuration to use for connecting to the RAI Server.
You can load the configuration within a Go application as follows:
package main
import "github.com/relationalai/rai-sdk-go/rai"
func main() {
rai.NewClientFromConfig("default")
}
To load a different configuration, you can replace "default"
with a different profile name.
Creating a Client
Most API operations use a client, which contains the necessary settings for making requests against the RelationalAI REST APIs.
You can test your client by listing the databases as follows:
package main
import (
"log"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
// get list of databases
rsp, err := client.ListDatabases()
if err != nil {
log.Fatal(err)
}
// print them
rai.Print(rsp, 4)
}
This should print a list with the database info, assuming your keys have the corresponding permissions. See the Listing Databases section below.
The remaining code examples in this document assume that you have a valid client in the client
Go variable and that you have imported the necessary packages, such as the ones in the previous examples.
Here’s an example:
import (
"log"
"github.com/relationalai/rai-sdk-go/rai"
)
...
Additionally, most of the Go API calls return an error together with the expected result if something goes wrong. Therefore, you can typically handle the errors by logging them as above, or in any other way that may be appropriate. This is an example for logging:
rsp, err := client.ListDatabases()
if err != nil {
log.Fatal(err)
}
Managing Users
A client with the right permissions can create, disable, and list the users under the account.
Creating a User
You can create a user using the CreateUser
function of the client.
To create a user, you need to specify an email and a set of roles for the user:
email := "user@relational.ai"
roles := []string{ "user" }
rsp, err := client.CreateUser(email, roles)
rai.Print(rsp, 4)
Here, email
is a string identifying the user, and roles
is a list of strings, each one representing a role.
The roles currently supported are user
and admin
. user
is the default role if no role is specified.
Deleting a User
You can delete a user through:
rsp, err := client.DeleteUser(id)
rai.Print(rsp, 4)
The argument id
is a string representing a given user’s ID.
Disabling and Enabling a User
You can disable a user using the DisableUser
function of the client as follows:
rsp, err := client.DisableUser(id)
rai.Print(rsp, 4)
In this case, id
is a string representing a given user’s ID, as returned within the response in the user creation example above.
You can reenable the user by using the EnableUser
function as follows:
rsp, err = client.EnableUser(id);
rai.Print(rsp, 4);
Listing Users
You can list users using the ListUsers
function of the client as follows:
rsp, err := client.ListUsers()
rai.Print(rsp, 4)
Retrieving User Details
You can retrieve user details from the system using the GetUser
function of the client as follows:
rsp, err := client.GetUser(id)
rai.Print(rsp, 4)
Here, similarly to DisableUser
above, id
is a string ID uniquely identifying the user, such as "auth0|XXXXXXXXXXXXXXXXXX"
.
Finding Users Using Email
You can look up a user’s details by specifying their email through the FindUser
function of the client:
rsp, err := client.FindUser("user@relational.ai")
rai.Print(rsp, 4)
Managing OAuth Clients
OAuth clients can be managed with the following functions, provided you have the corresponding permissions:
rsp, err := client.CreateOAuthClient(name, permissions)
Here, name
is a string identifying the client.
permissions
is a list of permissions from the following supported permissions:
create:accesskey
create:engine
create:oauth_client
create:user
delete:engine
delete:database
delete:oauth_client
list:accesskey
list:engine
list:database
list:oauth_client
list:permission
list:role
list:user
read:engine
read:credits_usage
read:oauth_client
read:role
read:user
rotate:oauth_client_secret
run:transaction
update:database
update:oauth_client
update:user
You can get a list of OAuth clients as follows:
rsp, err := client.ListOAuthClients()
rai.Print(rsp, 4)
You can get details for a specific OAuth client, identified by the string id
, as follows:
rsp, err := client.GetOAuthClient(id)
This is how to delete the OAuth client identified by the string id
:
rsp, err := client.DeleteOAuthClient(id)
Each OAuth client has its own set of permissions, which determine the operations it can execute. Depending on the permissions, some operations, such as listing other users and creating or deleting engines, may fail. See the RAI Console Managing Users guide for more details.
Managing Engines
To query and update RelationalAI databases, you will need a running engine. The following API calls create and manage them.
Creating an Engine
You can create a new engine as follows. Note that the default size is XS
:
package main
import (
"log"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
// create engine
engine := "my-engine"
size := "XS"
rsp, err := client.CreateEngine(engine, size)
if err != nil {
log.Fatal(err)
}
rai.Print(rsp, 4)
}
Valid sizes are given as a string and can be one of:
XS
(extra small).S
(small).M
(medium).L
(large).XL
(extra large).
Note: It may take some time before your engine is in the “PROVISIONED” state, where it is ready for queries. It will be in the “PROVISIONING” state before that.
To list all the engines that are currently provisioned, you can use ListEngines()
:
rsp, err := client.ListEngines()
If you want to list engines that are in a given state, you can use the "state"
parameter as follows:
rsp, err := client.ListEngines("state", "DELETED")
Most of the API examples below assume there is a running engine (in the “PROVISIONED” state) in the engine
string variable, and a test database in the database
string variable:
// replace with your values for testing:
database := "mydatabase";
engine := "myengine";
Deleting an Engine
You can delete an engine with:
rsp := client.DeleteEngine(engine)
Note that since RelationalAI decouples computation from storage, deleting an engine does not delete any cloud databases. See Managing Engines for more details.
Getting Info for an Engine
The details for a specific engine can be retrieved with GetEngine()
:
rsp, err := client.GetEngine(opts.Engine)
rai.Print(rsp, 4)
Managing Databases
Creating a Database
You can create a database by calling the client’s CreateDatabase()
method as follows:
database := "mydatabase";
rsp, err := client.CreateDatabase(database)
rai.Print(rsp, 4)
The result from a successful CreateDatabase()
call will look like this:
{
"id": "xxxx-xxxx-xxx-xxxx-xxx",
"name": "mydatabase",
"region": "us-east",
"account_name": "acount-name",
"created_by": "xxxx@xyzw",
"deleted_on": "",
"state": "CREATED"
}
Cloning a Database
You can use CloneDatabase()
to clone a database by specifying source
and destination
arguments:
sourceDB := "myDB";
targetDB := "cloneDB";
rsp, err := client.CloneDatabase(targetDB, sourceDB)
rai.Print(rsp, 4);
With this API call, you can clone a database from myDB
to "cloneDB"
, creating an identical copy.
Any subsequent changes to either database will not affect the other.
Cloning a database fails if the source database does not exist.
You cannot clone from a database until an engine has executed at least one transaction on that database.
Retrieving Database Details
You can view the details of a database using the GetDatabase()
method of a client:
rsp, err := client.GetDatabase(sourceDB)
rai.Print(rsp, 4);
The response is a JSON object.
If the database does not exist, there will be an error in err
.
Listing Databases
Using ListDatabases()
will list the databases available to the account:
rsp, err := client.ListDatabases()
rai.Print(rsp, 4);
A variation of the ListDatabases()
method takes a parameter state
as input that can be used to filter databases by state.
Example states are: “CREATED”, “CREATING”, or “CREATION_FAILED”.
rsp, err := client.ListDatabases("state", "CREATED")
rai.Print(rsp, 4);
Deleting Databases
You can delete a database with DeleteDatabase()
if the client has the right permissions.
The database is identified by its name, as used in CreateDatabase()
:
err := client.DeleteDatabase("my-old-db")
Deleting a database cannot be undone.
Rel Models
Rel models are collections of Rel code that can be added, updated, or deleted from a dedicated database. A running engine — and a database — is required to perform operations on models.
Loading a Model
The LoadModel()
method of the client loads a Rel model in a given database.
LoadModel()
takes as input the database, the engine, a name for the model, and an io.Reader
where the model is read from:
func (c *Client) LoadModel(
database, engine, name string, r io.Reader,
) (*TransactionResult, error)
For example, consider the following simple Rel model, stored in a file named hello.rel
:
def R = "hello", "world"
You can then use the following complete example, which loads a model named hello
from the hello.rel
file into the my-database
database using the my-engine
engine:
package main
import (
"log"
"os"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
file := "hello.rel" // file with model
name := "hello" // model name
database := "my-database"
engine := "my-engine"
// create reader
r, err := os.Open(file)
if err != nil {
log.Fatal(err)
}
// load the model
rsp, err := client.LoadModel(database, engine, name, r)
if err != nil {
log.Fatal(err)
}
rai.Print(rsp, 4);
}
You can also provide the model code through a string, instead of a file, by first creating a Reader
:
package main
import (
"log"
"strings"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
model := "def R = \"hello\", \"world\""
name := "hello" // model name
database := "my-database"
engine := "my-engine"
// create reader from string
r := strings.NewReader(model)
// load the model
rsp, err := client.LoadModel(database, engine, name, r)
if err != nil {
log.Fatal(err)
}
rai.Print(rsp, 4);
}
Loading Multiple Models
In addition to the simple version of providing one model either through a file or a string, you can provide a number of models using a map
through the LoadModels()
method:
func (c *Client) LoadModels(
database, engine string, models map[string]io.Reader,
) (*TransactionResult, error)
The map contains the model names as string
together with the respective Reader
.
Here is an example that loads three different models coming from three different files:
package main
import (
"io"
"log"
"os"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
// assume the files are model1.rel, model2.rel, etc.
modelNames := []string{"model1", "model2", "model3"}
modelMap := make(map[string]io.Reader)
for _, m := range modelNames {
r, err := os.Open(m + ".rel")
if err != nil {
log.Fatal(err)
}
modelMap[m] = r
}
database := "my-database"
engine := "my-engine"
// load all the models
rsp, err := client.LoadModels(database, engine, modelMap)
if err != nil {
log.Fatal(err)
}
rai.Print(rsp, 4);
}
Note that if the database already contains an installed model with the same given name, it is replaced by the new source.
Deleting a Model
You can delete a model from a database using the DeleteModel()
method and providing the model model_name
like this:
rsp, err := client.DeleteModel(database, engine, model_name)
Deleting Multiple Models
You can also delete multiple models at once using the DeleteModels()
:
rsp, err := client.DeleteModels(database, engine, model_name)
Note that model_name
is a string map
containing the names of the models to be deleted.
Listing Installed Models
You can list the models in a database using the ListModelNames()
method like this:
rsp, err := client.ListModelNames(database, engine)
rai.Print(rsp, 4);
This returns a JSON array of names.
To see the contents of a named model, you can use:
rsp, err := client.GetModel(database, engine, name)
rai.Print(rsp, 4);
name
is the name of the model.
You can also list all models together with their code using ListModels()
:
rsp, err := client.ListModels(database, engine)
rai.Print(rsp, 4);
Querying a Database
The high-level API method for executing queries against the database is Execute()
.
The function call blocks until the transaction is completed or there are several timeouts indicating that the system may be inaccessible.
It specifies a Rel source, which can be empty, and a set of input relations.
The Execute()
function is defined as follows:
func (c *Client) Execute(
database, engine, source string,
inputs map[string]string,
readonly bool,
) (*TransactionAsyncResult, error)
Here is an example of Execute()
for a read query:
source := "def output[x in {1;2;3}] = x * 2"
rsp, err := client.Execute(database, engine, source, nil, true)
if err != nil {
log.Fatal(err)
}
In the code above, rsp
contains a TransactionAsyncResult
struct that has different fields containing the results and the status details of the transaction that was just executed.
For example, here is how to check the status of the transaction:
fmt.Println(rsp.Transaction.State) // ABORTED or COMPLETED
Similarly, you can view the results of the transaction as follows:
rai.Print(rsp, 4)
This gives the following output:
{
"GotCompleteResult": true,
"Transaction": {
"id": "12345678-1234-1234-1234-123456789012",
"state": "COMPLETED"
},
"Results": [
{
"RelationID": "v1",
"Table": [
1,
2,
3
]
},
{
"RelationID": "v2",
"Table": [
2,
4,
6
]
}
],
"Metadata": [
{
"relationId": "/:output/Int64/Int64",
"types": [
":output",
"Int64",
"Int64"
]
}
],
"Problems": []
}
If the transaction was aborted, you would see a description of the problem in the Problems
field of the JSON output.
Note that in Execute()
, the readonly
parameter is false
by default.
Write queries, which update base relations through the control relations insert
and delete
,
must use readonly=false
.
Here is an API call to load some CSV data and store them in the base relation mybaserelation
:
data := `name,lastname,id
John,Smith,1
Peter,Jones,2`
source := `def config:schema:name="string"
def config:schema:lastname="string"
def config:schema:id="int"
def config:syntax:header_row=1
def config:data = mydata
def delete[:mybaserelation] = mybaserelation
def insert[:mybaserelation] = load_csv[config]`
inputs := map[string]string{"mydata" : data}
readonly := false
rsp, err := client.Execute(database, engine, source, inputs, readonly)
fmt.Println(rsp.Transaction.State)
rai.Print(rsp, 4)
The RelationalAI SDK for Go also supports asynchronous transactions, through
ExecuteAsync()
that takes similar parameters to Execute()
.
In summary, when you issue a query to the database, the return output contains a transaction ID that can subsequently be used to retrieve the actual query results.
ExecuteAsync()
is defined similarly to Execute()
, but in this case the running process is not blocked.
You can obtain the query results whenever they are ready by polling the transaction state:
package main
import (
"log"
"time"
"github.com/relationalai/rai-sdk-go/rai"
)
func main() {
// create client with default profile
client, err := rai.NewClientFromConfig("default")
if err != nil {
log.Fatal(err)
}
database := "my-database"
engine := "my-engine"
source := "def output[x in {1;2;3}] = x * 2"
rsp, err := client.ExecuteAsync(database, engine, source, nil, true)
if err != nil {
log.Fatal(err)
}
// get the transaction ID
id := rsp.Transaction.ID
for { // poll until transaction is either completed or aborted
txn, err := client.GetTransaction(id)
if err != nil {
log.Fatal(err)
}
if txn.Transaction.State == "COMPLETED" || txn.Transaction.State == "ABORTED" {
results, _ := client.GetTransactionResults(id)
rai.Print(results, 4)
break
}
time.Sleep(2 * time.Second)
}
}
Similarly to client.GetTransactionResults(id)
, you can also get metadata and problems for a given transaction ID:
metadata, _ := client.GetTransactionMetadata(id)
problems, _ := client.GetTransactionProblems(id)
The query size is limited to 64MB. An error will be returned if the request exceeds this API limit.
Getting Multiple Relations Back
In order to return multiple relations, you can define subrelations of output
.
Here’s an example:
source := `def a = 1;2 def b = 3;4 def output:one = a def output:two = b`
rsp, _ := client.Execute(database, engine, source, nil, true)
rai.Print(rsp, 4)
It gives the following output:
{
"GotCompleteResult": true,
"Transaction": {
"id": "12345678-1234-1234-1234-123456789012",
"state": "COMPLETED"
},
"Results": [
{
"RelationID": "v1",
"Table": [
1,
2
]
},
{
"RelationID": "v1",
"Table": [
3,
4
]
}
],
"Metadata": [
{
"relationId": "/:output/:one/Int64",
"types": [
":output",
":one",
"Int64"
]
},
{
"relationId": "/:output/:two/Int64",
"types": [
":output",
":two",
"Int64"
]
}
],
"Problems": []
}
Note that each section of metadata corresponds to the respective section of results, i.e., the first metadata section corresponds to the first section of results.
Result Structure
The response is a JSON string with the following fields:
Field | Meaning |
---|---|
RelationID | This is a key for the relation, for example, "/:output/Int64" . |
Table | This contains the results of the query in a JSON-array format. |
Each query is a complete transaction, executed in the context of the provided database.
The metadata are also a JSON string with the following fields:
Field | Meaning |
---|---|
relationID | This is a key for the relation, for example, "/:output/:two/Int64" . This key describes the keys of the relation together with the type of data. |
types | This is a JSON-array that contains the key names of the relation and their data type. |
Finally, problems also contain a JSON string with the following fields:
Field | Meaning |
---|---|
error_code | The type of error that happened, for example, "PARSE_ERROR" . |
is_error | Whether an error occurred or there was some other problem. |
is_exception | Whether an exception occurred or there was some other problem. |
message | A short description of the problem. |
path | A file path for the cases when such a path was used. |
report | A long description of the problem. |
type | Type of the problem, for example, "ClientProblem" . |
Specifying Inputs
The Execute()
and ExecuteAsync()
functions take an optional inputs
map
, which can be used to map relation names to string constants for the duration of the query.
Here is an example:
inputs := map[string]string{ "foo" : "bar" }
source := "def output = foo"
rsp, _ := client.Execute(database, engine, source, inputs, true)
rai.Print(rsp, 4)
This will return the string "bar"
.
Functions that transform a file and write the results to a base relation can be written like this.
The Rel calls load_csv
and load_json
can be used in this way, via the data parameter, to write results to a base relation.
See, for example, the sample code using load_csv
in Querying a Database.
Printing Responses
The rai.Print()
function prints API responses that come back in JSON format.
Here’s an example:
source := "def output = 'a';'b';'c'"
rsp, _ := client.Execute(database, engine, source, nil, true)
rai.Print(rsp, 4)
This gives the following output:
{
"GotCompleteResult": true,
"Transaction": {
"id": "12345678-1234-1234-1234-123456789012",
"state": "COMPLETED"
},
"Results": [
{
"RelationID": "v1",
"Table": [
97,
98,
99
]
}
],
"Metadata": [
{
"relationId": "/:output/Char",
"types": [
":output",
"Char"
]
}
],
"Problems": []
}
You can also view the results only, instead of all the information of the transaction, as follows:
source := "def output = 'a';'b';'c'"
rsp, _ := client.Execute(database, engine, source, nil, true)
id := rsp.Transaction.ID
results, _ := client.GetTransactionResults(id)
rai.Print(results, 4)
This gives the following output:
[
{
"RelationID": "v1",
"Table": [
97,
98,
99
]
}
]
In case there are problems, you can view the problems as follows:
source := "def output = aaa" // aaa is undefined
rsp, _ := client.Execute(database, engine, source, nil, true)
id := rsp.Transaction.ID
problems, _ := client.GetTransactionProblems(id)
rai.Print(problems, 4)
This gives the following output:
[
{
"error_code": "UNDEFINED",
"is_error": true,
"is_exception": false,
"message": "`aaa` is undefined.",
"path": "",
"report": "1| def output = aaa\n ^^^\n\nThe problem occurred while compiling declaration `output`:\n1| def output = aaa\n ^^^^^^^^^^^^^^^^\n\n",
"type": "ClientProblem"
}
]
Loading Data: LoadCSV
and LoadJSON
As a convenience, the Go API includes LoadCSV
and LoadJSON
functions.
These are not strictly necessary, since the load utilities in Rel itself can be used
in a non-read-only Execute()
or ExecuteAsync()
query that uses the inputs
parameter.
See, for example, the sample code using the Rel load_csv
in Querying a Database.
The Go function LoadCSV()
loads data
and inserts the result into the base relation named by the relation
argument.
Additionally, LoadCSV()
attempts to guess the schema of the data.
For more control over the schema, use a non-read-only Execute()
or ExecuteAsync()
query using the inputs
option.
func (c *Client) LoadCSV(
database, engine, relation string, r io.Reader, opts *CSVOptions,
) (*TransactionResult, error)
The CSVOptions (opens in a new tab) class allows you to specify how to parse a given CSV file.
Through a CSVOptions
object, you can specify, for example, the delimiter and the escape character of a given file.
Similarly to LoadCSV()
, LoadJson()
loads the data
string as JSON and inserts it into the base relation named by the relation
argument:
func (c *Client) LoadJSON(
database, engine, relation string, r io.Reader,
) (*TransactionResult, error)
Here is an example:
data := "{\"name\": \"John\", \"age\": 20}"
r := strings.NewReader(data)
rsp, _ := client.LoadJSON(database, engine, "myjson", r)
rai.Print(rsp, 4)
Note: In both cases, the relation
base relation is not cleared, allowing for multipart, incremental loads.
To clear it, you can do:
def delete[:relation] = relation
Listing Base Relations
You can list base relations as follows:
rsp, _ := client.ListEDBs(database, engine)
rai.Print(rsp, 4)
This will list the base relations in the given database
.
The result is a JSON list of objects.
Transaction Cancellation
You can cancel an ongoing transaction by calling the following function:
rsp, _ := client.CancelTransaction(opts.ID)
rai.Print(rsp, 4)
The argument opts.ID
is a string that represents the transaction ID.
An example is rsp.Transaction.ID
from a previous client.ExecuteAsync()
API call.