Chapter 7. Built-in Operations

LogiQL supports built-in operations over values of primitive types. These operations allow you to compare values, perform arithmetic, manipulate strings and datetime values. Some operations are supported through symbolic operators, such as +, -, etc. These operators also have their corresponding "textual" forms, such as int:add, decimal:subtract. Other operations, e.g., datetime:parse, are available only in textual form. This chapter explains these operations and their semantics.

The signatures of some functions are specified using the following form:

function_name[arg1, arg2, ..., argn] = val -> type1(arg1), type2(arg2) ,..., typen(argn), typev(val).

This can be interpreted as follows: the function function_name takes n arguments, and returns a value. The types of these arguments (either primitive types described in Chapter 6, Primitive Types or entity types described in Section 8.3, “Entity Predicates”) are type1, type2, etc., respectively. The type of the function's value is typev

This particular style of signature specification follows from the way LogiQL supports predicate declaration, detailed in Section 8.1, “Predicate Declaration”.

7.1. Comparisons

Comparison Operations

LogiQL provides comparison operations for primitive types datetime, boolean, string, int, float, and decimal. Equality and disequality (= and !=) are also available for entity types (Section 8.3, “Entity Predicates”).

Only values of the same type can be compared. For example, a value of type float cannot be directly compared with a value of type decimal, neither could a value of type decimal be directly compared with a value of type boolean or type string.

If one wants to compare values of different types, an explicit conversion must first be performed (see the section called “X:Y:convert”). Please note that there is no facility for conversion between different entity types.

Comparison operations are binary, and each of them has two forms: an infix operator and a prefix binary relation. The following table lists the supported comparison operations in both forms.

Operator Prefix form Example
= T:eq_2(x, y) 3 = x, int:eq_2(3, x)
!= T:ne_2(x, y) 3.0d != x, decimal:ne_2(3.0d, x)
< T:lt_2(x, y) 3 < 4, int:le_2(3, 4)
> T:gt_2(x, y) 3.1f > x, float:ge_2(3.1f, x)
<= T:le_2(x, y) 3 <= 4, int:le_2(3, 4)
>= T:ge_2(x, y) "ab" >= "aab", string:ge_2("ab", "aab")

Note

T can be datetime, string, boolean, int, float, or decimal. Comparing two values of different types will cause a compile-time error:

Example 7.1. Invalid use of comparison

3.0d > 4.57f
"3.0" = 3.0

Comparison Functions

LogiQL also supports prefix binary functions for comparison, where the two arguments of the function are the values being compared, and the result of the function is a boolean value. If a comparison holds, the result is true; otherwise, it is false.

Prefix function Example
T:eq_3[x,y] = z int:eq_3[3, 4] = false, string:eq_3["foo", "bar"] = false
T:ne_3[x,y] = z int:ne_3[3, 4] = true, string:ne_3["foo", "bar"] = true
T:lt_3[x,y] = z int:lt_3[4, 4] = false, float:lt_3[3.1f, 5.2f] = true
T:gt_3[x,y] = z int:gt_3[4, 4] = false, float:gt_3[3.1f, 5.2f] = false
T:le_3[x,y] = z int:le_3[4, 4] = true, float:le_3[3.1f, 5.2f] = true
T:ge_3[x,y] = z int:ge_3[4, 4] = true, float:ge_3[3.1f, 5.2f] = false

Note T can be datetime, string, boolean, int, float, or decimal. Just as for comparison relations above, an attempt to use these functions to compare two values of different types will cause a compile-time error.

Comparison of strings according to locale

The comparisons for strings described above simply compare the underlying byte representations in lexicographic order. We also provide operations that compare Unicode strings according to locale.

These take the form of three-argument operations, where the first argument is the locale, and the other two are the strings to be compared. There are no corresponding infix operators.

Operation Meaning
ustring:eq_3(loc, x, y) x = y
ustring:ne_3(loc, x, y) x != y
ustring:lt_3(loc, x, y) x < y
ustring:gt_3(loc, x, y) x > y
ustring:le_3(loc, x, y) x <= y
ustring:ge_3(loc, x, y) x >= y

Just as for other comparisons, LogiQL supports also the corresponding boolean functions: ustring:eq_4[localeString, leftString, rightString] = booleanResult, etc.

Currently the locale is only the language, specified in the form of a string. The string is a lower case two-letter or three-letter ISO-639 code: "en" for English, "sv" for Swedish etc.

Example 7.2. Comparison of Unicode strings according to locale

create --unique

addblock <doc>
  data1("lęk").  data2("łąk").

  data1("mot").  data2("måt").

  p(a, b) <- a < b,                    data1(a), data2(b).  // bytes
  q(a, b) <- ustring:lt_3("en", a, b), data1(a), data2(b).  // English
  r(a, b) <- ustring:lt_3("pl", a, b), data1(a), data2(b).  // Polish
  s(a, b) <- ustring:lt_3("sv", a, b), data1(a), data2(b).  // Swedish
</doc>
echo --- Byte ordering:
print p
echo --- English:
print q
echo --- Polish:
print r
echo --- Swedish:
print s

close --destroy

In Polish the letter ł comes after l. In Swedish the letter å comes after o.

created workspace 'unique_workspace_2017-02-22-19-26-48'
added block 'block_1Z1BK9GV'
--- Byte ordering:
"lęk" "måt"
"lęk" "łąk"
"mot"  "måt"
"mot"  "łąk"
--- English:
"lęk" "måt"
--- Polish:
"lęk" "måt"
"lęk" "łąk"
--- Swedish:
"lęk" "måt"
"mot"  "måt"
deleted workspace 'unique_workspace_2017-02-22-19-26-48'

The number of tuples is different for each locale, because the orderings are different. For example, in the Polish locale "lęk" precedes "łąk", but in the Swedish locale "łąk" precedes "lęk" (though most Swedes are unaware of this).

Condition Function

LogiQL supports a condition function, T:cond[c,x,y] = z, where T can be any type: primitive or entity (Section 8.3, “Entity Predicates”). cond has a semantics similar to if-then-else in other programming languages: if condition c holds, then variable z is assigned the value of x, otherwise, z gets the value of y.

Example 7.3. 

int:cond[true, 3, 5] = z

instantiates z to 3, but

string:cond[int:gt_3[3, 5], "yes", "no"] = z

instantiates z to "no", because the condition evaluates to false.

7.2. Arithmetic Operators

LogiQL provides a set of built-in functional predicates that allow us to do arithmetic on the primitive types. Additionally, the language features some basic operators that allow us to write traditional arithmetic expressions.

For example, x = y + z * 2 is equivalent to x = y + (z * 2). If x, y and z are integers, then this is equivalent to x = int:add[y, int:multiply[z, 2]].

Since the underlying built-in functional predicates (such as int:add) are used much less frequently than the operators, we describe them in this section.

+

The infix addition operator + can be applied to two values of the same numeric type. If you wish to add values of two different numeric types, e.g., int and decimal, an explicit conversion must be applied to one of the arguments. See Section 7.9, “Conversions” for details about conversion functions.

Additionally, LogiQL supports the prefix form T:add[x,y] = z, where T can be int, decimal, or float.

For int and float, the result of an addition is correct only if it is within range (see the section called “int” and the section called “float”). If the correct result is out of range, the returned result will be some incorrect value, and there will be no indication of an error. For example, 9223372036854775807 + 1 will yield -9223372036854775808.

For addition of decimal values, overflow is treated as logical failure (i.e., it does not produce a value).

*

The infix multiplication operator * can be applied to two values of the same numeric type. If you wish to apply the operator to two values of different numeric types, e.g., int and decimal, an explicit conversion must be applied to one of the arguments. See Section 7.9, “Conversions” for details about conversion functions.

Additionally, LogiQL supports three prefix forms of multiplication:

  • T:multiply[x, y] = z, where T can be int, decimal, or float, and both x and y mut be of type T.
  • decimal:int:multiply[x, y] = z, where a decimal value x can be multiplied by an int value y to produce a decimal value z.
  • int:decimal:multiply[x, y] = z, where an int value x can be multiplied by a decimal value y to produce a decimal value z.

For multiplications that result in a decimal value, overflow is treated as logical failure (i.e., no value is produced). For other forms of multiplication the result is correct only if it is within range.

Example 7.4. 

3 * 4 = 12
int:multiply[3, float:int:convert[3.7f]] = 9
decimal:int:multiply[0.12345, 100] = 12.345
int:decimal:multiply[100, 0.12345] = 12.345

-

The infix subtraction operator - can be applied to two values of the same numeric type. If you wish to subtract values of two different numeric types, e.g., int and decimal, an explicit conversion must be applied to one of the arguments. See Section 7.9, “Conversions” for details about conversion functions.

Additionally, LogiQL supports the prefix form T:subtract[x,y] = z, where T can be int, decimal, or float.

For decimal subtraction overflow/underflow is treated as logical failure (i.e., no value is produced). For the other numeric types the result is correct only if it is within range.

Note that LogiQL does not support (unary) arithmetic negation. To compute -x for variable x, you can subtract x from 0 (i.e., 0 - x) or use the T:negate function.

/

The infix division operator / can be applied to two values of the same numeric type. If you wish to divide two values of different numeric types, e.g., int and decimal, an explicit conversion must be applied to one of the arguments. See Section 7.9, “Conversions” for details about conversion functions.

Additionally, LogiQL supports the prefix form T:divide[x,y] = z, where T can be int, decimal, or float.

