• RKGMS
• SDK GUIDES
• CLI

# RelationalAI CLI

This guide presents the main features of the RelationalAI Command-Line Interface, which is used to interact with RelationalAI’s Relational Knowledge Graph Management System (RKGMS).

The rai-cli is open source and is available in the GitHub repository:

Contributions and pull requests are welcome.

Note: This guide applies to rai-cli, the latest iteration of the RelationalAI CLI. The relationalai-sdk package is deprecated.

The last section describes how the different Console notebook cell types can be implemented with this CLI.

## Installation

To use the CLI, you can directly download a binary for your installation from the rai-cli releases.

You can find precompiled binaries for MacOS, Linux, and Windows for different architectures. After selecting the one that fits your system, you can unzip the archive and include the rai binary in your system’s path.

## Configuration

To connect to the RAI Server using the RelationalAI CLI, you need a configuration file. See SDK Configuration for more details.

The CLI allows you to specify a configuration to use for connecting to the RAI Server. You can specify which configuration to use through the --config and --profile flags. The first flag specifies the file to use, while the second one specifies the name of the configuration. Here is an example using the ./myconfig file with the default profile to list all databases:

rai --config ./myfonfig --profile default list-databases

To load a different configuration from within a configuration file, you can replace "default" with a different profile name.

## Managing Users

The CLI allows a client with the right permissions to create, disable, and list the users under an account.

### Creating a User

You can create a user using the create-user command in the CLI. To create a user, you need to specify an email and, optionally, a set of roles for the user:

rai create-user user@relational.ai --role user

Here, user@relational.ai is a string identifying the user. The --role flag allows you to provide a role for the specific user that is about to be created. The roles currently supported are user and admin. user is the default role if no role is specified.

### Disabling and Enabling a User

You can disable a user using the disable-user command:

rai disable-user auth0|123456789

In this case, auth0|123456789 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 enable-user command:

rai enable-user auth0|123456789

### Deleting a User

You can delete a user in a way very similar to disabling them by using the delete-user command:

rai delete-user auth0|123456789

### Listing Users

You can list users using the list-users command of the CLI as follows:

rai list-users

### Retrieving User Details

You can retrieve user details from the system using the get-user command of the CLI as follows:

rai get-user auth0|123456789

Here, similarly to disable-user above, you need to provide 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 find-user command of the CLI:

rai find-user user@relational.ai

## Managing OAuth Clients

OAuth clients can be managed with the following commands, provided you have the corresponding permissions:

rai create-oauth-client name --perms permissions

Here, name is a string identifying the client. permissions is a list of permissions from the following supported permissions:

• create:accesskey
• create:compute
• create:oauth_client
• create:user
• delete:compute
• delete:database
• delete:oauth_client
• list:accesskey
• list:compute
• list:database
• list:oauth_client
• list:permission
• list:role
• list:user
• read:compute
• 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:

rai list-oauth-clients

You can get details for a specific OAuth client, identified by the string client-id, as follows:

rai get-oauth-client client-id

This is how to delete the OAuth client identified by the string client-id:

rai delete-oauth-client client-id

## 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:

rai create-engine my-engine

You can create an engine of a different size by specifying the size with the --size flag:

rai create-engine my-engine --size M

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 list-engines command:

rai list-engines

If you want to list engines that are in a given state, you can use the --state flag as follows:

rai list-engines --state DELETED

Most of the API examples below assume there is a running engine in the “PROVISIONED” state as well as a test database.

### Deleting an Engine

You can delete an engine with:

rai delete-engine my-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 compute engine can be retrieved with the get-engine command:

rai get-engine my-engine

## Managing Databases

### Creating a Database

You can create a database by using the create-database command as follows:

rai create-database my-database

The result from a successful create-database command will look like this:

{
"id": "xxxx-xxxx-xxx-xxxx-xxx",
"name": "my-database",
"region": "us-east",
"account_name": "acount-name",
"created_by": "xxxx@xyzw",
"created_on": "",
"state": "CREATED"
}

### Cloning a Database

You can use the clone-database command to clone a database by specifying the target and source databases in that order. Here is an example that clones my-database to my-database-clone:

rai clone-database my-database-clone my-database

With this command, you create an identical copy of my-database. Any subsequent changes to either database will not affect the other. Cloning a database fails if the source database does not exist.

### Retrieving Database Details

You can view the details of a database using the get-database command:

rai get-database my-database

The response is a JSON object. If the database does not exist, there will be an error reported as Not found.

### Listing Databases

Using the list-databases command will list the databases available to the account:

rai list-databases

A variation of the list-databases command takes a parameter --state as input that can be used to filter databases by state. Example states are: “CREATED”, “CREATING”, or “CREATION_FAILED”.

rai list-databases --status CREATED

### Deleting Databases

