Rel Data Types

This reference guide describes the various data types in Rel.

Overview

CategoryNameDescription
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.
TimePrimitiveDateDate.
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:

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

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

Introduction

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.

Number

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).

Example:

def output  = {1; 1.5; pi_float64; decimal[64, 2, 3.14159]; rational[64, 2, 3]}

ic number_ic { subset(output, Number) }

Relation: output

2//3
1
1.5
3.14
3.141592653589793

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, subtract, 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.

Construction

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

def output = {1; -10; int[8, 5]; int[128, -10^10]}

Relation: output

-10000000000
-10
1
5

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

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

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

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))

Relation: output

:a1234
:b5

Noteworthy Operations

Examples

def a = 432123432123432123
def output:overflow = a^2
def output:wide = int128[a]^2

Relation: output

:overflow-4321982966069415783
:wide186730660590134449234847760988287129

Unsigned Integer

Construction

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.

def output = {0xF; 0o77777777; 0xFFFF123FFFFF; uint[128, 1234]}

Relation: output

15
1234
16777215
281470987927551

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.

def R = {0xF; 0o77777777; uint128[1234]}

def output(x) = R(x) and (UInt(x) or UnsignedInt(32, x))

Relation: output

16777215

Noteworthy Operations

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

Examples

def a = uint[64, 1000]
def b = uint[32, 100]
def uint_type[x] = UnsignedInt(16, x), "UInt16"
@inline
def uint_type[x] = UnsignedInt(32, x), "UInt32"
@inline
def uint_type[x] = UnsignedInt(64, x), "UInt64"
@inline
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]

Relation: output

:atype"UInt64"
:btype"UInt32"

Float

Floats are 64-bit by default.

Construction

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].

def output = {2.0; float[32, 1.321]; 7.297e-3}

Relation: output

0.007297
1.321
2.0

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.

def R = (2.0, float[32, 1.321])

ic float_ic {
subset(R, (Float, Floating[32]))
}

def output = R

Relation: output

2.01.321

Noteworthy Operations

Rational

Construction

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.

def output = {rational[64, 2, 3]; rational[8, -1, 7]}

Relation: output

-1//7
2//3

Type Relation : Rational(nbits, x).

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

def R = rational[16, -5, -7]

def output(x) = R(x) and Rational(16, x)

Relation: output

5//7

Noteworthy Operations

Accessors:

See also:

Examples

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

Relation: output

:fields8//181
:ops34//380//312//5

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

def x = rational[64, 1, 3]

def output = x * 3, x + 5

Relation: output

1//116//3

FixedDecimal

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)$range
160–4$-2^{15}/10^d$ to $(2^{15}-1)/10^d$
320–8$-2^{31}/10^d$ to $(2^{31}-1)/10^d$
640–18$-(2^{63})/10^d$ to $(2^{63}-1)/10^d$
1280–37$-(2^{127})/10^d$ to $(2^{127}-1)/10^d$

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

@inline
def decimal_range[bits, decimals] =
-2^{bits-1}/10^{decimals}, (2^{bits-1}-1)/10^decimals
def output = decimal_range[32, 4]

Relation: output

-214748.3648214748.3647

Construction

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

def output = {decimal[32, 4, 2/3]; decimal[128, 20, sqrt[2]]}

Relation: output

0.6667
1.41421356237309509632

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.

def R = {decimal[64, 4, pi_float64]; decimal[64, 10, pi_float64]}

def output(x) = R(x) and FixedDecimal(64, 4, x)

Relation: output

3.1416

Noteworthy Operations

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

Examples

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

Relation: output

:pi_33.142
:sum_one9.0
:sum_two8
def d = parse_decimal[64, 2, "10.6"]

ic fixed_decimal_ic {FixedDecimal(64, 2, d)}

def output = d

Relation: output

10.60

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

@inline
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]

Relation: output

:fixed215.22
:float215.22000000000003

Text Data Types

Character (Char)

UTF-16 characters, thus allowing Unicode.

Construction

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

def output = {'a'; '1'; 'α'; '文'; '👍'}

Relation: output

'1'
'a'
'α'
'文'
'👍'

Type Relation: Char(x)

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

def R = {'C'; "abc"; 1}

def output(x) = R(x) and Char(x)

Relation: output

'C'

Noteworthy Operations

Examples

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

def output = char["abc"]

Relation: output

1'a'
2'b'
3'c'

Characters can be concatenated directly to strings with concat:

def output = concat['a', 'b'], concat['a', "bc"]

Relation: output

"ab""abc"

String

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.

Construction

String constants are enclosed in double quotes ". Multi-line string constants can be specified by using triple-double quotes ("""), which are also useful for including the double quote symbol inside a string. A quote can be escaped as ", and the \ character can be specified as “\”.

