Skip to content
  • Rel
  • Data Types

Rel Data Types

This reference guide describes the various data types in Rel.


NumericPrimitiveSignedInt[8], SignedInt[16], SignedInt[32], SignedInt[64], SignedInt[128]Signed integers.
NumericPrimitiveUnsignedInt[8], UnsignedInt[16], UnsignedInt[32], UnsignedInt[64], UnsignedInt[128]Unsigned integers.
NumericPrimitiveFloating[16], Floating[32], Floating[64]Floating point numbers.
NumericPrimitiveRational[8], Rational[16], Rational[32], Rational[64], Rational[128]Rational numbers.
NumericPrimitiveFixedDecimal[nbits, ndecimals]Fixed-sized decimals with ndecimals digits of decimal precision and total bit size of nbits.
NumericAbstractNumberUnion of all numerical data types.
TextPrimitiveCharUTF-16 characters.
TextPrimitiveStringVariable-sized strings.
TimePrimitiveDateTimeTimestamps containing date and time information.
TimePrimitiveYear , Month , Week, DayDate periods.
TimePrimitiveHour, Minute, Second, Millisecond, Microsecond, NanosecondTime periods.
KeyPrimitiveHashHash value.
KeyPrimitiveAutoNumberInteger auto-numbers.
KeyAbstractEntityEntity key, union of Hash and AutoNumber.
MetaPrimitiveRelNameRelation names.
OtherAbstractAnyUnion of all data types.
OtherPrimitiveboolean_true, boolean_falseBoolean data type (for JSON support only).
OtherPrimitiveFilePosFile positions in a data file.
OtherPrimitiveMissingSingleton representing missing data.

We classify the data types based on their main purpose:

NumericNumerical data types.
TextText-based data types.
TimeTime-related data types.
KeyData types for identification purposes like UUIDs.
MetaData types referring to metadata information (like relation names).
OtherAll other supported data types.

We also distinguish between two kinds of data types:

PrimitiveData types with no further sub-types.
AbstractData types built on top of primitive types, usually unions of multiple primitive data types.


Relations are unordered collections of tuples. A tuple is an ordered collection of individual data values. Each data value has a data type.

In Rel, each data type has an associated unary relation, which includes all the values of that type. Type relations are often infinite, and therefore cannot be listed directly, but are still useful when specifying knowledge graphs, data schemas, and constraints.

Checking types is particularly useful for integrity constraints, as many of the examples in this document show. (See the concept guide on Integrity Constraints for more details.)

For each data type, Rel generally provides:

  1. A way to construct values of that data type,
  2. A type relation that tests if a given value belongs to that type, and
  3. Operations specific to the data type.

The following sections describe each supported data type and provide examples of how they can be used.

Numeric Data Types

Rel includes several numerical types, where the number of bits is specified to achieve a desired range and precision.

Ints and floats are 64-bit special cases of a more general set of fixed-width numerical types.


As a convenience, Number is a super-type that includes all the numerical types. It doesn’t come with its own constructor, meaning you can’t create this data type directly. You can only create directly primitive numerical data types.

Type Relation: Number(x).


// query
def output  = {1; 1.5; pi_float64; decimal[64, 2, 3.14159]; rational[64, 2, 3]}
ic number_ic { subset(output, Number) }
Loading number...

Noteworthy Operations

Numerical operations are usually only compatible between values of the same type. When incompatible types are used, the result is the empty relation, false.

Operations include: +, -, *, ^, and /, also known as add, substract, multiply, power, and divide.

Rel also offers bit-level operations between integers or unsigned integers of the same width:

Signed Integer

Integers are 64-bit signed integers, by default.


Integer constants and int[nbits, x] with bit size nbits, which can be 8, 16, 32, 64, or 128. Shortcut constructors: int64, int128.

// query
def output = {1; -10; int[8, 5]; int[128, -10^10]}
Loading construction...

Type Relation: Int(x), SignedInt(nbits, x)

For 64-bit integers (the default): Int(x).

For other bit-lengths: SignedInt(nbits, x).

// query
def R = 1234; int[8, 5]
def output:a(x) = R(x) and (Int(x))
def output:b(x) = R(x) and (SignedInt(8, x))
Loading type-relation-int...

Noteworthy Operations


// query
def a = 432123432123432123
def output:overflow = a^2
def output:wide = int128[a]^2
Loading operations...

Unsigned Integer


