RelationalAI SDK for Julia
This guide presents the main features of the RelationalAI SDK for Julia, which can be used to interact with RelationalAI’s Relational Knowledge Graph System (RKGS).
The rai-sdk-julia package is open source and is available in this GitHub repository:
RelationalAI/rai-sdk-julia
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-julia, the latest iteration of the RelationalAI SDK for Julia.
The relationalai-sdk package is deprecated.
Requirements
You can check the rai-sdk-julia (opens in a new tab) repository for the latest version requirements to interact with the RKGS using the RelationalAI SDK for Julia.
Installation
The RelationalAI SDK for Julia is a stand-alone package. It can be installed using the Julia REPL:
using Pkg; Pkg.add("RAI")Configuration
The RelationalAI SDK for Julia can access your RAI Server credentials using a configuration file. See SDK Configuration for more details.
The Julia API load_config() function takes the configuration file and the profile name as optional arguments:
using RAI: load_config
cfg = load_config(fname="~/.rai/config", profile = "default")To load a different configuration, you can replace "default" with a different profile name.
Creating a Context
Most API operations use a context struct that contains the necessary settings for making requests against the RelationalAI REST APIs.
To create a context using the default profile in your ~/.rai/config file, you can use:
using RAI: Context, load_config
cfg = load_config()
# to specify a non-default profile use:
# cfg = load_config(profile = "myprofile")
ctx = Context(cfg)The remaining code examples in this document assume that you have a valid context in the ctx Julia variable and that you have brought the RAI module into the current namespace:
using RAIYou can test your configuration and context by running:
list_databases(ctx)This should return a list with database info, assuming your keys have the corresponding permissions. See Listing Databases below.
Additionally, most of the Julia API calls throw an HTTPError exception when there is an issue.
Therefore you can typically wrap the API calls discussed here in a try ... catch block similar to:
try
list_databases(ctx)
catch e
e isa HTTPError ? show(e) : rethrow()
endYou can find the full test example here (opens in a new tab).
Managing Users
This section covers the API functions that you need to manage users.
Each user has a role associated with specific permissions. These permissions determine the operations that the user can execute. See User Roles in the Managing Users and OAuth Clients guide for more details.
Creating a User
You can create a user as follows:
create_user(ctx, email, roles)Here, email is a string, identifying the user, and roles is a list of roles.
The roles currently supported are user and admin, with user being the default role.
Disabling and Enabling a User
You can disable a user through:
disable_user(ctx, id)Again, id is a string representing a given user’s ID.
You can reenable the user as follows:
enable_user(ctx, id)Listing Users
list_users(ctx)Getting Information for a User
You can get information for a user as follows:
get_user(ctx, user)Here, user is a string ID, for example, "auth0|XXXXXXXXXXXXXXXXXX".
Finding Users Using Email
You can look up a user’s details by specifying their email:
find_user(ctx, email)In this case, email is a string.
Deleting a User
You can delete a user through:
delete_user(ctx, id)In this case, id is a string representing a given user’s ID.
Managing OAuth Clients
This section covers the API functions that you need to manage OAuth clients.
Each OAuth client has a specific set of permissions. These permissions determine the operations that the OAuth client can execute. See [Permissions for OAuth Clients](/rkgms/console/user-management(#permissions-for-oauth-clients) in the Managing Users and OAuth Clients guide for more details.
Creating an OAuth Client
You can create an OAuth client as follows:
create_oauth_client(ctx, name, permissions)name is a string identifying the client.
permissions is a list of permissions from the following supported permissions:
create:accesskeycreate:enginecreate:oauth_clientcreate:userdelete:enginedelete:databasedelete:oauth_clientlist:accesskeylist:enginelist:databaselist:oauth_clientlist:permissionlist:rolelist:userread:engineread:credits_usageread:oauth_clientread:roleread:userrotate:oauth_client_secretrun:transactionupdate:databaseupdate:oauth_clientupdate:user
Listing OAuth Clients
You can get a list of OAuth clients as follows:
list_oauth_clients(ctx)Getting Information for an OAuth Client
You can get details for a specific OAuth client, identified by the string id, as follows:
get_oauth_client(ctx, id)Deleting an OAuth Client
You can delete an OAuth client identified by the string id as follows:
delete_oauth_client(ctx, id)Managing Engines
This section discusses the API functions you need to use to manage engines.
Creating an Engine
You can create a new engine as follows:
engine = "my_engine"
rsp = create_engine(ctx, engine)
println(rsp)By default, the engine size is XS.
You can create an engine of a different size by specifying the size parameter:
engine = "my_engine"
size = "XS"
rsp = create_engine(ctx, engine, size=size)Valid sizes are given as a string and can be one of:
XS(extra small).S(small).M(medium).L(large).XL(extra large).
Your engine may take some time to reach the “PROVISIONED” state, where it is ready for queries. It is in the “PROVISIONING” state until then.
Listing Engines
You can list all engines associated with your account as follows:
list_engines(ctx)This returns a JSON array containing details for each engine:
[
{
"id": "******",
"name": "my_engine",
"region": "us-east",
"account_name": "******",
"created_by": "******",
"created_on": "2023-07-10T17:15:22.000Z",
"size": "S",
"state": "PROVISIONED"
}
]To list engines that are in a given state, you can use the state parameter:
list_engines(ctx, state="PROVISIONED")Possible states are:
"REQUESTED"."PROVISIONING"."PROVISIONED"."DEPROVISIONING".
If there is an error with the request, an HTTPError exception is thrown.
Getting Information for an Engine
You can get information for a specific engine as follows:
engine = "my_engine"
rsp = get_engine(ctx, engine)
println(rsp)This gives you the following output:
{
"id": "******",
"name": "my_engine",
"region": "us-east",
"account_name": "******",
"created_by": "******",
"created_on": "2023-07-10T17:15:22.000Z",
"size": "S",
"state": "PROVISIONED"
}An HTTPError exception will be thrown if the engine does not exist.
Deleting an Engine
You can delete an engine with:
engine = "my_engine"
rsp = delete_engine(ctx, engine)
println(rsp)If successful, this is the output:
{"status":
{
"name":"my_engine",
"state":"deleting",
"message":"engine \"my_engine\" deleted successfully"
}
}RelationalAI decouples computation from storage. Therefore, deleting an engine does not delete any cloud databases. See Managing Engines for more details.
Managing Databases
This section covers the API functions you need to use to manage databases.
Creating a Database
You can create a database as follows:
database = "my_database"
rsp = create_database(ctx, database)
println(rsp)The result from a successful create_database call looks like this:
{
"id": "6a******",
"name": "my_database",
"region": "us-east",
"account_name": "******",
"created_by": "******",
"created_on": "2023-07-10T17:15:22.000Z",
"state": "CREATED"
}Cloning a Database
You can clone a database by specifying the target and source database names as follows:
rsp = create_database(ctx, "my_clone_database", source="my_database")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.
Listing Databases
You can list the available databases associated with your account as follows:
rsp = list_databases(ctx)
println(rsp)This returns a JSON array containing details for each database:
[
{
"id": "******",
"name": "my_database",
"region": "us-east",
"account_name": "******",
"created_by": "******",
"created_on": "2023-07-20T08:03:03.616Z",
"state": "CREATED"
}
]To filter databases by state, you can use the state parameter.
For instance:
state = "CREATED"
rsp = list_databases(ctx, state)Possible states are:
"CREATED"."CREATING"."CREATION_FAILED"."DELETED".
Getting Information for a Database
You can get information for a specific database as follows:
database = "my_database"
rsp = get_database(ctx, database)
println(rsp)This gives you the following output:
{
"id": "******",
"name": "my_database",
"region": "us-east",
"account_name": "******",
"created_by": "******",
"created_on": "2023-07-20T08:03:03.616Z",
"state": "CREATED"
}If the database does not exist, an HTTPError exception is thrown.
Deleting a Database
You can delete a database as follows:
database = "my_database"
rsp = delete_database(ctx, database)Deleting a database cannot be undone.
The remaining code examples in this guide assume that you have a running engine in engine and a database in database.
Managing Rel Models
This section covers the API functions you can use to manage 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 load_models function loads a Rel model in a given database.
In addition to the usual context, the database and engine arguments, it takes a Julia dictionary.
This dictionary maps names to models so that more than one named model can be loaded at once.
For example, this is how to add a Rel model code file to a database:
model_string = """def countries = {"United States of America"; "Germany"; "Japan"; "Greece"}
def oceans = {"Arctic"; "Atlantic"; "Indian"; "Pacific"; "Southern"}"""
load_models(ctx, database, engine, Dict("my_model" => model_string))If the database already contains an installed model with the same given name, then it is replaced by the new one.
If you need to load from a file, you can read it into a string first. For example:
model_string = read("my_model.rel", String)
load_models(ctx, database, engine, Dict("my_model" => model_string))Loading Multiple Models
You can also provide a Julia dictionary with a collection of models, together with their names.
Here’s an example that loads multiple models at once:
model_string1 = """def countries = {"United States of America"; "Germany"; "Japan"; "Greece"}"""
model_string2 = """ def oceans = {"Arctic"; "Atlantic"; "Indian"; "Pacific"; "Southern"}"""
load_models(ctx, database, engine, Dict("my_model" => model_string, "my_model2" => model_string))Listing Models
You can list the models within a database as follows:
list_models(ctx, database, engine)This returns a JSON array of names:
[
"rel/alglib",
"rel/display",
"rel/graph-basics",
"rel/graph-centrality",
"rel/graph-components",
"rel/graph-degree",
"rel/graph-measures",
"rel/graph-paths",
"rel/histogram",
"rel/intrinsics",
"rel/mathopt",
"rel/mirror",
"rel/net",
"rel/stdlib",
"rel/vega",
"rel/vegalite"
]In the example above, you can see all the built-in models associated with a database.
Getting Information for a Model
To see the contents of a given model, you can use:
model_name = "my_model"
get_model(ctx, database, engine, model_name)This gives the output:
{
"name": "my_model",
"value": "def my_range(x) = range(1, 10, 1, x)"
}In the example above, my_model defines a specific range.
Deleting Models
You can delete installed models from a database as follows:
delete_models(ctx, database, engine, model_name)Note that model_name is a string vector containing the names of the model or models to be deleted.
Querying a Database
The API call for executing queries against the database is exec.
It is a synchronous function, meaning that the running code is blocked until the transaction is completed or there are several timeouts indicating that the system may be inaccessible.
Each query is a complete transaction, executed in the context of the provided database.
The exec function specifies a Rel query, which can be empty, and a set of input relations.
It is defined as follows:
function exec(
ctx::Context,
database::AbstractString,
engine::AbstractString,
query;
inputs = nothing,
readonly = false,
kw...
)Here’s an example of a read query using exec:
rsp = exec(
ctx,
database,
engine,
"def output = {1; 2; 3}"
)
show_result(rsp)By default, readonly is false.
Write queries, which update base relations through the control relations insert and delete,
must use readonly=false.
Here’s an API call to load some CSV data and store them in the base relation my_base_relation:
data = """
name,lastname,id
John,Smith,1
Peter,Jones,2
"""
exec(
ctx, database, engine,
"""
def config:schema:name="string"
def config:schema:lastname="string"
def config:schema:id="int"
def config:syntax:header_row=1
def config:data = my_data
def delete[:my_base_relation] = my_baserelation
def insert[:my_base_relation] = load_csv[config]
""",
inputs = Dict("my_data" => data),
readonly=false
)The RelationalAI SDK for Julia also supports asynchronous transactions, through exec_async.
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.
exec_async is defined as exec, but in this case the running processes are not blocked:
rsp_async= exec_async(
ctx,
database, engine,
"def output = {1; 2; 3}"
)If needed, you can block the running process until the transaction has reached a terminal state, i.e., "COMPLETED" or "ABORTED", through wait_until_done:
wait_until_done(ctx, rsp_async)For instance, this can be useful for canceling an ongoing transaction:
try
wait_until_done(ctx, rsp_async)
catch
cancel_transaction(txn)
endFinally, you can fetch the results:
if rsp_async.transaction["state"] == "COMPLETED"
results = get_transaction_results(ctx, rsp_async.transaction["id"])
println(results)
endSimilarly to get_transaction_results, you can also get metadata and problems for a given transaction ID:
metadata = get_transaction_metadata(ctx, rsp_async.transaction["id"])
problems = get_transaction_problems(ctx, rsp_async.transaction["id"])The query size is limited to 64MB. An HTTPError exception will be thrown if the request exceeds this API limit.
Getting Multiple Relations Back
In order to return multiple relations, you can define subrelations of output.
For example:
rsp = exec(
ctx,
database,
engine,
"def a = 1;2 def b = 3;4 def output:one = a def output:two = b"
)
show_result(rsp)This gives the following output:
/:output/:two/Int64
(3,)
(4,)
/:output/:one/Int64
(1,)
(2,)Result Structure
The response is a Julia dictionary with the following keys:
| Field | Meaning |
|---|---|
| Transaction | Information about the transaction status, including the identifier. |
| Metadata | Metadata information about the results key. |
| Results | Query output information. |
| Problems | Information about any existing problems in the database — which are not necessarily caused by the query. |
Transaction
The transaction key is a JSON string with the following fields:
| Field | Meaning |
|---|---|
| ID | Transaction identifier. |
| State | Transaction state. See Transaction States for more details. |
For example:
{
"id": "******",
"state": "COMPLETED"
}Metadata
The metadata key is a JSON string with the following fields:
| Field | Meaning |
|---|---|
| Relation ID | This is a relation identifier, for example, "/:output/:two/Int64". It describes the relation name /:output/:two followed by its data schema Int64. |
| Types | This is a JSON array that contains the key names of the relation and their data type. |
For example:
{
relation_id {
arguments {
tag: CONSTANT_TYPE
constant_type {
rel_type {
tag: PRIMITIVE_TYPE
primitive_type: STRING
}
value {
arguments {
tag: STRING
string_val: "output"
}
}
}
}
arguments {
tag: PRIMITIVE_TYPE
primitive_type: INT_64
}
}
}Results
The results key is a vector with the following fields:
| Field | Meaning |
|---|---|
| Relation ID | This is a key for the relation, for example, "v1". It refers to the column name in the Arrow table that contains the data, where "v" stands for variable, since a relation’s tuples contain several variables. |
| Table | This contains the results of the query in a JSON-array format. |
For example:
v1: [[1,2,3]]Problems
The problems key is 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 | The type of problem, for example, "ClientProblem". |
For example:
{
'is_error': True,
'error_code': 'PARSE_ERROR',
'path': '',
'report': '1| def output = {1; 2; 3\n ^~~~~~~~\n', 'message': 'Missing closing `}`.',
'is_exception': False,
'type': 'ClientProblem'
}Specifying Inputs
The exec API call takes an optional inputs dictionary that can be used to map relation names to string constants for the duration of the query.
Here’s an example:
rsp = exec(
ctx,
database,
engine,
"def output = foo",
inputs = Dict("foo" => "asdf")
)
show_result(rsp)This will return the string "asdf" back.
Functions that transform a file and write the results to a base relation can be written in this way.
The calls load_csv and load_json can actually 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 show_result function prints API responses. See the previous examples.
Loading Data
The load_csv and load_json functions allow you to load data into a database.
These are not strictly necessary, since the Rel load utilities can also be used for this task.
See the CSV Import and JSON Import guides for more details.
It’s advisable to load data using built-in Rel utilities within queries, rather than these specific SDK functions. See Querying a Database for more details.
Loading CSV Data
The load_csv function loads CSV data and inserts the result into the base relation named by the relation argument.
function load_csv(
ctx::Context,
database::AbstractString,
engine::AbstractString,
relation::AbstractString,
data;
delim = nothing, # default: ,
header = nothing, # a Dict from col number to name (base 1)
header_row = nothing, # row number of header, nothing for no header
escapechar = nothing, # default: \
quotechar = nothing, # default: "
kw...
)Here’s an example:
data = read("my_data_file.csv", String)
load_csv(ctx, database, engine, "my_csv", data)By default, load_csv attempts to infer the schema of the data.
The options argument allows you to specify how to parse a given CSV file, including the schema, delimiters, and escape characters.
See this example (opens in a new tab) for more details.
Loading JSON Data
The load_json function loads JSON data and inserts them into the base relation named by the relation argument.
function load_json(
ctx::Context,
database::AbstractString,
engine::AbstractString,
relation::AbstractString,
data;
kw...
)Here’s an example:
load_json(ctx, database, engine, "my_json", """{"a" : "b"}""")In both the LoadCsvAsync() and LoadJsonAsync() methods, the base relation relation is not cleared, allowing for multipart, incremental loads.
You can clear a base relation, such as my_base_relation, as follows:
rsp = exec(
ctx,
database,
engine,
"def delete[:my_base_relation] = my_base_relation"
)Listing Base Relations
You can list the base relations in a given database as follows:
list_edbs(ctx, database, engine)The result is a JSON list of objects.
Managing Transactions
This section covers the API functions you can use to manage transactions.
Canceling Transactions
You can cancel an ongoing transaction as follows:
rsp = cancel_transaction(ctx, id)
println(rsp)The argument id is a string that represents the transaction ID.
An example is rsp_async.transaction["id"] from a previous exec_async API call.