def output = {"abc"; "Michael Theodore \"Mickey\" Mouse"}

Relation: output

"Michael Theodore "Mickey" Mouse"
"abc"

Multi-line string constant:

def output = """
a,b,c
1,2,"three"
"""

Relation: output

" a,b,c 1,2,"three" "

Type Relation: String(x).

String(x) tests if x is a string.

def R = 1; 'a'; "b"; "abc"

def output(x) = R(x) and String(x)

Relation: output

"abc"
"b"

Noteworthy Operations

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

def s = "abcde"

def output:first_char = char[s, 1]
def output:substring = substring[s, 2, 4]

Relation: output

:first_char'a'
:substring"bcd"

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

def output = string[1] ;
string[3.4] ;
string[unix_epoch] ;
string[Hour[1]] ;
string[:a]

ic string_ic { string[:a] = "a" }

Relation: output

"1"
"1 hour"
"1970-01-01T00:00:00"
"3.4"
"a"

Examples

def output = concat["a", concat["b", string_trim["    c"]]]

Relation: output

"abc"

Meta Data Types

RelName

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).

Construction

RelName constants start with a :.

def T = :bar

module T
def foo = true
end

def output = T

Relation: output

:bar
:foo

Type Relation: RelName(x)

RelName(x) tests whether x is a RelName.

def mymodule:f = 1

ic { subset(mymodule, (RelName, Int)) }

def output = mymodule

Relation: output

:f1

Noteworthy Operations

relname_string converts between strings and RelNames, in both directions:

def output:string = relname_string[:foo]
def output:name = x : relname_string(x, "bar")

Relation: output

:name:bar
:string"foo"

Examples

module myrel
def foo = 1
end
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

Relation: output

:bar
:foo

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

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

Relation: output

RelationalAITypes.HashValue(0x712ff96f72202bab6a9699de1a387d4d)
RelationalAITypes.HashValue(0x8355416c7920b239863116eab2d52395)
RelationalAITypes.HashValue(0xf76cef701f06b691659de6c2085a8ee6)

See the Entity Concept Guide for more details.

Hash

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

Construction

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.

def output = hash128[{("a",1); ("b",2); ("c",3)}]

Relation: output

"a"1RelationalAITypes.HashValue(0x9488cdb702f91ebed4a76c960070032a)
"b"2RelationalAITypes.HashValue(0x0b8e043dc48a2a19ea0b5824f5dc2df3)
"c"3RelationalAITypes.HashValue(0x52ba690433be102aee6ad72e0cd84c9e)
entity E e = 1;2;3

Relation: E

RelationalAITypes.HashValue(0x3111960dd0471d0506552524a28b47c6)
RelationalAITypes.HashValue(0x6d6970b8d4e8d4321df80c245ca86083)
RelationalAITypes.HashValue(0x787684667ba600b7728ab1cb8aad5cfb)

Type Relation: Hash(x).

Hash(x) tests whether x is a Hash.

def output = hash128[{("a",1); ("b",2); ("c",3)}]

ic hash_ic { subset(output:values, (String, Int, Hash)) }

Relation: output

"a"1RelationalAITypes.HashValue(0x9488cdb702f91ebed4a76c960070032a)
"b"2RelationalAITypes.HashValue(0x0b8e043dc48a2a19ea0b5824f5dc2df3)
"c"3RelationalAITypes.HashValue(0x52ba690433be102aee6ad72e0cd84c9e)
entity E e = 1;2;3

ic { Hash(E) }

Noteworthy Operations

The hash_value_uint128_convert utility converts a Hash type to a Uint128:

def hashes = hash128[{"a" ; "b"}]
def output(x,y,z) = hashes(x,y) and z = hash_value_uint128_convert[y]

Relation: output

"a"RelationalAITypes.HashValue(0xd15cc59eee89911895f77b30d93dbbf9)278290350656597958582994784350977768441
"b"RelationalAITypes.HashValue(0xdcb9709a7d3692f7b6dc2833fe583c16)293393017861274673590867226134540991510

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

def output = uint128_hash_value_convert[0x00000123456789abcdef]

Relation: output

RelationalAITypes.HashValue(0x00000000000000000123456789abcdef)

AutoNumber

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

Construction

The auto_number[R] utility and @auto_number entities.

def output = auto_number[{"a"; "b"; "c"}]

Relation: output

"a"RelationalAITypes.AutoNumberValue(0x0000000000000001)
"b"RelationalAITypes.AutoNumberValue(0x0000000000000002)
"c"RelationalAITypes.AutoNumberValue(0x0000000000000003)
@auto_number entity E e = 1;2;3

Relation: E

RelationalAITypes.AutoNumberValue(0x0000000000000004)
RelationalAITypes.AutoNumberValue(0x0000000000000005)
RelationalAITypes.AutoNumberValue(0x0000000000000006)