Unsigned integers can be constructed with

  • uint[nbits, x] with bit size nbits which can be 8, 16, 32, 64, or 128,
  • 0xNNNN for hexadecimal (base 16) constants,
  • 0oNNNN for octal (base 8) constants.

Hexadecimal and octal constants are assigned the unsigned integer with the lowest number of bits needed to represent it.

// query
def output = {0xF; 0o77777777; 0xFFFF123FFFFF; uint[128, 1234]}
Loading unsigned-integers...

Shortcut constructors: uint64, uint128.

Type Relation: UInt(x) (64-bit)

UInt(x) holds iff x is a 64-bit integer.

Generalized type relation: UnsignedInt(nbits, x) with bit size nbits which can be 8, 16, 32, 64, or 128.

// query
def R = {0xF; 0o77777777; uint128[1234]}
def output(x) = R(x) and (UInt(x) or UnsignedInt(32, x))
Loading type-relation...

Noteworthy Operations

Mathematical operations between unsigned integers of different widths give an empty result.


// query
def a = uint[64, 1000]
def b = uint[32, 100]
def uint_type[x] = UnsignedInt(16, x), "UInt16"
def uint_type[x] = UnsignedInt(32, x), "UInt32"
def uint_type[x] = UnsignedInt(64, x), "UInt64"
    def uint_type[x] = UnsignedInt(128, x), "UInt128"
def output:sum = a + b // empty
def output:atype = uint_type[a]
def output:btype = uint_type[b]
Loading mathematical-operations...


Floats are 64-bit by default.


Float constants can be specified using a decimal point, or with scientific notation, as in 1e6 or 1.321E2.

To specify the bit-width, use the constructor float[nbits, x], for a float constant x with bit size nbits which can be 16, 32, or 64.

Shortcut constructor: float64[x].

// query
def output = {2.0; float[32, 1.321]; 7.297e-3}
Loading float-64...

Type Relation: Float(x)

(Shortcut) Type relation (64-bit): Float(x), true iff x is a 64-bit float.

Generalized type relation: Floating(nbits, x). This works for 16, 32, and 64 bits.