For decimal fixed-point division, overflow is handled as logical failure (i.e., no value is produced). For other types the value is correct only if it is within range (except when we divide by zero: see below).

For integer and decimal types, division by 0 results in logical failure (i.e., no value is produced). For the floating point type, division by 0 succeeds, resulting in infinity for positive values, and negative infinity for negative values.

Example 7.5. 

   -> !decimal:divide[12.0, 0.0] = _.

7.3. Arithmetic Functions

Note

Not all the functions that are defined for int are also defined for int128. You can assume a function is defined for int128 only if that type is mentioned in the description.

decimal:int:multiply

decimal:int:multiply[x, y] = z -> decimal(x), int(y), decimal(z).

See the section called “*”.

float:arccos

float:arccos[x]=y -> float(x), float(y).

Calculates the arccosine of x (for x in the interval [-1,1]).

Example 7.6. 

float:arccos[1f] = 0f.

float:arcsin

float:arcsin[x]=y -> float(x), float(y).

Calculates the arcsine of x (for x in the interval [-1,1]).

Example 7.7. 

float:arcsin[0f] = 0f.

float:arctan

float:arctan[x]=y -> float(x), float(y).

Calculates the arctangent of x.

Example 7.8. 

float:arctan[0f] = 0f.

float:exp

float:exp[x] = y -> float(x), float(y).

The exponential function, inverse of the natural log function. The x-th power of the Euler number e is y.

Example 7.9. 

float:exp[2.0f] = 7.3890569893f.
float:ln[float:exp[2.0f]] = 2.0f.

float:ln, float:log, float:log10

float:ln[x]    = y -> float(x), float(y).
float:log[x]   = y -> float(x), float(y).
float:log10[x] = y -> float(x), float(y).

Logarithm functions: ln and log compute the natural logarithm of x, and log10 computes the base-10 logarithm of x.

float:pow

float:pow[x,y] = z -> float(x), float(y), float(z).

Computes x raised to the power y, and assigns the result to z.

Example 7.10. 

float:pow[10.0f,2.0f] = 100.0f.

float:sqrt

float:sqrt[x] = y -> float(x), float(y).

Calculates the non-negative square root of x, for non-negative x. If x is negative, there is a logical failure (i.e., no value is produced).

Example 7.11. 

float:sqrt[4.0f] = 2.0f.

float:tan

float:tan[x] = y -> float(x), float(y).

Calculates the tangent of x (given in radians).

Example 7.12. 

float:tan[pi[]/2.0f] = 1.0f.

int:decimal:multiply

int:decimal:multiply[x, y] = z -> int(x), decimal(y), decimal(z).

See the section called “*”.

int:mod

int:mod[x, y] = z -> int(x), int(y), int(z).

Computes the remainder of dividing x by y. If y = 0, there is a logical failure (i.e., no value is produced).

Example 7.13. 

int:mod[10, 2] = 0.
int:mod[11, 2] = 1.
int:mod[7, 10] = 7.
int:mod[-3, 10] = -3.
int:mod[3, -10] = 3.
int:mod[123, -10] = 3.
int:mod[-123, -10] = -3.
int:mod[-123, 10] = -3.
-> !int:mod[5, 0] = _.

T:abs

int:abs[x]     = y -> int(x),     int(y).
decimal:abs[x] = y -> decimal(x), decimal(y).
float:abs[x]   = y -> float(x),   float(y).
int128:abs[x]  = y -> int128(x),  int128(y).

Calculates the absolute value y of x.

Example 7.14. 

decimal:abs[-0.532] = 0.532.
float:abs[-0.532f] = 0.532f.
int:abs[-532] = 532.

T:add