Type Relation: AutoNumber(x).

AutoNumber(x) tests whether x is an AutoNumber.

def output = auto_number[{"a"; "b"; "c"}]

ic auto_number_ic { subset(output, (String, AutoNumber)) }

Relation: output

"a"RelationalAITypes.AutoNumberValue(0x0000000000000007)
"b"RelationalAITypes.AutoNumberValue(0x0000000000000008)
"c"RelationalAITypes.AutoNumberValue(0x0000000000000009)
@auto_number entity E e = 1;2;3

ic { AutoNumber(E) }

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.

Date

Calendar date.

Construction

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

def output = {1776-07-04; 2020-02-29}

Relation: output

1776-07-04
2020-02-29

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.

def d = 2021-12-14

ic { Date(d) }

Noteworthy Operations

Accessors:

See also:

Examples

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]

Relation: output

:dayname"Saturday"
:dayofweek6
:dayofyear114
:monthname"April"
:tuple1616423
:week16

To compute the number of days between two dates:

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)

Relation: output

30 days

Datetime

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

Construction

Datetimes can be specified directly as YYYY-MM-DDThh:mm:ss<timezone>, where <timezone> is Z, or + or - followed by hh:mm. For example:

def output = {
2021-10-12T01:22:31+10:00;
1955-11-12T22:04:00-08:00;
1970-01-01T00:00:00Z
}

Relation: output

1955-11-13T06:04:00
1970-01-01T00:00:00
2021-10-11T15:22:31

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

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"]
}

Relation: output

2018-03-11T06:00:00
2018-06-12T13:00:00

Type Relation: DateTime(x)

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

def dt = 2021-10-12T01:22:31+10:00

ic {DateTime(dt)}

Noteworthy Operations

Examples

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"]

Relation: output

:one2021
:three2020
:two2020

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

@inline
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 }

Relation: output

86400000

Date and Time Periods

Construction

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.

def output = {Second[100]; Hour[2]; Day[1]; Month[24]}

Relation: output

100 seconds
24 months
1 day
2 hours

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),

def R = {Nanosecond[100]; Microsecond[10]; Minute[1]}

def output(x) = R(x) and (is_Nanosecond(x) or is_Minute(x))

Relation: output

100 nanoseconds
1 minute

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

def R = {Day[100]; Week[52]; Month[1]; Year[2000]}

def output(x) = R(x) and (is_Week(x) or is_Year(x))

Relation: output

2000 years
52 weeks

Noteworthy Operations

Examples

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.

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]

Relation: output

:feb2014-02-28
:jan2014-01-29
:mar2014-04-28

Compare to:

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]

Relation: output

:feb2016-02-29
:jan2016-01-29
:mar2016-04-29

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

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]

Relation: output

:one2014-02-28
:two2014-03-01

Other Data Types

Any

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.

Example

def R = (1, 3) ; (1, "foo")

ic any_ic {subset(R, (Int, Any) )}

def output = R

Relation: output

13
1"foo"

Boolean

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).

Construction

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

def output = {boolean_false; boolean_true}

Relation: output

false
true

Type Relation: Boolean(x)

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

def R = {(1, boolean_true); (2, true); (3, boolean_false); (4, false)}

def output(x, y) = R(x, y) and Boolean(y)

Relation: output

1true
3false

Noteworthy Operations

Examples

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

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

Relation: output

:addressmissing
:companyfalse
:name"JJ"
:persontrue

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

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]

Relation: output

:not_andtrue
:ortrue

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

Construction

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.


def config:data="""
a,b,c
1,2,3
4,5,6
"""

def csv = load_csv[config]

def output(p) = csv(_, p, _)

def csv_ic {
subset(csv, (RelName, FilePos, String))
}

Relation: output

RelationalAITypes.FilePos(7)
RelationalAITypes.FilePos(13)

Examples

def config:data="""
a,b,c
1,2,3
4,5,6
"""

def output = table[load_csv[config]]

Relation: output

abc
RelationalAITypes.FilePos(7)"1""2""3"
RelationalAITypes.FilePos(13)"4""5""6"

See the CSV import guide for more details.

Missing

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.

Construction

A missing data value is created by simply writing missing.

def output = missing

Relation: output

missing

Type Relation: Missing(x)

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

def R = {("+1", '👍'); ("+10", missing); ("+100", '💯')}

def output(x, y) = R(x, y) and not Missing(y)

Relation: output

"+1"'👍'
"+100"'💯'

Examples

Parsing JSON data that contains explicit null data.

def json = parse_json[""" { "address": null, "name" : "JJ" } """]
ic missing_ic { Missing(json:address) }

def output = json

Relation: output

:addressmissing
:name"JJ"