// query
def R = (2.0, float[32, 1.321])
ic float_ic {
   subset(R, (Float, Floating[32]))
def output = R
Loading floating...

Noteworthy Operations



Rational numbers are constructed with rational[nbits, numerator, denominator] for integer numerator and denominator. The supported bit sizes for nbits are: 8, 16, 32, 64, and 128.

// query
def output = {rational[64, 2, 3]; rational[8, -1, 7]}
Loading numerator-denominator...

Type Relation : Rational(nbits, x).

The binary relation Rational(nbits, x) checks that x is of type Rational with a bit size of nbits.

// query
def R = rational[16, -5, -7]
def output(x) = R(x) and Rational(16, x)
Loading binary-relation...

Noteworthy Operations


See also:


// query
def x = rational[64, 200, 25]
def y = rational[64, 10, 3]
def output:fields = x, numerator[x], denominator[x]
def output:ops = x + y, x * y, x / y
Loading rational-num-den...

We can add and multiply rationals by integers, but not by floats.

// query
def x = rational[64, 1, 3]
def output = x * 3, x + 5
Loading multiply-rationals...


FixedDecimal numbers have two parameters:

  • decimals: the number of decimal digits (to the right of .), and
  • bits: the total number of bits used.

The following parameter combinations are supported:

bit sizedecimals (d)(d)range
80—2215/10d-2^{15}/10^d to (2151)/10d(2^{15}-1)/10^d
160—4215/10d-2^{15}/10^d to (2151)/10d(2^{15}-1)/10^d
320—9231/10d-2^{31}/10^d to (2311)/10d(2^{31}-1)/10^d
640—18(263)/10d-(2^{63})/10^d to (2631)/10d(2^{63}-1)/10^d
1280—38(2127)/10d-(2^{127})/10^d to (21271)/10d(2^{127}-1)/10^d

You can use Rel to compute the range of a fixed decimal type:

// query
def decimal_range[bits, decimals] =
    -2^(bits-1)/10^(decimals), (2^(bits-1)-1)/10^(decimals)
def output = decimal_range[32, 4]
Loading decimal-range...


The constructor decimal[nbits, ndecimals, v] creates a fixed-size decimal for v.

// query
def output = {decimal[32, 4, 2/3]; decimal[128, 20, sqrt[2]]}
Loading fixed-size-decimal...

Type Relation: FixedDecimal(nbits, ndecimals, x)

The ternary relation FixedDecimal(nbits, ndecimals, x) checks that x is of type FixedDecimal with a bit size of nbits and ndecimals decimal precision.

// query
def R = {decimal[64, 4, pi_float64]; decimal[64, 10, pi_float64]}
def output(x) = R(x) and FixedDecimal(64, 4, x)
Loading ternary-relation...

Noteworthy Operations

Operations between fixed-size decimals with different digits of precision yield an empty result.


// query
def output:pi_3 = decimal[64, 3, 3.14159265359]
def output:sum_one = decimal[64, 1, 4.5] * 2
def output:sum_two = decimal[64, 0, 4.5] * 2
Loading sum-one-two...
// query
def d = parse_decimal[64, 2, "10.6"]
ic fixed_decimal_ic {FixedDecimal(64, 2, d)}
def output = d
Loading parse-decimal...

Fixed decimals are particularly useful when you want to avoid floating-point errors, such as when dealing with financial data. For example:

// query
def mydec[x] = decimal[64,2,x]
def output:float = 140.28 + 57.14 + 17.80
def output:fixed = mydec[140.28] + mydec[57.14] + mydec[17.80]
Loading float-fixed...

Text Data Types

Character (Char)

UTF-16 characters, thus allowing Unicode.


A character (char) has a Unicode character as its value and is specified with single quotes '.

// query
def output = {'a'; '1'; 'α'; '文'; '👍'}
Loading char-unicode...

Type Relation: Char(x)

The unary relation Char(x) checks if x has type Char.

// query
def R = {'C'; "abc"; 1}
def output(x) = R(x) and Char(x)
Loading char-x...

Noteworthy Operations

  • char(string, i, c) - can enumerate the characters c of a string s, with their positions i.
  • concat[x, y] - works for char or string values of x and y.


For a string s, char[s, i] is the i-th char in s, starting at 1:

// query
def output = char["abc"]
Loading char-abc...

Characters can be concatenated directly to strings with concat:

// query
def output = concat['a', 'b'], concat['a', "bc"]
Loading concat...


Strings are sequences of Rel chars (UTF-16 characters). The index of the first character is 1. Individual characters can be accessed with the char[str, i] relation.


String constants are enclosed in double quotes ". Multiline string constants can be specified by using triple double quotes ("""), which are also useful for including the double quote symbol inside a string. It is also possible to specify “raw” strings by writing raw followed by an odd number of double quotes: for example, raw" ... ". Raw strings allow even greater freedom in what can be written inside a string.

Furthermore, Rel features string interpolation.

See section String Constants in Rel Language Reference for more details.

// query
def output = {"abc"; "Michael Theodore \"Mickey\" Mouse"}
Loading quote-symbol...

A multiline string constant:

// query
def output = """
1,2,"three", four
Loading multi-line-string...

A raw string constant:

// query
def output = raw"""
1,2,"three", four
Loading raw-string...