A database can be deleted with the delete-database command if the client has the right permissions.

The database is identified by its name, as used in the create-database command:

rai delete-database my-database

Deleting a database cannot be undone.

## Rel Model Sources

Rel model sources are collections of Rel code that can be added, updated, or deleted from a particular database. Since they update a database, a running engine — and a database — is required to perform operations on sources.

### Installing Rel Model Sources

The load-model command installs a Rel model source in a given database. The load-model command takes as input the database, the filename where to read the model from, and a name for the model. If no name is specified, the filename is used as the name of the model.

For example, consider the following simple Rel model, stored in a file named hello.rel:

def R = "hello", "world"

Here is an example that loads a model to my-database using my-engine from the hello.rel file:

rai load-model my-database hello.rel

In addition to the simple version of providing one model through a file, you can also provide a number of models using a list of files through the load-models command. Here is an example that loads model1.rel, model2.rel, and model3.rel in my-database using my-engine:

rai load-models my-database model1.rel model2.rel model3.rel

In case you want to keep the loaded models grouped in the database, you can specify a prefix for the loaded models through the --prefix flag. This flag will group the models under the specified string in the database. Here is an example that uses my-model as a prefix:

rai load-models my-database model1.rel model2.rel model3.rel --prefix my-model

Note that if the database already contains an installed source with the same given name, it is replaced by the new source.

### Deleting a Rel Model Source

You can delete a source from a database by using the delete-models command and providing the model name like this:

rai delete-models my-database model1

You can delete more than one model at a time by specifying multiple names in the delete-models command like this:

rai delete-models my-database model1 model2 model3

### Listing Installed Rel Model Sources

You can list the sources in a database using the list-model-names command like this:

rai list-model-names my-database

This returns a JSON array of names.

To see the contents of a source named my-model, you can use:

rai get-model my-database my-model

You can also list all models together with their sources using the list-models command:

rai list-models my-database

## Querying a Database

The high-level command for running a single query/transaction against the database is exec. The exec command blocks until the transaction is completed or there are several timeouts indicating that the system may be inaccessible. You can provide exec with a query either through a command-line string or through a file.

Here is an example of the exec command for a simple Rel query:

rai exec my-database --engine my-engine --code "def output[x in {1;2;3}] = x * 2"

Consider a file simple.rel containing the following Rel code:

def output[x in {1;2;3}] = x * 2

You can execute the same query by specifying the file name as follows:

rai exec my-database --engine my-engine --file simple.rel

The output in both cases is:

/:output/Int64/Int64
1, 2
2, 4
3, 6

Note that the exec command has an additional --readonly flag. By default, the --readonly flag is false. Queries meant to update base relations with insert and delete must use --readonly false. Also, by default, only the output relation is returned, as in the Console notebooks.

As another query example, here is a csv-example.rel file with Rel code that loads some CSV data and stores them in the base relation mybaserelation:

def mydata = """
name,lastname,id
John,Smith,1
Peter,Jones,2
"""

def config:schema:name="string"
def config:schema:lastname="string"
def config:schema:id="int"
def config:data = mydata
def delete[:mybaserelation] = mybaserelation
def insert[:mybaserelation] = load_csv[config]

You can execute this Rel code as follows:

rai exec my-database --engine my-engine --file csv-example.rel

The query size is limited to 64MB. An error will be returned if the request exceeds this API limit.

### Getting Multiple Relations Back

As in the RAI Console, if you want to return multiple relations, you can define subrelations of output. Here is an example:

rai exec my-database --engine my-engine --code \
"def a = 1;2 def b = 3;4 def output:one = a def output:two = b"

It gives the following output:

/:output/:one/Int64
1
2

/:output/:two/Int64
3
4

The default functionality of the CLI is to provide the input in this textual format. You can also request for a JSON output that is more detailed and that contains metadata about the transaction just performed. The output can also report any potential problems. For more details, see the Printing Responses section.

To get the output in JSON format, you can specify the --format json flag as follows:

rai exec my-database --engine my-engine --format json --code \
"def a = 1;2 def b = 3;4 def output:one = a def output:two = b"

This produces a much more detailed output:

{
"GotCompleteResult": true,
"Transaction": {
"id": "12345678-1234-1234-1234-123456789012",
"state": "COMPLETED"
},
"Results": [
{
"RelationID": "/:output/:one/Int64",
"Table": [
[
1,
2
]
]
},
{
"RelationID": "/:output/:two/Int64",
"Table": [
[
3,
4
]
]
}
],
"relations": [
{
"relation_id": {
"arguments": [
{
"tag": 3,
"constant_type": {
"rel_type": {
"tag": 1,
"primitive_type": 16
},
"value": {
"arguments": [
{
"tag": 16,
"Value": {
"StringVal": "b3V0cHV0"
}
}
]
}
}
},
{
"tag": 3,
"constant_type": {
"rel_type": {
"tag": 1,
"primitive_type": 16
},
"value": {
"arguments": [
{
"tag": 16,
"Value": {
"StringVal": "b25l"
}
}
]
}
}
},
{
"tag": 1,
"primitive_type": 2
}
]
},
"file_name": "0.arrow"
},
{
"relation_id": {
"arguments": [
{
"tag": 3,
"constant_type": {
"rel_type": {
"tag": 1,
"primitive_type": 16
},
"value": {
"arguments": [
{
"tag": 16,
"Value": {
"StringVal": "b3V0cHV0"
}
}
]
}
}
},
{
"tag": 3,
"constant_type": {
"rel_type": {
"tag": 1,
"primitive_type": 16
},
"value": {
"arguments": [
{
"tag": 16,
"Value": {
"StringVal": "dHdv"
}
}
]
}
}
},
{
"tag": 1,
"primitive_type": 2
}
]
},
"file_name": "1.arrow"
}
]
},
"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

When the response is returned as a JSON string, it contains the following fields:

FieldMeaning
RelationIDThis 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.
TableThis 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 is also a JSON string containing internal system information on the relations — for example, for the types of the data.

Finally, problems also contains a JSON string with the following fields:

FieldMeaning
error_codeThe type of error that happened, for example, "PARSE_ERROR".
is_errorWhether an error occurred or there was some other problem.
is_exceptionWhether an exception occurred or there was some other problem.
messageA short description of the problem.
pathA file path for the cases when such a path was used.
reportA long description of the problem.
typeType of the problem, for example, "ClientProblem".

## Printing Responses

The CLI allows you to get responses back in two different formats by specifying the --format flag.

The default format, called pretty, returns results in text form. Here is an example:

rai exec my-database --engine my-engine --code "def output='a';'b';'c'"

This is equivalent to:

rai exec my-database --engine my-engine --format pretty --code "def output='a';'b';'c'"

This command produces the following output:

/:output/Char
97
98
99

You can change the output format to JSON by changing the --format flag. Here is the same example producing JSON output:

rai exec my-database --engine my-engine --format json --code "def output='a';'b';'c'"

This command produces the following output:

{
"GotCompleteResult": true,
"Transaction": {
"id": "e3057f0d-27cb-6224-1086-f3d86bafe6ae",
"state": "COMPLETED"
},
"Results": [
{
"RelationID": "/:output/Char",
"Table": [
[
97,
98,
99
]
]
}
],
"relations": [
{
"relation_id": {
"arguments": [
{
"tag": 3,
"constant_type": {
"rel_type": {
"tag": 1,
"primitive_type": 16
},
"value": {
"arguments": [
{
"tag": 16,
"Value": {
"StringVal": "b3V0cHV0"
}
}
]
}
}
},
{
"tag": 1,
"primitive_type": 14
}
]
},
"file_name": "0.arrow"
}
]
},
"Problems": []
}

The JSON format has more detailed information on the results, including internal metadata and any potential problems that may have occured.

Here is an example code producing a problem since aaa is undefined:

rai exec my-database --engine my-engine --format json --code "def output=aaa"

This populates the following text in the "Problems" section of the JSON output:

{
...
"Problems": [
{
"type": "ClientProblem",
"error_code": "UNDEFINED",
"is_error": true,
"is_exception": false,
"message": "aaa is undefined.",
"report": "1| def output=aaa\n              ^^^\n\nThe problem occurred while compiling declaration output:\n1| def output=aaa\n   ^^^^^^^^^^^^^^\n\n"
}
]
}

## Loading Data: load-csv and load-json Commands

As a convenience, the CLI includes load-csv and load-json commands. These are not strictly necessary, since the load utilities in Rel itself can also be used for this task. For more details, see Data I/O: CSV Import and Data I/O: JSON Import and Export.

The load-csv command loads CSV data from a file and inserts the result into a base relation specified by the --relation flag. If no relation is specified, the data are loaded in a relation named after the file provided as a parameter to the load-csv command. Additionally, load-csv attempts to guess the schema of the data. For more control over the schema and parsing of the data, load-csv provides additional flags. Specifically:

• --delim char allows you to change the delimiter character from the default ,.
• --escapechar char allows you to change the escape character from the default \.
• --quotechar char allows you to change the quote character from the default ".
• --header-row int allows you to specify the row number of the header. You should specify 0 for no header. The default value is 1.
• --schema string allows you to specify the schema of the imported data. If no schema string is specified, load-csv imports all data as strings.

Here are the contents of an example CSV file with four columns four-cols.csv:

cocktail,quantity,price,date
"martini",2,12.50,"2020-01-01"
"sazerac",4,14.25,"2020-02-02"
"cosmopolitan",4,11.00,"2020-03-03"
"bellini",3,12.25,"2020-04-04"