int:add[x, y]     = z -> int(x),     int(y),     int(z).
decimal:add[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:add[x, y]   = z -> float(x),   float(y),   float(z).
int128:add[x, y]  = z -> int128(x),  int128(y),  int128(z).

See the section called “+”.

T:divide

int:divide[x, y]     = z -> int(x),     int(y),     int(z).
decimal:divide[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:divide[x, y]   = z -> float(x),   float(y),   float(z).
int128:divide[x, y]  = z -> int128(x),  int128(y),  int128(z).

See the section called “/”.

T:max

int:max[x, y]     = z -> int(x),     int(y),     int(z).
decimal:max[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:max[x, y]   = z -> float(x),   float(y),   float(z).
int128:max[x, y]  = z -> int128(x),  int128(y),  int128(z).

Returns the larger of x or y.

T:min

int:min[x, y]     = z -> int(x),     int(y),     int(z).
decimal:min[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:min[x, y]   = z -> float(x),   float(y),   float(z).
int128:min[x, y]  = z -> int128(x),  int128(y),  int128(z).

Returns the smaller of x or y.

T:multiply

int:multiply[x, y]     = z -> int(x),     int(y),     int(z).
decimal:multiply[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:multiply[x, y]   = z -> float(x),   float(y),   float(z).
int128:multiply[x, y]  = z -> int128(x),  int128(y),  int128(z).

See the section called “*”.

T:negate

int:negate[x]     = r -> int(x),     int(r).
decimal:negate[x] = r -> decimal(x), decimal(r).
float:negate[x]   = r -> float(x),   float(r).
int128:negate[x]  = r -> int128(x),  int128(r).

The result r equals -x.

Example 7.15. Arithmetic negation

create --unique

addblock <doc>
  p[]  = int:negate[3].
  pp[] = int:negate[int:negate[3]].

  q[]  = decimal:negate[3.2d].
  qq[] = decimal:negate[decimal:negate[3.2d]].

  r[] = float:negate[3.4e-3].
  rr[] = float:negate[float:negate[3.4e-3]].
</doc>
print p
print pp
print q
print qq
print r
print rr

close --destroy

will print

-3
3
-3.2
3.2
-0.0034
0.0034

T:range (arithmetic sequences)

int:range(start, end, stride, x) ->
      int(start), int(end), int(stride), int(x).
decimal:range(start, end, stride, x) ->
      decimal(start), decimal(end), decimal(stride), decimal(x).
float:range(start, end, stride, x) ->
      float(start), float(end), float(stride), float(x).

The range() predicate is usually used to specify a sequence, for example,

int:range(100, 200, 1, x)

specifies the values x = 100, 101, 102, ..., 200. To get the sequence 100, 110, 120, ... one would use stride = 10.

T:range(start, end, stride, x) is true when there is an integer i such that x = start + i * stride, and

  • either stride > 0 and (start ≤ x ≤ end);
  • or stride < 0 and (end ≤ x ≤ start).

The integer i is restricted to the range 0 ≤ i < 2^64.

Note

Using stride < 0 is deprecated, since the order in which values are produced is unspecified; int:range(10, 0, -1, x) is logically equivalent to int:range(0, 10, 1, x).

Note

A straightforward implementation would set x = start and then repeat x += stride. This does not work properly for floating-point types:

  • Repeated addition results in cumulative precision loss. For example, in float:range(-1000000f, 0f, 0.1f, x), which counts from -1000000 to 0 in increments of 0.1, we'd expect a sequence of 10000001 numbers, with the last two values being -0.1, 0; but if repeated addition is used, one gets a sequence of 9296504 numbers, with the last two values being -0.158386, -0.0583855. (This is the result in version 3.9.x of the system.)

  • In floating-point arithmetic, if the stride is sufficiently small then one has x + stride = x, i.e., adding the stride makes no change to x. For example, in float:range(1.0f,1.00001f,1E-10f,x), adding 1.0 + 1E-10 yields 1.0. (Version 3.9.x goes into an infinite loop for this example.)

The current LogicBlox system implements range() for floating-point types by calculating x = start + i * stride for each value to avoid the precision loss of x += stride. It uses an efficient search algorithm to enumerate values of i that produce distinct values of x; for example, float:range(1.0f, 1.00001f, 1E-10f, x) finds i = 0, 597, 1789, 2981, 4173, ... and produces the values x = 1.0000000000, 1.0000001192, 1.0000002384, ..., etc.

T:subtract

int:subtract[x, y]     = z -> int(x),     int(y),     int(z).
decimal:subtract[x, y] = z -> decimal(x), decimal(y), decimal(z).
float:subtract[x, y]   = z -> float(x),   float(y),   float(z).
int128:subtract[x, y]  = z -> int128(x),  int128(y),  int128(z).

See the section called “-”.

7.4. Rounding Functions

7.4.1. Rounding Functions for Decimal Fixed-Point

decimal:ceil

decimal:ceil[x] = y -> decimal(x), decimal(y).

Rounds the decimal value x to the smallest integral value y not smaller than x. This means that positive as well as negative values are rounded towards positive infinity.

Overflow is handled as logical failure. For the ceil function this is only possible on inputs between 10^18 - 1 and 10^18.

Example 7.16. 

decimal:ceil[10.9] = 11.0.
decimal:ceil[11.0] = 11.0.
decimal:ceil[-10.9] = -10.0.

decimal:floor

decimal:floor[x] = y -> decimal(x), decimal(y).

Rounds the decimal value x to the largest integral value y not greater than x. This means that positive as well as negative values are rounded towards negative infinity.

Overflow is handled as logical failure. For the floor function this is only possible on inputs between -10^18 and -10^18 + 1.

Example 7.17. 

decimal:floor[ 10.9] =  10.0.
decimal:floor[ 11.0] =  11.0.
decimal:floor[-10.9] = -11.0.

decimal:round

decimal:round[x] = y -> decimal(x), decimal(y).

Rounds to the nearest integer; a tie is broken by rounding towards an even value. Overflow does not produce a value.

This function is equivalent to decimal:roundHalfToEven.

Example 7.18. 

decimal:round[10.9] = 11.0.
decimal:round[10.1] = 10.0.
decimal:round[10.5] = 10.0.
decimal:round[11.5] = 12.0.
decimal:round[-10.9] = -11.0.
decimal:round[-10.1] = -10.0.
decimal:round[-10.5] = -10.0.
decimal:round[-11.5] = -12.0.

decimal:round2

decimal:round2[x, n] = y -> decimal(x), int(n), decimal(y).

Rounds the decimal value at the n'th digit of the fraction. For n = 0, this function is equilvalent to decimal:round.

A tie is broken towards an even value. This function is equivalent to decimal:roundHalfToEven2.

If n is negative, then the integral part is rounded instead. For example, for n=1 the decimal value is rounded to a multiple of ten, and for n=2 to a multiple of hundred.

Overflow does not produce a value.

Example 7.19. 

decimal:round2[123.4567,  3] = 123.457 .
decimal:round2[123.4567,  2] = 123.46 .
decimal:round2[123.4567,  1] = 123.5 .
decimal:round2[123.4567,  0] = 123.0 .
decimal:round2[123.4567, -1] = 120 .
decimal:round2[123.4567, -2] = 100 .

decimal:roundHalf*

decimal:roundHalfToEven  [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfToEven2 [x, n] = y -> decimal(x), int(n), decimal(y)

decimal:roundHalfToOdd   [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfToOdd2  [x, n] = y -> decimal(x), int(n), decimal(y)

decimal:roundHalfDown  [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfDown2 [x, n] = y -> decimal(x), int(n), decimal(y)

decimal:roundHalfUp    [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfUp2   [x, n] = y -> decimal(x), int(n), decimal(y)

decimal:roundHalfAwayFromZero  [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfAwayFromZero2 [x, n] = y -> decimal(x), int(n), decimal(y)

decimal:roundHalfTowardZero    [x   ] = y -> decimal(x),         decimal(y).
decimal:roundHalfTowardZero2   [x, n] = y -> decimal(x), int(n), decimal(y).

Rounds similarly to decimal:round and decimal:round2, but with specific tie-breaking policies.

Example 7.20. 

decimal:roundHalfUp[0.49999] = 0.0.
decimal:roundHalfUp[-0.49999] = 0.0.
decimal:roundHalfUp[22.7] = 23.0.
decimal:roundHalfUp[-22.7] = -23.0.
decimal:roundHalfUp[22.5] = 23.0.
decimal:roundHalfUp[-22.5] = -22.0.

7.4.2. Rounding Functions for Binary Floating-Point

float:ceil

float:ceil[x] = y -> float(x), float(y).

Rounds the float value x to the smallest integral value y not smaller than x. This means that positive as well as negative values are rounded towards positive infinity.

Example 7.21. 

float:ceil[3.141592f] = 4.0f.
float:ceil[-0.532f] = 0.0f.

float:floor

float:floor[x] = y -> float(x), float(y).

Rounds the decimal value x to the largest integral value y not greater than x. This means that positive as well as negative values are rounded towards negative infinity.

Example 7.22. 

float:floor[3.141592f] = 3.0f.
float:floor[-0.532f] = -1.0f.

float:round

float:round[x] = y -> float(x), float(y).

Rounds to the nearest integer; a tie is broken by rounding towards an even value.

Example 7.23. 

float:round[0.121f] = 0.0f.
float:round[0.873f] = 1.0f.
float:round[0.5f] = 0.0f.    // Round to 0 (since 0 is even)
float:round[1.5f] = 2.0f.    // Round to 2 (since 2 is even)

float:roundHalfUp

float:roundHalfUp[x] = y -> float(x), float(y).

Rounds to the nearest integer; a tie is broken by rounding towards positive infinity.

Example 7.24. 

float:roundHalfUp[23.5f] = 24.0f
float:roundHalfUp[-23.5f] = -23.0f

7.5. Integer Bit Manipulation Functions

int:bit_not

int:bit_not[x] = y -> int(x), int(y).

Returns the bitwise complement of x. That is, all bits that were 0 in x become 1 in y, and those that were 1 in x become 0 in y.

Example 7.25. 

int:bit_not[4] = -5.
int:bit_not[0] = -1.
int:bit_not[-1] = 0.

int:bit_and

int:bit_and[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise logical 'and' of the integers x and y. If bits at the same offset of x and y are both 1, then the bit at the same offset of z will also be 1. Otherwise the bit at that offset will be 0.

Example 7.26. 

int:bit_and[2, 4] = 0.
int:bit_and[1, 3] = 1.
int:bit_and[0b1111, 0b1010] = 0b1010.

int:bit_or

int:bit_or[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise logical 'or' of the integers x and y. If either bit at the same offset of x and y is 1, then the bit at the same offset of z will also be 1. Otherwise the bit at that offset will be 0.

Example 7.27. 

int:bit_or[2, 4] = 6.
int:bit_or[-1, 3] = -1.
int:bit_or[0b1010, 0b0101] = 0b1111.

int:bit_xor

int:bit_xor[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise logical 'xor' of the integers x and y. If one of the bits at the same offset of x and y is 1, but not both, then the bit at the same offset of z will also be 1. Otherwise the bit at that offset will be 0.

Example 7.28. 

int:bit_xor[-1, -1] = 0.
int:bit_xor[2, 4] = 6.
int:bit_xor[0b1111, 0b1010] = 0b0101.
int:bit_xor[0b1010, 0b0101] = 0b1111.

int:bit_lshift

int:bit_lshift[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise left shift of the integer x by the integer y. This shifts the bits of x "higher" by y positions. As a result, y bits are truncated off the high end of x and then the y low bits are padded with 0.

It is required that y be greater than or equal to 0 and less than the maximum number of bits in the integer (64 in the current implementation of the integer type). If y is outside this range, no value is bound for z.

Example 7.29. 

int:bit_lshift[1, 1] = 2.
int:bit_lshift[2, 1] = 4.
int:bit_lshift[-1, 1] = -2.
int:bit_lshift[0b1111, 1] = 0b11110.
int:bit_lshift[0b1010, 2] = 0b101000.

int:bit_rshift

int:bit_rshift[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise right shift of the integer x by the integer y. This shifts the bits of x "lower" by y positions. As a result, y bits are truncated off the low end of x and then the y high bits are padded with 0.

It is required that y be greater than or equal to 0 and less than the maximum number of bits in the integer (64 in the current implementation of the integer type). If y is outside this range, no value is bound for z.

Example 7.30. 

int:bit_rshift[1, 1] = 0.
int:bit_rshift[2, 1] = 1.
int:bit_rshift[-1, 1] = 9223372036854775807.
int:bit_rshift[-2, 1] = 4611686018427387903.
int:bit_rshift[0b1111, 1] = 0b111.
int:bit_xor[0b1010, 2] = 0b10.

int:bit_rshiftse

int:bit_rshiftse[x, y] = z -> int(x), int(y), int(z).

Returns the bitwise right shift of the integer x by the integer y, but with sign extension. This shifts the bits of x "lower" by y positions. As a result, y bits are truncated off the low end of x and then the y high bits are padded with 0 if the high (sign) bit of x is 0, otherwise they are padded with 1.

It is required that y be greater than or equal to 0 and less than the maximum number of bits in the integer (64 in the current implementation of the integer type). If y is outside this range, no value is bound for z.

Example 7.31. 

int:bit_rshiftse[1, 1] = 0.
int:bit_rshiftse[2, 1] = 1.
int:bit_rshiftse[-1, 1] = -1.
int:bit_rshiftse[-2, 1] = -1.
int:bit_rshiftse[-4, 1] = -2.
int:bit_rshiftse[0b1111, 1] = 0b111.
int:bit_rshiftse[0b1010, 2] = 0b10.

7.6. String Operations

Note

The basic string operations work directly on the byte representations of strings. In the UTF-8 encoding some Unicode characters are represented by sequences of two or three bytes, so these string operations may give surprising results.

For cases where this matters, we have recently added "Unicode-aware" string operations. Each of these operations has a name that begins with ustring: instead of string:. So, for example, string:length[s] will be the number of bytes in string s, whereas ustring:length[s] will be the number of Unicode characters in s.

At least for the time being, LogiQL sports both variants of the operations. This preserves upwards compatibility with older versions, but also allows one to use the basic operations in applications for which full support of Unicode is not needed: the basic operations are somewhat more efficient.

Backward compatibility is broken in the case of regular expressions. Earlier versions of string:match, string:matches and string:rewrite used the Posix regular expression library. To avoid spurious differences with ustring:matches etc. all these operations now use Perl-like syntax for regular expressions (in the version supported by the ICU library). However, replacement patterns can be written both in the Perl-like form (e.g., "$1") and in Posix-like form (e.g., "\\1" or """\1"""). For all but the more exotic cases the difference between the two forms of regular expressions should not be noticeable.

Warning

The "Unicode-aware" operations have been introduced in LogicBlox version 4.4.5, and are not available in earlier versions.

string:add

string:add[s1, s2] = r -> string(s1), string(s2), string(r). 

The result r is the concatenation of the strings s1 and s2.

Example 7.32. 

string:add["the", string:add[" quick", " brown"]] = "the quick brown".

Note

The symbol + may be used as shorthand.

Example 7.33. 

"the" + " quick" + " brown" = "the quick brown".

string:alpha_num / ustring:alpha_num

string:alpha_num(s) -> string(s).
ustring:alpha_num(s) -> string(s). 

True if the string s consists of digits and/or letters only. string:alpha_num does not handle multi-byte characters properly, ustring:alpha_num does.

Example 7.34. 

string:alpha_num("aZ123").   // true
string:alpha_num("ah!").     // false
string:alpha_num(" aZ123").  // false

Example 7.35. 

create --unique

addblock <doc>
  data("10 części!").

  p(n, ch) <-  string:alpha_num(ch), ch = ustring:at[s, n], data(s).
  q(n, ch) <- ustring:alpha_num(ch), ch = ustring:at[s, n], data(s).
</doc>
print data
echo --- p ---
print p
echo --- q ---
print q

close --destroy

yields

created workspace 'unique_workspace_2017-02-18-13-26-37'
added block 'block_1Z1BKQCU'
"10 części!"
--- p ---
0 "1"
1 "0"
3 "c"
4 "z"
7 "c"
8 "i"
--- q ---
0 "1"
1 "0"
3 "c"
4 "z"
5 "ę"
6 "ś"
7 "c"
8 "i"
deleted workspace 'unique_workspace_2017-02-18-13-26-37'

string:at / ustring:at

string:at[s, n] = c -> string(s), int(n), int(c).
ustring:at[s, n] = c -> string(s), int(n), string(c).

string:at returns the integer value of the n-th byte in the sequence of bytes that represent the characters of the string s. Counting starts from 0.

ustring:at returns a string containing the single character that is n-th character of the string s. Counting starts from 0.

If n is larger than the number of bytes/characters in the string, the operation silently fails.

Note

Both string:at[s, n] = c and ustring:at[s, n] = c can be used with the variable n unbound, to iterate over the bytes/characters in a string.

Example 7.36.  string:at vs. ustring:at

create --unique

addblock <doc>
  euro("12\u20AC!").

  p(n, c)  <- c  =  string:at[s, n], euro(s).
  q(n, ch) <- ch = ustring:at[s, n], euro(s).
</doc>
print euro
echo -- p --
print p
echo -- q --
print q

close --destroy

results in:

created workspace 'unique_workspace_2017-02-06-00-44-16'
added block 'block_1Z1BJU0Q'
"12€!"
-- p --
0 49
1 50
2 -30
3 -126
4 -84
5 33
-- q --
0 "1"
1 "2"
2 "€"
3 "!"
deleted workspace 'unique_workspace_2017-02-06-00-44-16'

string:hash

string:hash[s] = n -> string(s), int(n). 

Returns a hash value for the given string.

string:length / ustring:length

string:length[s] = n -> string(s), int(n).
ustring:length[s] = n -> string(s), int(n). 

string:length returns the number of bytes used for representing the characters of string s.

ustring:length returns the number of Unicode characters in string s.

Example 7.37. 

create --unique

addblock <doc>
  euro("\u20AC").

  p(n) <- n =  string:length[s], euro(s).
  q(n) <- n = ustring:length[s], euro(s).
</doc>
print euro
print p
print q

close --destroy

The result is:

created workspace 'unique_workspace_2017-02-05-20-48-24'
added block 'block_1Z1BKPXR'
"€"
3
1
deleted workspace 'unique_workspace_2017-02-05-20-48-24'

string:like / string:notlike / ustring:like / ustring:notlike

string:like(s, pattern)    -> string(s), string(pattern).
string:notlike(s, pattern) -> string(s), string(pattern).

ustring:like(s, pattern)    -> string(s), string(pattern).
ustring:notlike(s, pattern) -> string(s), string(pattern). 

string:like checks if the string s matches a wildcard pattern. A pattern may contain the character _, which represents a single character, or %, which matches any string of zero or more characters.

Use \% and \_ to match the percent-sign character and the underscore character, respectively. (Note that within a single-line string the \ itself must be escaped, so to match every string that contains a percent sign one would use the pattern "%\\%%" or """%\%%""".)

string:notlike can be used to find strings that do not match a pattern.

string:like and string:notlike treat both the string and the pattern as a sequence of bytes, which may lead to unexpected results in the presence of multi-byte Unicode characters. ustring:like and ustring:notlike treat both strings as sequences of Unicode characters. See Example 7.39, “ string:like vs. ustring:like.

Example 7.38. 

string:like("226-223-4921", "226%").     // true: starts with "226"
string:like("226-223-4921", "%223%").    // true: contains substring "223"
string:like("226-223-4921", "%21").      // true: ends with "21"
string:like("226-223-4921", "%2%3%4%").  // true: contains '2', '3', '4'

string:notlike("226-223-4921", "226%").  // false: starts with "226"
string:notlike("226-223-4921", "99-%").  // true: does not start with "99-"

string:like("_%", "\\_%\\%").            // true: starts with '_', ends with '%'
string:like("_%.", "\\_%\\%" ).          // false: does not end with '%'

Example 7.39.  string:like vs. ustring:like

create --unique

addblock <doc>
  data("området").
  data("öken").

  p(s) <-  string:like(s, "%r_d%"),  data(s).
  q(s) <- ustring:like(s, "%r_d%"),  data(s).
</doc>
echo --- p ---
print p
echo --- q ---
print q

close --destroy

string:like does not treat å as a single character:

created workspace 'unique_workspace_2017-02-18-21-13-34'
added block 'block_1Z1BMEKY'
--- p ---
--- q ---
"området"
deleted workspace 'unique_workspace_2017-02-18-21-13-34'

string:lower / ustring:lower

string:lower[s] = r -> string(s), string(r).
ustring:lower[s] = r -> string(s), string(r). 

string:lower converts the characters of the string to lower-case.

ustring:lower converts the characters of the string to lower case, treating Unicode characters properly.

Example 7.40. 

string:lower["PSMITH"] = "psmith".

See also Example 7.62, “Converting to lower and upper case”.

string:match / ustring:match

string:match(s, regex) -> string(s), string(regex).
ustring:match(s, regex) -> string(s), string(regex). 

True if the string s matches regex. The latter is a regular expression.

Just like string:matches, string:match can give unexpected results if the regular expression contains multi-byte Unicode characters. ustring:match will work as expected.

Example 7.41. string:match

string:match("226-223-4921", """\d+-\d+-\d+""").  // true
string:match("226-223-4921", """\l+-\l+-\l+""").  // false

Example 7.42.  string:match vs. ustring:match

create --unique

addblock <doc>
  p(1) <-   string:match("źźźżż", "(ź*)(ż*)").
  q(1) <-  ustring:match("źźźżż", "(ź*)(ż*)").
</doc>
echo -- p --
print p
echo -- q --
print q

close --destroy

string:match cannot find a match, because the asterisk is applied not to the entire character, but only to its last byte:

created workspace 'unique_workspace_2017-02-17-20-08-46'
added block 'block_1Z1BIXZU'
-- p --
-- q --
1
deleted workspace 'unique_workspace_2017-02-17-20-08-46'

string:matches / ustring:matches

string:matches[s, regex, n] = subexp
   -> string(s), string(regex), int(n), string(subexp).

ustring:matches[s, regex, n] = subexp
   -> string(s), string(regex), int(n), string(subexp). 

regex is a regular expression.

Any one of these two operations matches the string s against the regular expression regex and returns the n-th matching subexpression. The operation fails if regex does not match the entire string s.

"Matching subexpression" number 0 is the entire string. The other matching subexpressions are the substrings that match those parts of regex that are enclosed in parentheses. These parts are numbered from left to right, and nested parenthesized parts are numbered before the next parenthesized part.

If an alternative subexpression matches, then there is no attempt to match the next alternative, if any. Those alternatives that did not take part in the final match are shown as matching empty strings.

All this is illustrated by the example below.

If the regular expression contains multi-byte Unicode characters, then string:matches might not give the expected effect, unlike ustring:matches. See Example 7.44, “ string:matches vs. ustring:matches.

Note

Both string:matches[s, regex, n] = subexp and ustring:matches[s, regex, n] = subexp can be used with the variable n unbound, to iterate over the matching subexpressions.

Warning

Before LogicBlox version 4.4.2 string:matches did not work quite correctly even on strings with single-byte characters. (ustring:matches was not yet available.)

Example 7.43. string:matches

The following rules attempt to match the same string with various regular expressions.

p(n, s) <- s = string:matches["aaabc", "((a*)b.)", n].
q(n, s) <- s = string:matches["aaabc", "(a*)(b)(.)", n].
r(n, s) <- s = string:matches["aaabc", "a", n].
s(n, s) <- s = string:matches["aaabc", "(a)", n].
t(n, s) <- s = string:matches["aaabc", "a*..", n].
u(n, s) <- s = string:matches["aaabc", "(a*)(..)", n].
v(n, s) <- s = string:matches["aaabc", "(a)*(..)", n].
w(n, s) <- s = string:matches["aaabc", "(a*)((a)(.))(.)", n].
x(n, z) <- z = string:matches["aaabc", "(a*(b.*))|aa(.*)", n].
y(n, z) <- z = string:matches["aaabc", "(a(b.*))|aa(.*)", n].
z(n, z) <- z = string:matches["aaabc", "(a*)(b).", n], n = 1.

The results are:

+- p
|  0,"aaabc"
|  1,"aaabc"
|  2,"aaa"
+- p [end]
+- q
|  0,"aaabc"
|  1,"aaa"
|  2,"b"
|  3,"c"
+- q [end]
+- r
+- r [end]
+- s
+- s [end]
+- t
|  0,"aaabc"
+- t [end]
+- u
|  0,"aaabc"
|  1,"aaa"
|  2,"bc"
+- u [end]
+- v
|  0,"aaabc"
|  1,"a"
|  2,"bc"
+- v [end]
+- w
|  0,"aaabc"
|  1,"aa"
|  2,"ab"
|  3,"a"
|  4,"b"
|  5,"c"
+- w [end]
+- x
|  0,"aaabc"
|  1,"aaabc"
|  2,"bc"
|  3,""
+- x [end]
+- y
|  0,"aaabc"
|  1,""
|  2,""
|  3,"abc"
+- y [end]
+- z
|  1,"aaa"
+- z [end]

The first tuple of p contains the entire string. The second tuple contains the part that matches the entire regular expression, because the latter is enclosed in parentheses. The third tuple contains the part that matches the nested parenthesized expression (a*).

The difference between u and v is caused by the placement of parentheses below or around the Kleene star (i.e., *).

In the rule for x it is the first alternative that matches; in the rule for y it is the second.

z contains just one tuple, because n is given a specific value.

Example 7.44.  string:matches vs. ustring:matches

create --unique

addblock <doc>
  p(n, z) <-  z =  string:matches["źźźżż", "(ź*)(ż*)", n].
  q(n, z) <-  z = ustring:matches["źźźżż", "(ź*)(ż*)", n].
</doc>
echo -- p --
print p
echo -- q --
print q

close --destroy

string:matches cannot find a match, because the asterisk is applied not to the entire character, but only to its last byte:

created workspace 'unique_workspace_2017-02-13-20-52-07'
added block 'block_1Z1BJN1M'
-- p --
-- q --
0 "źźźżż"
1 "źźź"
2 "żż"
deleted workspace 'unique_workspace_2017-02-13-20-52-07'

string:quote

string:quote[s, q] = r -> string(s), string(q), string(r). 

Format string s by escaping non-printable characters and adding quotes. q is the quote symbol, and must be either an empty string, or a string consisting of one single-byte character.

If q is an empty string, then the result is formatted by escaping non-printable characters, but quotes are not added.

The quote can be any character. It is always escaped when found inside the original string, to distinguish it from its uses as a quote. For example, string:quote["x", "x"] = "x\\xx", that is, a string consisting of four characters: x, \, x and x.

See also string:unquote.

Example 7.45. 

string:quote["ab", "\""] = "\"ab\"".

Example 7.46. 

create --unique

addblock <doc>
  p(x) <- x = string:quote["ab\t\rc", "\""].
  q("\"ab\\t\\rc\"").
  r(x) <- p(x), q(x).
</doc>
print p
print q
print r

close --destroy

yields

created workspace 'unique_workspace_2017-03-08-20-51-26'
added block 'block_1Z1BKMRX'
"\"ab\t\rc\""
"\"ab\t\rc\""
"\"ab\t\rc\""
deleted workspace 'unique_workspace_2017-03-08-20-51-26'

In other words, the characters in p are: ", a, b, \, t, \, r, c and ". Before quoting, the string contained a tabulator and a carriage return, after quoting it does not.

string:quote_excel

string:quote_excel[s, q] = r -> string(s), string(q), string(r). 

Very similar to string:quote, but formats the string according to the common practices of Excel. This means that when the quote character specified by q is encountered inside the string, it is not preceded by a backslash, but duplicated.

See also string:unquote_excel.

Example 7.47. 

string:quote_excel["a\"b", "\""] = "\"a\"\"b\"".
string:quote["x", "x"] = "xxxx".

string:replace

string:replace[s, substr, replace] = r
   -> string(s), string(substr), string(replace), string(r). 

Replace occurrences of the substring substr in s with replace. If substr does not occur in s, the result is s. If substr is the empty string, it is deemed to occur before each character and after the last character of s; if s is empty, then the empty substring occurs once.

Example 7.48. 

string:replace["you've got to be kidding", "you've", "you have"]
   = "you have got to be kidding".
string:replace["Warning: person %s not found", "%s", "Waldo"]
   = "Warning: person Waldo not found".
string:replace["babanana", "banana", "nana"] = "banana".
string:replace["xy", "", "What?"] = "What?xWhat?yWhat?".
string:replace["", "", "Really!"] = "Really!".

string:rewrite / ustring:rewrite

string:rewrite[s, regex, replace] = r
   -> string(s), string(regex), string(replace), string(r).

ustring:rewrite[s, regex, replace] = r
   -> string(s), string(regex), string(replace), string(r). 

Rewrite the string s using the regular expression regex and the replacement text replace. If the replacement text is to contain the dollar character ($), it must be escaped with a backslash (\).

More precisely: whenever regex matches a substring of s, that substring is replaced by the expansion of replace. (We use the term "expansion", because replace may include references to parenthesised groups in regex, e.g., \1 is the first such group. See the description of string:matches for more information about how the groups are numbered.)

The matching is "greedy", e.g., "(a*)" will match "aaa" once.

The operation fails only when the regular expression is malformed.

In the presence of multi-byte Unicode characters string:rewrite is brittle. For example, the value of string:rewrite["ééÉÉÉ", "(éé*)(É*)", """\1"""] is ééÉÉ" (which is wrong!). Worse, string:rewrite["éééÉÉÉ", "(éé*)(É*)", """\1"""] may trigger an error:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xa9 in position 4: 'utf8' codec can't decode byte 0xa9 in position 4: invalid start byte in field: blox.connect.StringColumn.values

ustring:rewrite handles all Unicode characters properly.

Example 7.49. 

string:rewrite["afoobbarc", ".*(foo).*(bar).*", """_\1_\2_"""] = "_foo_bar_".
string:rewrite["aa b  c   d\r\ne\n\n", """[ \t\r\n]+""", " "] = "aa b c d e ".
string:rewrite["fabcd", "(a)(b)", """\2"""] = "fbcd".
string:rewrite["faabcbd", "(a*)(b)", """\1"""] = "faacd".            // 4
string:rewrite["eee", "e", "\\0"] = "eee".                           // 5
string:rewrite["fff", "f", "\\1"] = "".                              // 6
ustring:rewrite["ééÉÉÉ", "(éé*)(É*)", """\\2\1"""] = """\2éé""".     // 7
string:rewrite["""abcd""", """(a.c)d""", """A\$C\1\$"""] = "A$Cabc$".
string:rewrite["abcd", "(a.c)d", "A\\$C\\1\\$"] = "A$Cabc$".

In the fourth example the pattern matches two substrings: aab and the second b. After the second match the value of \1 is the empty string, which replaces the b.

In the fifth example \0 represents the entire string.

In the sixth example \1 is the empty string, because there are no parenthesised groups in the pattern.

In the seventh example \\2\1 represents \, followed by 2, followed by matching group number 1.

string:split / ustring:split

string:split[s, delim, n] = token
   -> string(s), string(delim), int(n), string(token).

ustring:split[s, delim, n] = token
   -> string(s), string(delim), int(n), string(token). 

delim must be a string consisting of one character. If it isn't, the operation will silently fail.

Each of these two operations splits the string s at every occurrence of delim and returns the n-th part (counting from 0). If delim does not occur in the string, the entire string is deemed to be part 0. If delim is the last character in the string, then the last part will be an empty string.

Unlike ustring:split, string:split does not work well with multi-byte Unicode characters. For example, in string:split[s, "ä", n] the delimiter will not be recognised as a single character, and the operation will fail.

Note

Both string:split[s, delim, n] = tok and ustring:split[s, delim, n] = tok can be used with the variable n unbound, to iterate over the parts of the string.

Example 7.50. string:split

create --unique

addblock <doc>
  p(n, s) <-  s = string:split["First,Part,23,, Test", ",", n].
</doc>
print p

close --destroy

results in:

created workspace 'unique_workspace_2017-02-17-22-49-35'
added block 'block_1Z1BIXOX'
0 "First"
1 "Part"
2 "23"
3 ""
4 " Test"
deleted workspace 'unique_workspace_2017-02-17-22-49-35'

Example 7.51.  string:split vs. ustring:split

create --unique

addblock <doc>
  data("AaÄäÅåÖöOo").
  data2("chcieć mieć coś to żadna cześć").

  p(n, part) <- part =  string:split[s, "ä", n],  data(s).
  q(n, part) <- part = ustring:split[s, "ä", n],  data(s).
  r(n, part) <- part = ustring:split[s, "ć", n],  data2(s).
</doc>
echo -- p --
print p
echo -- q --
print q
echo -- r --
print r

close --destroy

The results:

created workspace 'unique_workspace_2017-02-13-00-22-50'
added block 'block_1Z1BUQ4N'
-- p --
-- q --
0 "AaÄ"
1 "ÅåÖöOo"
-- r --
0 "chcie"
1 " mie"
2 " coś to żadna cześ"
3 ""
deleted workspace 'unique_workspace_2017-02-13-00-22-50'

string:substring / ustring:substring

string:substring[s, start, length] = r
   -> string(s), int(start), int(length), string(r).

ustring:substring[s, start, length] = r
   -> string(s), int(start), int(length), string(r). 

ustring:substring returns the substring of s starting at position start and containing up to length characters. If length < 0, the result is an empty string; if start < 0, the operation fails.

Note that the first character of a string has position 0, so that, say, ustring:substring[s, 0, ustring:length[s]] returns the full string.

string:substring is very similar, but counts bytes rather than characters. This makes it rather brittle. For example, if an lb script contains

data("AaÄäÅåÖöOo").

p1_4(sub) <- sub = string:substring[s, 1, 4],  data(s).

then the result will be an error message:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xc3 in position 3: 'utf8' codec can't decode byte 0xc3 in position 3: unexpected end of data in field: blox.connect.StringColumn.values

Example 7.52. string:substring

string:substring["SKD50194", 0, 3] = "SKD".
string:substring["SKD50194", 3, 5] = "50194".

// If start+length is past the end of the string, result will be
// less than length characters.
string:substring["SKD50194", 3, 10] = "50194".

// If start is past the end of the string, an empty string results.
string:substring["SKD50194", 10, 4] = "".

Example 7.53. ustring:substring

create --unique

addblock <doc>
  data("AaÄäÅåÖöOo").

  q(sub)      <- sub = ustring:substring[s,  0, ustring:length[s]], data(s).
  q1_4(sub)   <- sub = ustring:substring[s,  1,  4],                data(s).
  q0_14(sub)  <- sub = ustring:substring[s,  0, 14],                data(s).
  q10_14(sub) <- sub = ustring:substring[s, 10, 14],                data(s).
</doc>
print q
print q1_4
print q0_14
print q10_14

close --destroy

The result:

created workspace 'unique_workspace_2017-02-12-20-29-06'
added block 'block_1Z1BJ3LS'
"AaÄäÅåÖöOo"
"aÄäÅ"
"AaÄäÅåÖöOo"
""
deleted workspace 'unique_workspace_2017-02-12-20-29-06'

string:T:convert

string:boolean:convert[s]  = v -> string(s), boolean(v).
string:datetime:convert[s] = v -> string(s), datetime(v).
string:decimal:convert[s]  = v -> string(s), decimal(v).
string:int:convert[s]      = v -> string(s), int(v).
string:float:convert[s]    = v -> string(s), float(v). 

Converts a string to a boolean, datetime, decimal, integer, or floating-point value. If the string is unparseable, or if the resulting value would be out of range, then conversion fails (i.e., produces no value).

Example 7.54. 

string:float:convert["3.1415926535897931"] = 3.1415926535897931
string:int:convert["100"] = 100

Please note that the strings are not the LogiQL literals described in Section 4.2, “Literals”. For example, the value of string:float:convert["1"] is 1.0, but string:float:convert["1f"] will fail.

The system guarantees that converting a value to a string and back will preserve the value.

Example 7.55. 

string:float:convert[
     float:string:convert[3.1415926535897931f]] = 3.1415926535897931.

Note

It is not guaranteed that a conversion from string to value to string will preserve the original string: precision might be lost.

Example 7.56. 

float:string:convert[
   string:float:convert["3.141592653589793238"]] = 3.1415926535897931.
                                            ^^^                     ^

For conversions to boolean all inputs are case-insensitive. The strings "true", "TrUe", etc. are recognized as true. The strings "false", "fALSE", etc. are recognized as false. If s is a string whose lowercase form is neither "false" nor "true", then s is not converted to a boolean (i.e., the attempt to convert fails).

In the case of conversion to decimal numbers (i.e., string:decimal:convert) the string can contain an exponent part of the form en or En, where -20 <= n <= 20. If n has a value outside this range, the conversion will fail. Otherwise the digits will be shifted by n and rounded to 18 decimal digits. If the result is outside the range for LogiQL decimal integers, the conversion will fail. If the result is smaller than 1e-18 and greater than -1e-18, then the produced value will be 0.

Example 7.57. Conversion from string to decimal

string:decimal:convert["0.001e20"] = 100000000000000000
string:decimal:convert["1e-18"]) = 0.000000000000000001
string:decimal:convert["0.1e-18"]) = 0
string:decimal:convert["12345678901234567890.12345678901234565000e-2"] =
     123456789012345678.901234567890123456
string:decimal:convert["12345678901234567890.12345678901234565001E-2"] =
     123456789012345678.901234567890123457
string:decimal:convert["12345678901234567890.1234567890123456e-1"] will fail

string:trim

string:trim[s] = r -> string(s), string(r). 

Removes leading and trailing whitespace from a string.

Example 7.58. 

string:trim["  some words "] = "some words".

string:unquote

string:unquote[s, q] = r -> string(s), string(q), string(r). 

Parses string s by un-escaping non-printable characters and removing outer quotes (if any). q is the quote symbol, and must be either an empty string or a string consisting of one single-byte character.

If q is the empty string, no outer quotes are removed. Otherwise, if s does not begin with the character in q, no outer quotes are removed. In both cases, the escaped non-printable characters are still converted back to their non-escaped form: for example, the two-character sequence \ followed by t is converted to the single character \t (i.e., the tabulator).

If q is correctly formed, then the following holds:

string:unquote[string:quote[s, q], q] = s

Example 7.59. 

string:unquote["\"a\tb\"", "\""] = "a\tb".

string:unquote_excel

string:unquote_excel[s, q] = r -> string(s), string(q), string(r). 

Similar to string:unquote, except that the input string is assumed to conform to Excel-style conventions (i.e., the quote is represented by two quotes rather than an escaped quote).

Example 7.60. 

string:unquote_excel["\"a\"\"b\"", "\""]="a\"b".

string:upper / ustring:upper

string:upper[s] = r -> string(s), string(r).
ustring:upper[s] = r -> string(s), string(r). 

string:upper converts the characters of the string to upper case.

ustring:upper converts the characters of the string to upper case, treating multi-byte Unicode characters properly.

Example 7.61. 

string:upper["Psmith"] = "PSMITH".

Example 7.62. Converting to lower and upper case

create --unique

addblock <doc>
  data("AaÄäÅåÖöOo").

  pu(us)  <- us = string:upper[ s],  data(s).
  pl(ls)  <- ls = string:lower[us],  pu(us).

  qu(us)  <- us = ustring:upper[s],   data(s).
  ql(ls)  <- ls = ustring:lower[us],  qu(us).
</doc>
print data
print pu
print pl
print qu
print ql

close --destroy
ustring:upper["Psmith"] = "PSMITH".

The result is

created workspace 'unique_workspace_2017-02-12-01-13-06'
added block 'block_1Z1BJ2QD'
"AaÄäÅåÖöOo"
"AAÄäÅåÖöOO"
"aaÄäÅåÖöoo"
"AAÄÄÅÅÖÖOO"
"aaääååööoo"
deleted workspace 'unique_workspace_2017-02-12-01-13-06'

7.7. Boolean Operations

boolean:and

boolean:and[x, y] = z -> boolean(x), boolean(y), boolean(z).

Returns true if both x and y are true, false otherwise.

boolean:bitxor

boolean:bitxor[x, y] = z -> boolean(x), boolean(y), boolean(z).

Returns true if exactly one of x and y is true, false otherwise.

boolean:or

boolean:or[x, y] = z -> boolean(x), boolean(y), boolean(z).

Returns false if both x and y are false, true otherwise.

boolean:not

boolean:not[x] = y -> boolean(x), boolean(y).

Returns true if x is false, false otherwise.

7.8. Date/Time Operations

Some of the operations described in this section refer to time zones.

There are three ways to specify a time zone in LogiQL:

  • As a string with an offset from UTC time. For example, "+5:30:OO" (which can also be written as "+5:30" or "+530"). The offset must refer to an existing time zone, possibly adjusted for Daylight Saving Time. (A list of generally recognised time zones is loaded by the system from a configuration file.) The granularity of time zone offsets is 15 minutes, so an offset such as "-1:25" is probably the result of a typo.

  • As a string with an abbreviation of the official name of the time zone. For example, "EST" (which stands for "Eastern Standard Time"). Unfortunately, the names of some of the officially recognised time zones have identical abbreviations (e.g., "IST" is used both for Indian Standard Time and for Israeli Standard Time): the system will treat the abbreviation as representing only one of such zones (the choice is made in a configuration file).

  • As a string with an abbreviation of the of the official name of the time zone when it is in Daylight Saving Time. For example, Detroit is normally in the time zone denoted by EST, but during certain periods of the year the time zone can be denoted by EDT. Not every time zone has such a secondary designation, and if it does exist, then its uniqueness cannot be guaranteed.

datetime:{PART} / datetime:{PART}TZ

datetime:year[d]   = i -> datetime(d), int(i).
datetime:month[d]  = i -> datetime(d), int(i).
datetime:day[d]    = i -> datetime(d), int(i).
datetime:hour[d]   = i -> datetime(d), int(i).
datetime:minute[d] = i -> datetime(d), int(i).
datetime:second[d] = i -> datetime(d), int(i).

datetime:yearTZ[d, tz]   = i -> datetime(d), string(tz), int(i).
datetime:monthTZ[d, tz]  = i -> datetime(d), string(tz), int(i).
datetime:dayTZ[d, tz]    = i -> datetime(d), string(tz), int(i).
datetime:hourTZ[d, tz]   = i -> datetime(d), string(tz), int(i).
datetime:minuteTZ[d, tz] = i -> datetime(d), string(tz), int(i).
datetime:secondTZ[d, tz] = i -> datetime(d), string(tz), int(i).

datetime:PART extracts a component from a datetime and returns it as an integer. The versions with a timezone parameter return the part for the given timezone.

Example 7.63. 

datetime:year[datetime:create[2013, 10, 31, 15, 30, 0]] = 2013.
datetime:hour[datetime:create[2013, 10, 31, 15, 30, 0]] = 15.

datetime:hourTZ[datetime:create[2013, 10, 31, 15, 30, 0], "PST"] = 8.

datetime:add / datetime:subtract

datetime:add[old, offset, resolution] = new
   -> datetime(old), int(offset), string(resolution), datetime(new).
datetime:subtract[old, offset, resolution] = result
   -> datetime(old), int(offset), string(resolution), datetime(result).

datetime:add adds time to a datetime, while datetime:subtract subtracts time. For both, the resolution argument is a string representing the resolution of the offset. Valid resolutions are "years", "months", "days", "hours", "minutes", and "seconds".

Example 7.64. 

d[] = datetime:create[2013, 10, 31, 15, 30, 0].
datetime:add[d[], 1, "days"] = x  -> datetime:day[x] = 1.
datetime:add[d[], 1, "days"] = x  -> datetime:month[x] = 11.

datetime:subtract[d[], 1, "days"] = x -> datetime:day[x] = 30.
datetime:subtract[d[], 1, "days"] = x -> datetime:month[x] = 10.

datetime:create / datetime:createTZ

datetime:create[y, m, d, h, m, s] = dt
   -> int(y), int(m), int(d), int(h), int(m), int(s), datetime(dt).
datetime:createTZ(y, m, d, h, m, s, tz] = dt
   -> int(y), int(m), int(d), int(h), int(m), int(s), string(tz), datetime(dt).

datetime:create creates a datetime value with the given year, month, day, hour, minute, and second specified. Without a timezone parameter, the time is assumed to be UTC.

Example 7.65. 

d[] = datetime:create[2013, 10, 31, 15, 30, 0].
-> datetime:string:convert[d[]] = "2013-10-31 15:30:00 UTC".

dtz[] = datetime:createTZ[2013, 10, 31, 15, 30, 0, "PST"].
-> datetime:string:convert[dtz[]] = "2013-10-31 22:30:00 UTC".

datetime:export / datetime:import

datetime:export[d] = i -> datetime(d), int(i).
datetime:import[i] = d -> int(i), datetime(d).

datetime:export converts a datetime into an opaque integer value. The integer value i can be used via datetime:import to recreate the datetime value.

Example 7.66. 

i[] = datetime:export[datetime:create[2013, 10, 31, 15, 30, 0]].
datetime:import[i[]] = d -> datetime:hour[d] = 15.

datetime:format

datetime:format[dt, format] = result
   -> datetime(dt), string(format), string(result).
datetime:formatTZ[dt, format, tz] = result
   -> datetime(dt), string(format), string(tz), string(result).

datetime:format formats a datetime into a string format (in case of formatTZ based on a given timezone) according to the specified datetime format.

The table below lists all the supported date facet format flags.

Format Specifier Description Example
%a Abbreviated weekday name. "Mon" => Monday
%A Long weekday name. "Monday"
%b Abbreviated month name. "Feb" => February
%B Full month name. "February"
%d Day of the month as decimal 01 to 31.
%D Equivalent to %m/%d/%y.
%G This has the same format and value as %Y, except that if the ISO week number belongs to the previous or next year, that year is used instead.
%g Like %G, but without century (two digits, not four).
%j Day of year as decimal from 001 to 366 for leap years, 001 to 365 for non-leap years. "060" => Feb-29
%m Month name as a decimal 01 to 12. "01" => January
%u The day of the week as a decimal, range 1 to 7, Monday being 1.
%U The week number of the current year as a decimal number, range 00 to 53, starting with the first Sunday as the first day of week 01. In 2005, January 1st was a Saturday, so it would be treated as belonging to week 00 of 2005. (Week 00 spans 2004-Dec-26 to 2005-Jan-01: this also happens to be week 53 of 2004). The following Sunday would be in week 01.
%V The ISO 8601:1988 week number of the current year as a decimal number, range 01 to 53, where week 1 is the first week that has at least 4 days in the current year, and with Monday as the first day of the week.
%w Weekday as decimal number 0 to 6. "0" => Sunday
%W Week number 00 to 53 where Monday is first day of week 1. Sunday, January 2, 2005 would be treated as belonging to week 00; the following Monday would be week 01.
%y Two digit year. "05" => 2005
%Y Four digit year. "2005"
%Y-%b-%d Default date format. "2005-Apr-01"
%Y%m%d ISO format. "20050401"
%Y-%m-%d ISO extended format. "2005-04-01"

The table below lists all the supported time facet format flags.

Format Specifier Description Example
%- Placeholder for the sign of a duration. Only displays when the duration is negative. "-13:15:16"
%+ Placeholder for the sign of a duration. Always displays for both positive and negative. "+13:15:16"
%f Fractional seconds are always used, even when their value is zero. "13:15:16.000000"
%F Fractional seconds are used only when their value is not zero. (Note: does not print '.' if fractional seconds is zero) "13:15:16", but "05:04:03.001234"
%H The hour as a decimal number using a 24-hour clock (range 00 to 23).
%I The hour as a decimal number using a 12-hour clock (range 01 to 12).
%k The hour (24-hour clock) as a decimal number (range 0 to 23); single digits are preceded by a blank.
%l The hour (12-hour clock) as a decimal number (range 1 to 12); single digits are preceded by a blank.
%M The minute as a decimal number (range 00 to 59).
%O The number of hours in a time duration as a decimal number (range 0 to max. representable duration); single digits are preceded by a zero.
%p Either AM or PM according to the given time value, or the corresponding strings for the current locale.
%P Like %p but in lowercase: am or pm or a corresponding string for the current locale.
%r The time in a.m. or p.m. notation. In the POSIX locale this is equivalent to %I:%M:%S %p.
%R The time in 24-hour notation (%H:%M).
%s Seconds with fractional seconds. "59.000000"
%S Seconds only. "59"
%T The time in 24-hour notation (%H:%M:%S).
%q ISO time zone (output only). "-0700" // Mountain Standard Time
%Q ISO extended time zone (output only). "-05:00" // Eastern Standard Time
%z Abbreviated time zone (output only). "MST" // Mountain Standard Time
%Z Full time zone name (output only). "EDT" // Eastern Daylight Time
%ZP Posix time zone string. "EST-05EDT+01,M4.1.0/02:00,M10.5.0/02:00"

Formatting is based on the Boost library. More documentation can be found on the Boost website.

datetime:part / datetime:partTZ

datetime:part[d, part] = i -> datetime(d), string(part), int(i).
datetime:partTZ[d, part, tz] = i
   -> datetime(d), string(part), string(tz), int(i).

datetime:part extracts a component specified by the part parameter from a datetime and returns it as an integer. The versions with a timezone parameter return the part for the given timezone. Valid values of part are: "year", "month", "day", "hour", "minute", and "second".

Example 7.67. 

d[] = datetime:create[2013, 10, 31, 15, 30, 0].
d[] = x -> datetime:part[x, "year"] = 2013.
d[] = x -> datetime:part[x, "hour"] = 15.

-> datetime:partTZ[d[], "hour", "PST"] = 8.

datetime:now

datetime:now[] = d -> datetime(d).

datetime:now contains the start date and time of the transaction. Within a single transaction, it will always have the the same value. Note that there is no guarantee that datetime:now reflects the ordering of multiple transactions. For example, there could be transactions with commit order T1, T2, T3, but with T1:datetime:now[] > T2:datetime:now[].

Example 7.68. 

datetime:string:convert[datetime:now[]] = "2013-07-31 19:38:47 UTC"

datetime:offset

datetime:offset[dFrom, dTo, resolution] = offset
   -> datetime(dFrom), datetime(dTo), string(resolution), int(offset).

Calculates the difference between dTo and dFrom in a certain resolution. Available resolutions: "years", "months", "days", "hours", "minutes" and "seconds".

For year, month, and, day we truncate both dates to the resolution and compute their difference then. That is, the difference in days between "2013-01-01 23:59" and "2013-01-02 00:01" is 1.

For hour, minute, and second we compute the offset in seconds and then round the result according to the resolution.

Example 7.69. 

d1[] = datetime:create[2013, 10, 31, 15, 30, 0].
d2[] = datetime:create[2013, 11,  1, 20, 30, 0].
-> datetime:offset[d1[], d2[], "hours"] = 29.
-> datetime:offset[d1[], d2[], "minutes"] = 29 * 60.
-> datetime:offset[d1[], d2[], "days"] = 1.
-> datetime:offset[d1[], d2[], "months"] = 1.

d3[] = datetime:create[2012, 12, 31, 23, 59, 59].
d4[] = datetime:create[2013,  1,  1,  0,  0,  0].
-> datetime:offset[d3[], d4[], "hours"] = 0.
-> datetime:offset[d3[], d4[], "minutes"] = 0.
-> datetime:offset[d3[], d4[], "seconds"] = 1.
-> datetime:offset[d3[], d4[], "days"] = 1.
-> datetime:offset[d3[], d4[], "months"] = 1.
-> datetime:offset[d3[], d4[], "years"] = 1.

datetime:parse

datetime:parse[s, format] = dt -> string(s), string(format), datetime(dt).

datetime:parse parses a string representing a datetime according to a specified datetime format.

The number of supported formatting patterns is limited compared to datetime:format.

Format Specifier Description Example
%d Day of month as decimal 1-31
%m Month of year as decimal 1-12
%b Abbreviated month name Feb
%q Quarter of year as decimal 1-4
%Y Year as 4 digits
%y Year as 2 digits
%H Hours as 2 digits in 24-hour clock (00-23)
%M Minutes as 2 digits (00-59)
%s Seconds as 2 digits (00-59) and fractional seconds as 1 to 6 digits where seconds and fractional seconds are separated by '.' (dot) 12.345678
%S Seconds as 2 digits (00-59)
%f Fractional seconds as 1 to 6 digits 012345
%F Fractional seconds as 1 to 6 digits prefixed by '.' (dot), or empty if no leading '.' (dot). Note: this includes strings with and without fractional seconds .012345 and empty string
%Q Time zone specifier. Either ISO time zone or ISO extended time zone or abbreviated time zone -0700 and -07:00 and UTC
%Z Same as %Q

datetime:string:convert

datetime:string:convert[d] = s -> datetime(d), string(s).

Converts d into a human-readable representation (UTC-based).

Example 7.70. 

d[] = datetime:create[2013, 10, 31, 15, 30, 0].
-> datetime:string:convert[d[]] = "2013-10-31 15:30:00 UTC".

string:datetime:convert

string:datetime:convert[s] = d -> string(s), datetime(d)

Converts the given string into a datetime value. The string should conform to the format: "%Y-%m-%d %H:%M:%S%F %Q".

In case the time-zone is missing, the datetime value is assumed to be in the UTC timezone.

Example 7.71. 

d[] = string:datetime:convert["2013-10-31 15:30:00 UTC"].
-> datetime:string:convert[d[]] = "2013-10-31 15:30:00 UTC".

d[] = string:datetime:convert["2013-10-31 15:30:00"].
-> datetime:string:convert[d[]] = "2013-10-31 15:30:00 UTC".

7.9. Conversions

X:Y:convert

boolean:string:convert[x]  = y -> boolean(x),  string(y).

datetime:string:convert[x] = y -> datetime(x), string(y).

decimal:float:convert[x]   = y -> decimal(x), float(y).
decimal:int:convert[x]     = y -> decimal(x), int(y).
decimal:string:convert[x]  = y -> decimal(x), string(y).
decimal:decimal:convert[x] = y -> decimal(x), decimal(y).

float:decimal:convert[x]   = y -> float(x), decimal(y).
float:int:convert[x]       = y -> float(x), int(y).
float:string:convert[x]    = y -> float(x), string(y).
float:float:convert[x]     = y -> float(x), float(y).

int:decimal:convert[x]     = y -> int(x), decimal(y).
int:float:convert[x]       = y -> int(x), float(y).
int:string:convert[x]      = y -> int(x), string(y).
int:int:convert[x]         = y -> int(x), int(y).

string:boolean:convert[s]  = v -> string(s), boolean(v).
string:datetime:convert[s] = v -> string(s), datetime(v).
string:decimal:convert[s]  = v -> string(s), decimal(v).
string:int:convert[s]      = v -> string(s), int(v).
string:float:convert[s]    = v -> string(s), float(v).
string:string:convert[x]   = y -> string(x), string(y).

LogiQL supports an unchecked conversion function, X:Y:convert[x] = y, that converts a value of type X to a value of type Y. X and Y can be types int, float, decimal, or string.

LogiQL also supports conversions between datetime values and strings, as well as between strings and booleans. The boolean true always converts to the string "true" and false always converts to "false". When converting from string to boolean, all inputs are case-insensitive.

Strings produced by these conversions are not LogiQL literals: the qualifiers f and d are not added to floating point or decimal values. (See Section 4.2, “Literals”.)

The conversions from string (i.e., when X is string) are further described in the section called “string:T:convert”.

Please note that the conversion is unchecked: if the value x cannot be represented as a value of type Y, some information is lost.

Example 7.72. 

float:int:convert[3.141592f] = 3
int:float:convert[3] = 3.0f
int:decimal:convert[1] = 1.0d
decimal:int:convert[2.4d] = 2
boolean:string:convert[false] = "false"
string:boolean:convert["TRUE"] = true
datetime:string:convert[datetime:create[2013, 10, 31, 15, 30, 0]]]) =
    "2013-10-31 15:30:00 UTC"

blox:lang:toX

In addition to the explicit unchecked conversion function X:Y:convert, LogiQL also supports a polymorphic conversion function, blox:lang:toX, where X can be Int, Float, Decimal, or String.

The polymorphic unchecked conversion provides programming convenience in that the source type need not be specified: it is inferred by the compiler.

Example 7.73. Polymorphic unchecked conversions

blox:lang:toString[3] = x

is equivalent to

int:string:convert[3] = x

The value of x that satisfies the conversion is "3".

X:Y:eq

LogiQL supports a checked conversion function, X:Y:eq[x] = y, that converts a value of type X to a value of type Y without losing information. If the value of x cannot be precisely represented in the type Y, the function produces no result for y (i.e., the attempt to convert fails).

X and Y must be chosen from the numeric types int, decimal, or float. Furthermore, checked conversion between decimal and float is not supported.

Example 7.74. 

Checked conversions with results:

float:int:eq[3.0f] = 3.
int:decimal:eq[3] = 3.0d.

Checked conversion with no result:

float:int:eq[3.1f]

7.10. Currency

float:currency:string

float:currency:string[x, y] = z -> string(x), float(y), string(z).

Formats a float according to the locale identifier specified by the first parameter. Identifiers are interpreted by the underlying ICU implementation (see ICU User Guide for "Locale").

Example 7.75. 

numbers(-123.4f).
numbers(1234.5f).

locales(x) -> string(x).
locales("@currency=USD").
locales("@currency=EUR").
locales("de_DE.UTF-8").
locales("en_US.UTF-8").

result(x, y, z) <-
   numbers(x),
   locales(y),
   float:currency:string[y, x] = z.

yields:

-123.4 "@currency=EUR" "-123.40"
-123.4 "@currency=USD" "-US$123.40"
-123.4 "de_DE.UTF-8"   "-123,40 €"
-123.4 "en_US.UTF-8"   "-$123.40"
1234.5 "@currency=EUR" "1,234.50"
1234.5 "@currency=USD" "US$1,234.50"
1234.5 "de_DE.UTF-8"   "1.234,50 €"
1234.5 "en_US.UTF-8"   "$1,234.50"

7.11. Unique Identifiers

uid<<>>

UniqueIdRule  =
        PositiveDeltaAtoms "<-" "uid" "<<" Identifier ">>" InputFormula.

PositiveDeltaAtoms  =  PositiveDeltaAtom { ","  PositiveDeltaAtom }.

PositiveDeltaAtom  =  "+" Atom.

InputFormula = Conjunction.

The uid function can be used to generate a set of unique identifiers for all the tuples in a predicate. The key space of the resulting predicate(s) must match the key space of the input predicate(s), and the resulting predicate(s) must have integer values.

The generated identifiers are unique in the context of a single database, in particular across the branches of a single database. They are not guaranteed to be universally unique across multiple databases, e.g., if you export the identifiers from one database and import into another.

Note

  • uid<<>> cannot be used in rules for database-lifetime IDB predicates.

  • The syntax given above describes the most frequent usage. In transaction-lifetime delta rules the delta atoms in the head need not be positive, but then:

    • if the atom in the head is negative (i.e., signifies deletion), then the uid<<x>> part of the rule is essentially ignored;

    • if an atom in the head is preceded with ^ (i.e., it represents an upsert), then the effect is to renumber the affected tuples.

Example 7.76. Legal uses of uid

create --unique

addblock <doc>
  F[x] = id -> string(x), int(id).
  G[x] = id -> string(x), int(id).

  R(x) -> string(x).
  Q(x) -> string(x).

  R("Joe").     R("Jill").
  Q("Helen").   Q("Henry").  Q("Jill").

  +F[x] = id <- uid<<id>> +R(x).   // database lifetime
</doc>

exec <doc>
  +F[x] = id, +G[x] = id <- uid<<id>> Q(x), !R(x).   // transaction lifetime
</doc>
echo --- F:
print F
echo --- G:
print G

exec <doc>
  -F[x] = id <- uid<<id>> Q(x).   // id ignored
  ^G[x] = id <- uid<<id>> Q(x).   // renumbering
</doc>
echo --- F2:
print F
echo --- G2:
print G

close --destroy

In the penultimate rule (which deletes tuples from F) the use of uid is effectively ignored, while the final rule renumbers the items in G. So the results will be something like

created workspace 'unique_workspace_2017-06-07-19-27-24'
added block 'block_1Z1FGZLX'
--- F:
"Helen" 10000000040
"Henry" 10000000043
"Jill"  10000000032
"Joe"   10000000034
--- G:
"Helen" 10000000040
"Henry" 10000000043
--- F2:
"Joe" 10000000034
--- G2:
"Helen" 10000000060
"Henry" 10000000065
"Jill"  10000000066
deleted workspace 'unique_workspace_2017-06-07-19-27-24'

Example 7.77. An illegal use of uid

create --unique

addblock <doc>
  F[x] = id -> string(x), int(id).
  R(x) -> string(x).

  R("Joe").     R("Jill").

  F[x] = id <- uid<<id>> R(x).   // Not allowed in database-lifetime IDB!
</doc>
echo ---
print F

close --destroy

The compiler will report an error:

Error: Database-lifetime uid<<>> rules may contain only positive delta atoms: F[x]=id is not a positive delta atom
In P2P rule Forall id::int,x::string .
F[x]=id <-
   uid<<id>>
      R(x).

7.12. Transaction Identifier

transaction:id[]

transaction:id[] = id -> int(id).

transaction:id[] contains a unique transaction identifier for the transaction. The transaction identifiers are only unique in the context of a single database, this means identifiers are also unique across branches of a single database. They are not guaranteed to be universally unique across multiple databases or if you export the identifiers from one database and import into another.

Example 7.78. 

txn_ids(x) -> int(x).
+txn_ids(x) <- x = transaction:id[].