Notice that the output of the last example is olmost identical to that of the previous one. The only difference is the initial newline character (\n). For convenience, a multiline string constant can begin with a newline, which is ignored. If the newline does not directly follow the initial """, but is, for instance, preceded by a space, then neither the newline nor the first character of the multiline string will be ignored.

Type Relation: String(x)

String(x) tests if x is a string.

// query
def R = 1; 'a'; "b"; "abc"
def output(x) = R(x) and String(x)
Loading string-test...

Noteworthy Operations

char indexes characters in a string; substring[s, i, j] gives the substring between indices i and j inclusive.

// query
def s = "abcde"
def output:first_char = char[s, 1]
def output:substring = substring[s, 2, 4]
Loading substring...

The string relation converts a wide variety of other types into their string representation:

// query
def output = string[1] ;
    string[3.4] ;
    string[unix_epoch] ;
    string[Hour[1]] ;
ic string_ic { string[:a]  = "a" }
Loading string-representation...


// query
def output = string_trim["   a b c    "]
Loading string-trim...
// query
def R = {(1, "Tokyo"); (2, "Sidney")}
def output = concat["We visited ", concat[R[1], concat[" and ", concat[R[2], "."]]]]
Loading string-concat...

The last example above could also have been written with string interpolation:

def R = {(1, "Tokyo"); (2, "Sidney")}
def output = "We visited %(R[1]) and %(R[2])."

Metadata Types


RelName tests whether a value corresponds to a relation name. Relation names (aka RelName), when treated as data, start with the symbol :.

Strictly speaking, RelNames are metadata and result in specialized relations.

They are used extensively in modules — see the Modules Concept Guide. The column names of imported CSV files are also RelNames (see the FilePos Section for an example).


RelName constants start with a :.

// query
def T = :bar
module T
    def foo = true
def output = T
Loading relname...

Type Relation: RelName(x)

RelName(x) tests whether x is a RelName.

// query
def mymodule:f = 1
ic { subset(mymodule, (RelName, Int)) }
def output = mymodule
Loading relname-test...

Noteworthy Operations

relname_string converts RelNames into strings:

// query
def output:string = relname_string[:foo]
Loading relname-string...


// query
module myrel
   def foo = 1
def myrel:bar = 2
ic relname_ic {subset(myrel, (RelName, Int))}
def names(name) = myrel(name, _)
ic names_ic { subset(names, RelName) }
def output = names
Loading relname-ic...

Entity Data Types


The data type Entity is a super-type that includes Hash and AutoNumber data types.

Type Relation: Entity(x)

The Entity relation holds if a value has an AutoNumber or Hash type, which are the types created by the entity declaration:

// query
entity Planet planet_constructor = {"Earth"; "Venus"; "Mars"}
ic entity_ic {
   subset(planet_constructor, {(String, Entity)})
ic constructor_ic {Entity(planet_constructor["Earth"])}
def output = Planet
Loading entity-ic...

See the Entity Concept Guide for more details.


The Hash type contains values generated by the hash128 utility, which includes entities generated with the (default) @hash annotation.


Hashes are constructed with hash128[R], which hashes each of the tuples in the relation and adds it as a new column in the result.

// query
def output = hash128[{("a",1); ("b",2); ("c",3)}]
Loading hash128...
// query
entity E e = 1;2;3
Loading entity-e-hash...

Type Relation: Hash(x).

Hash(x) tests whether x is a Hash.

// query
def output = hash128[{("a",1); ("b",2); ("c",3)}]
ic hash_ic { subset(output:values, (String, Int, Hash)) }
Loading hash-test...
// query
entity E e = 1;2;3
ic { Hash(E) }

Noteworthy Operations

The hash_value_uint128_convert utility converts a Hash type to a Uint128:

// query
def hashes = hash128[{"a" ; "b"}]
def output(x,y,z) = hashes(x,y) and z = hash_value_uint128_convert[y]
Loading hash-value-uint128...

In the other direction, Uint128 values are converted to Hash with uint128_hash_value_convert:

// query
def output = uint128_hash_value_convert[0x00000123456789abcdef]
Loading hash-convert...



Currently, this data type is DEPRECATED and should be avoided. It will be removed soon.

The AutoNumber type contains values generated by the auto_number utility, which includes entities generated with the @auto_number annotation.


The auto_number[R] utility and @auto_number entities.

// query
def output = auto_number[{"a"; "b"; "c"}]
Loading auto-number...
// query
@auto_number entity E e = 1;2;3
Loading entity-e...

Type Relation: AutoNumber(x).

AutoNumber(x) tests whether x is an AutoNumber.

// query
def output = auto_number[{"a"; "b"; "c"}]
ic auto_number_ic { subset(output, (String, AutoNumber)) }
Loading auto-number-ic...
// query
@auto_number entity E e = 1;2;3
ic { AutoNumber(E) }

Time-related Data Types

The basic date and datetime objects use the proleptic Gregorian calendar (which includes a year 0), and do not reference a timezone.

Date properties, such as date_year, do not require a timezone. We can think of dates as a unit of time, counted out day by day.

In contrast, datetime properties, such as datetime_year, do require a timezone. This is because we can interpret the 24-hour time component as relative to a timezone, giving different results for the year/month/day/hour/minute in some cases. (Notice that a datetime’s second field does not depend on the timezone, so datetime_second does not take one as an argument.) Finally, if we want to avoid timezone issues, and treat datetimes as a monotonically increasing sequence similar to dates, we can use the UTC timezone, which has no geographical restrictions or adjustments for daylight savings time.


Calendar date.


Date constants can be specified directly in YYYY-MM-DD format:

// query
def output = {1776-07-04; 2020-02-29}
Loading date...

The utility lib{parse_date[string, format]} parses a string into a date, using the same formats accepted by the Julia language.

They are also populated by the “date” type when loading CSV files.

Type Relation: Date(x).

Date(x) tests whether x has the data type Date.

// query
def d = 2021-12-14
ic { Date(d) }

Noteworthy Operations


See also:


// query
def d = parse_date["1616-4-23", "Y-m-d"]
def output:tuple = date_year[d], date_month[d], date_day[d]
def output:monthname = date_monthname[d]
def output:dayname = date_dayname[d]
def output:week = date_week[d]
def output:dayofyear = date_dayofyear[d]
def output:dayofweek = date_dayofweek[d]
Loading parse-date...

To compute the number of days between two dates:

// query
def d1 = parse_date["2014-1-29", "Y-m-d"]
def d2 = parse_date["2014-2-28", "Y-m-d"]
def output = x : date_add[d1, x] = d2 and is_Day(x)
Loading parse-date-between...


A point in time, timezone-agnostic, with nanosecond resolution. When working with datetimes, we do not consider timezones or leap seconds.


Datetimes can be specified directly as YYYY-MM-DDThh:mm:ss<timezone>, where <timezone> is Z, or + or - followed by hh:mm. For more detail on time zones, see the list of tz database time zones.

For example:

// query
def output = {
Loading time-zone...

They can be parsed from strings with parse_datetime[string, format].

// query
def output = {
    parse_datetime["2018-06-12 13:00 +00:00", "YYYY-mm-dd HH:MM zzzz"];
    parse_datetime["2018-03-11 01:00 America/New_York", "Y-m-d H:M Z"]
Loading parse-datetime...

Currently, not all ISO datetime formats are supported.

For instance, if your datetime is specified as y-m-dTH:M:S.sZ, some workaround is needed:

// query
def date_string = "2022-01-02T18:07:27.963Z"
def date_trimmed = substring[date_string, 1, num_chars[date_string]-1]
def output = parse_datetime[date_trimmed, "y-m-dTH:M:S.s"]

In this example, the timezone Z needs to be removed before the parsing (y-m-dTH:M:S.s format is allowed).

Type Relation: DateTime(x)

DateTime(x) tests whether x has the data type DateTime.

// query
def dt = 2021-10-12T01:22:31+10:00
ic {DateTime(dt)}

Noteworthy Operations


// query
def dt = parse_datetime["2021-01-01 01:00:00", "Y-m-d H:M:S"]
def output:one = datetime_year[dt, "Europe/Berlin"]
def output:two = datetime_year[dt, "America/New_York"]
def output:three = datetime_year[dt, "-03:00"]
Loading parse-datetime-year...

Note that the resolution of datetime_now is milliseconds, which will be sufficient for most use cases.

// query
def datetime_to_milliseconds(datetime, v) =
    datetime_to_nanoseconds(datetime, ns) and
    v = trunc_divide[ns, 10^6]
    from ns, vf
def dt = unix_epoch + Day[1]
def output = datetime_to_milliseconds[dt]
ic datetime_ic { output = 24 * 60 * 60 * 1000 }
Loading milliseconds...

Date and Time Periods


Time Period: Nanosecond[n], Microsecond[n], Millisecond[n], Second[n], Minute[n], Hour[n]

Date Period: Day[n], Week[n], Month[n], Year[n]

For all constructors the variable n is an integer indicating how many multiples of the time/date periods are requested.

// query
def output = {Second[100]; Hour[2]; Day[1]; Month[24]}
Loading time-date-period...

Type Relations

The following unary relations check whether a variable has the specific date-period-like or time-period-like data type.

Time Period: is_Nanosecond(x), is_Microsecond(x), is_Millisecond(x), is_Second(x), is_Minute(x), is_Hour(x),

// query
def R = {Nanosecond[100]; Microsecond[10]; Minute[1]}
def output(x) = R(x) and (is_Nanosecond(x) or is_Minute(x))
Loading nano-micro-minute...

Date Period: is_Day(x), is_Week(x), is_Month(x), is_Year(x).

// query
def R = {Day[100]; Week[52]; Month[1]; Year[2000]}
def output(x) = R(x) and (is_Week(x) or is_Year(x))
Loading date-period...

Noteworthy Operations


Time periods can be added or subtracted from dates and datetimes. Rel follows the Julia period-arithmetic conventions. Adding Year, Day, and Second periods to a date or datetime is straightforward, since these periods have a well-defined time duration.

Adding Month periods, on the other hand, is different. It usually advances to the same date (and time) in the corresponding new month, so that adding months to the 10th of a month always results in the 10th of another month. For leap years, however, we choose the 28th, as adding months to the 29th would give a non-existing date.

// query
def output:jan = parse_date["2014-1-29", "Y-m-d"] // not a leap year
def output:feb = output:jan + Month[1]
def output:mar = output:feb + Month[2]
Loading not-leap...

Compare to:

// query
def output:jan = parse_date["2016-1-29", "Y-m-d"] // a leap year
def output:feb = output:jan + Month[1]
def output:mar = output:feb + Month[2]
Loading leap...

Note that adding day and month periods is therefore not associative:

// query
def d = parse_date["2014-1-29", "Y-m-d"]
def output:one = (d + Day[1]) + Month[1]
def output:two = (d + Month[1]) + Day[1]
Loading add-day-month...

Other Data Types


The type Any covers all possible values, and can be used as a wildcard to match any type:

Type Relation: Any(x).

Any(x) will be true for any x.


// query
def R = (1, 3) ; (1, "foo")
ic any_ic {subset(R, (Int, Any) )}
def output = R
Loading any...



The Rel language is based on two-valued logic where the constants true and false are relations and not data values.

The relation true is represented by an empty tuple (), which is a relation of arity 0 and cardinality 1. The relation false is the empty relation {}, which has arity 0 and cardinality 0.

Rel does include, however, a separate Boolean data type that is useful for importing data that explicitly depends on boolean values — in particular, JSON data (see example below).


There are two constructors, one for each boolean value: boolean_true and boolean_false.

// query
def output = {boolean_false; boolean_true}
Loading boolean-false-true...

Type Relation: Boolean(x)

Boolean(x) tests whether x has the data type Boolean.

// query
def R = {(1, boolean_true); (2, true); (3, boolean_false); (4, false)}
def output(x, y) = R(x, y) and Boolean(y)
Loading boolean-x...

Noteworthy Operations


In the following example, the JSON true and false values are mapped to the Rel data type Boolean.

// query
def joe = parse_json["""
      { "address": null, "name" : "JJ" , "person" : true , "company" : false }