You can load this file in the relation fourcols using the CLI as follows:

rai load-csv my-database four-cols.csv --relation fourcols

You can view the loaded data using the exec command:

rai exec my-database --code "def output=fourcols"

This gives the following output:

/:output/:cocktail/FilePos/String
2, martini
3, sazerac
4, cosmopolitan
5, bellini

/:output/:date/FilePos/String
2, 2020-01-01
3, 2020-02-02
4, 2020-03-03
5, 2020-04-04

/:output/:price/FilePos/String
2, 12.50
3, 14.25
4, 11.00
5, 12.25

/:output/:quantity/FilePos/String
2, 2
3, 4
4, 4
5, 3

Note that all columns from the CSV file were imported as strings. You can specify the schema through the --schema flag. Here is an example that specifies a different schema for each column in the CSV file and loads the data in the fourcols_schema relation:

rai load-csv my-database four-cols.csv --relation fourcols_schema \
--schema "cocktail:string;quantity:int;price:float;date:date"

You can now view the new loaded data using the exec command:

rai exec my-database --engine my-engine --code "def output=fourcols_schema"

This gives the following output:

/:output/:cocktail/FilePos/String
2, martini
3, sazerac
4, cosmopolitan
5, bellini

/:output/:date/FilePos/Dates.Date
2, 737425
3, 737457
4, 737487
5, 737519

/:output/:price/FilePos/Float64
2, 12.5
3, 14.25
4, 11
5, 12.25

/:output/:quantity/FilePos/Int64
2, 2
3, 4
4, 4
5, 3

Similarly to load-csv, load-json loads the data from the specified file as JSON and inserts them into the base relation specified through the --relation flag.

Here is an example JSON file john.json:

{
"first_name": "John",
"last_name": "Smith",
"state": "WA" },
"phone": [
{ "type": "home",
"number": 206456 },
{ "type": "work",
"number": 206123 }
]
}

You can load this data into a john relation using the CLI as follows:

rai load-json my-database john.json --relation john

You can view the loaded data as follows:

rai exec my-database --engine my-engine --code "def output=john"

This gives the following output:

/:output/:address/:city/String
Seattle

WA

/:output/:first_name/String
John

/:output/:last_name/String
Smith

/:output/:phone/:[]/Int64/:number/Int64
1, 206456
2, 206123

/:output/:phone/:[]/Int64/:type/String
1, home
2, work

Note: In both load-csv and load-json, the base relation that is specified in the --relation flag is not cleared, allowing for multipart, incremental loads. To clear it, you can issue a non-read-only Rel query of the form:

def delete[:relation] = relation

For example, you can do this in the CLI as follows:

rai exec my-database --engine my-engine --code "def delete[:john]=john"

This will clear the john base relation.

## Listing Base Relations

You can list base relations as follows:

rai list-edbs my-database

This will list the base relations in the given database (my-database in the previous example). The result is a JSON list of objects.

## Notebook Cell Types

As explained in Working With RAI Notebooks, there are four notebook cell types: Query, Install, Update, and Markdown. This section describes how the three cell types Query, Install, and Update map to the exec and load-model commands of the CLI.

### Query Cells

The Query cell in a RAI Console notebook corresponds to a simple exec command with the flag --readonly specified, as shown below:

rai exec my-database --engine my-engine --readonly \
--code "def output = {(1,); (2,); (3,)}"

It gives the following output:

/:output/Int64
1
2
3

Note that no rules/definitions are persisted in this case.

### Install Cells

To persist derived relations definitions, you can use the load-model command. That is, the Install cell in a RAI Console notebook corresponds to the load-model command of the CLI, as shown below:

rai load-model my-database arity1.rel

Here, the file arity1.rel contains the simple Rel code:

def k = {2;3;50}

You can check that the relation above was persisted using the exec command:

rai exec my-database --engine my-engine --code "def output=k"

This is the output:

/:output/Int64
2
3
50

### Update Cells

The Update cell in a RAI Console notebook corresponds to an exec command. In this case you should not specify the --readonly flag. Here’s an example:

rai exec my-database --engine my-engine \
--code "def insert:employee = {(1, \"Han Solo\"); (2, \"Bart Simpson\")}"

You can modify the employee base relation like this:

rai exec my-database --engine my-engine \
--code "def insert:employee = {(3, \"King\"); (4, \"Queen\")}"

You can now query the modified employee base relation like this:

rai exec my-database --engine my-engine --code "def output=employee"

This gives the following output:

/:output/Int64/String
1, Han Solo
2, Bart Simpson
3, King
4, Queen

To update base relations, always use the readonly=false parameter in the Execute() or ExecuteAsync() calls.