Skip to content
Working With Models

Working With Models

This concept guide introduces installed Rel models, which can be reused later in queries or as building blocks for other models.

You have several options through which to manage and edit models: the RAI Console, the RelationalAI SDKs, the RelationalAI CLI, and the RelationalAI VS Code Extension.

Introduction

RAI databases are collections of:

  1. Relational data.
  2. Definitions and rules that define a model that operates on that data.

Simple definitions are often expressed as part of the query and forgotten afterwards. For example, the following query computes the sum of the numbers from 1 to 10, putting them in a myrange relation and then asking for sum[myrange]:

// read query
 
def myrange(x) = range(1, 10, 1, x)
def output = sum[myrange]

The relation myrange is a dervied relation. When this code is issued as a read query, it is not persisted in the database, and its definition is forgotten once the query is finished. This is convenient for simple auxiliary relations that are only used once.

For more advanced applications, you want to persist relation definitions in the database so they can be reused later in queries or as building blocks for more complex models. This step is called loading models. In this way, complex applications can be constructed in a modular way, as a collection of Rel libraries.

Installed Rel models are identified by a user-defined name. They can be added, removed, or updated, as you will see below.

🔎

Persisting relation definitions in the database is referred to as loading models. Once persisted, they are installed models.

Simple Example

Start by loading a small Rel model source that defines the transformation from Cartesian to spherical coordinates. You also define a relation π\pi, which holds the value of pi, taking advantage of Rel’s support for Unicode characters. This is a singleton relation, which means it has cardinality 1 and arity 1. Here’s what the code looks like:

// model
 
def π = pi_float64
 
@inline
def cartesian2spherical[R] = r, phi :
    r = sqrt[x^2 + y^2],
    phi = atan2[y, x] / π * 180
    from x, y where R(x, y)

The relation cartesian2spherical accepts an arity-2 relation R, which holds the Cartesian coordinate pairs (x, y), and evaluates a pair (r, phi), which is the radius and the angle of the corresponding 2D point. Now that you have installed this definition in the database, you can use it in a query like this:

// read query
 
def xy = {("a", 2, 0); ("b", 0, 4); ("c", -9, 0); ("d", 1, -1)}
def r_phi[i] = cartesian2spherical[xy[i]]
 
def output = r_phi

Installing Integrity Constraints

You can also install integrity constraints (ICs). The installed ICs will ensure that the database will always be in a state that fulfills the installed IC. For more information on ICs, see the Integrity Constraints concept guide.

For example, install an IC that makes sure that all of the elements of the myrel transaction are positive — along with an initial set of values for myrel itself:

// model
 
def myrel = {1; 2; 3}
 
ic myrel_positive(x) {
    myrel(x) implies x > 0
}

The integrity constraint passes, since everything in myrel is a positive number, and both the IC and the definition are successfully installed.

// read query
 
myrel

Impact on Future Transactions

Loading Models

If you now try to extend the definition of myrel by loading another definition for myrel that also includes negative numbers, then the transaction fails and the definition for myrel will not change:

// model
 
def myrel = {-1; 10}
 
def name = {"John"; "Jane"}

Any other definitions that you may have tried to load, such as name, will not succeed, and the installation of the entire code block will fail. Here’s how to check that myrel has not changed:

// read query
 
myrel

Querying the Database

Transactions that introduce definitions that violate installed ICs will fail. The system notifies you that the IC has been violated, and no results are evaluated. The error message may contain the content of the requested relations and details of the IC violation. Here’s an example:

// read query
 
def myrel = -1
 
def output = myrel

Write queries to update base relations also fail if an IC is violated. The requested base relation change, via insert or delete, will not be persisted in the database.

To demonstrate this behavior, try to expand myrel again by adding -1, and create a new base relation called name_base_relation:

// write query
 
def myrel = -1.0
 
def insert:name_base_relation = {"John"; "Jane"}
def output = name_base_relation

To check if the insertion was successful, query name_base_relation:

def output = name_base_relation

The base relation name_base_relation is still not defined, and the output is empty. The requested insertion into name_base_relation is not persisted in the database because of the IC violation.

ICs With Yet-To-Be Defined Relations

You can install integrity constraints for relations that have not been defined. Future transactions will report an UNDEFINED error. However, the errors will disappear and the integrity constraints will work as intended once the relations are created and populated.

You may suppress UNDEFINED errors for derived relations by initializing them to the empty set {}:

// model
 
def numbers = {}
 
ic numbers_ic(x) {
    numbers(x) implies Int(x)
}

Here, def numbers = {} ensures that numbers is defined, and no error message is issued. This is safe because Rel relations are the union of all of their definitions. Note that this trick does not work for base relations.

Model Introspection

Installed models are themselves stored in a base relation, which allows Rel programs to query and modify them.

This relation, available as rel:catalog:model, can be used to query, update, or delete the models themselves.

Here’s an example:

// write query
 
def insert:rel:catalog:model["my-rel-model"] = """
	def myrange = range[1,100,1]
	"""
def output:stats = sum[myrange], max[myrange]
def insert[:foo] = myrange
def output:foo = foo <++ "undefined"

This code has inserted a definition for myrange and was able to query it in the very same transaction.

However, myrange was not yet available for updating the base relation foo, since insert and delete statements, unlike outputs, are evaluated at the start of the transaction, while outputs are evaluated at the end. See Working With Base Relations for more details.

Here’s how to list the names of all the installed models, which include the Rel libraries:

// read query
 
def output(name) = rel:catalog:model(name, _)

To see the content of an installed model, you can query rel:catalog:model like this:

// read query
 
def output = rel:catalog:model["my-rel-model"]

This shows the code tagged "my-rel-model", which was installed above.

Here’s how to delete this model:

// write query
 
def delete:rel:catalog:model["my-rel-model"] = rel:catalog:model["my-rel-model"]
 

You can similarly delete any of the other installed models.

See the blog post “Rel: Live Programming and Self-Modifying Models” (opens in a new tab), for more on the self-modifying aspects of Rel.

Summary

In summary, loading Rel models means persisting relation definitions in order to reuse them later in queries or to construct more complex models. An installed integrity constraint, for example, ensures that relations and data will always obey the specified constraint. Future queries with definitions that violate the IC or modify the state of the database will fail.

Was this doc helpful?