ic { Boolean(joe:person) }
ic { boolean_and[joe:person, boolean_not[joe:company]] = boolean_true }
ic { joe:person = boolean_true }
def output = joe
Loading json-true-false...

Here is an example showing how and, or and not operations over the data types are written:

// query
def a = boolean_true
def b = boolean_false
def output:not_and = boolean_not[boolean_and[a, b]]
def output:or = boolean_or[a, b]
Loading and-or-not...

Note that boolean_and, boolean_or, and boolean_not are distinctly different from the Rel key words and, or, and not. The former operate only on boolean data, whereas the latter are part of logic expressions like Conjunction, Disjunction, and Negation.



FilePos types are created when importing CSV files. They are used as keys when joining columns from the same row. Currently, a user can’t directly create this data type themselves.

Type Relation: FilePos(x)

FilePos(x) tests whether x has the data type FilePos.

// query
def config:data="""
def csv = load_csv[config]
def output(p) = csv(_, p, _)
def csv_ic {
    subset(csv, (RelName, FilePos, String))
Loading filepos...


// query
def config:data="""
def output = table[load_csv[config]]
Loading config-data...

See the CSV import guide for more details.



Generally, missing or null data is simply represented in Rel by not having these facts in a relation.

Missing is a singleton type, containing only one value, missing.

It is used for data that requires an explicit representation of null data. For instance, JSON data requires an explicit representation for missing data (see example below). Data representation that is not fully normalized, such as Third normal form (3NF), also requires an explicit representation of missing data.

For data that is stored in the fully normalized Graph Normal Form (GNF), missing data points are simply represented by not stating the missing fact in the relation. GNF is the recommended way to model data and makes best use of the RKGMS query optimizer.


A missing data value is created by simply writing missing.

// query
def output = missing
Loading missing...

Type Relation: Missing(x)

Missing(x) tests whether x has the data type Missing.

// query
def R = {("+1", '👍'); ("+10", missing); ("+100", '💯')}
def output(x, y) = R(x, y) and not Missing(y)
Loading missing-x...


Parsing JSON data that contains explicit null data.

// query
def json = parse_json[""" { "address": null, "name" : "JJ" } """]
ic missing_ic { Missing(json:address) }
def output = json
Loading parse-json-ic...
Was this doc helpful?