Expressions
Expressions are the most complex part of RZSQL's syntax. An expression represents a computation that produces a scalar value, that is, a value of some data type that could be stored in a table column.
The full syntax diagram for expr
is rather daunting, but may serve as a useful
reference.
expr
The simplest expressions
Sprinkled onto the spaghetti of the above diagram are some tasty meatballs of simplicity. These are the best starting points for getting a handle on expressions, so they're right at the top.
Literals
A literal is just a plain value like 1
or 'Jeff'
.
Bind parameters
A bind parameter lets you pass a value from your program into your SQL
command. Bind parameters are like any other names, but are prefixed
with an @
sign.
The data type of a bind parameter is inferred from its usage, much like F# variable types are inferred.
Column names
Within some contexts, such as a select statement, you can access table columns by name from within an expression.
Parentheses
Parentheses serve two roles in expressions. As in most languages, they can be
used to override the default operator precedence and control the order of
operations. For example, here parentheses are used to force x
and y
to be
added before multiplying in z
:
(x + y) * z
However, parentheses also allow you use a sub-query expected to return a single value in an expression:
select (select Email from Users limit 1) as FirstEmail
These are called scalar subqueries. Scalar subqueries should always have only one column, and RZSQL enforces this at compile time. They should also return no more than one row, but RZSQL cannot verify this, so it is up to you to ensure it.
If a scalar subquery produces multiple rows, the outcome is dependent on your SQL backend. It may use the first row, or throw an error, or do something completely unexpected.
The CAST expression
The CAST
expression is used to convert values from one type to
another. For example:
select CAST('123' as int64) as IntegerColumn
As far as RZSQL's type system is concerned, a value of any type can be converted
to any other type using CAST
. This may fail at runtime depending on your
database backend.
Operators
RZSQL supports a variety of operators. Operators can be:
- Unary, meaning they are written preceding their single operand as in
NOT x
- Binary, meaning they are written between their operands, as in
x + y
- Ternary, meaning they are written with three operands, as in
x between y and z
All operators are left-associative, meaning that if operator A
and B
have
the same precedence, then x A y B z
groups like (x A y) B z
.
The following table briefly describes each operator in order of precedence, from highest to lowest.
Operator | Type | Precedence | Example | Notes |
---|---|---|---|---|
COLLATE |
Binary | 10 | 'str' COLLATE NOCASE |
Right side is a collation name. |
NOT |
Unary | 9 | NOT true |
True if operand is false. |
~ |
Unary | 9 | ~0xff |
Integer bitwise complement. |
- |
Unary | 9 | -2 |
-x is like x * -1. |
|| |
Binary | 8 | abc || def |
String concatenation. |
* |
Binary | 7 | 5 * 3 |
Numeric multiplication. |
/ |
Binary | 7 | 10 / 2 |
Numeric division. |
% |
Binary | 7 | 5 % 3 |
Integer modulo. |
+ |
Binary | 6 | 5 + 3 |
Numeric addition. |
- |
Binary | 6 | 5 - 3 |
Numeric subtraction. |
<< |
Binary | 5 | 32 << 1 |
Integer bitwise left-shift. |
>> |
Binary | 5 | 64 >> 1 |
Integer bitwise right-shift. |
& |
Binary | 5 | 123456 & 0xff |
Integer bitwise AND. |
| |
Binary | 5 | 0x00ff | 0xff00 |
Integer bitwise OR. |
>= |
Binary | 4 | 4 >= 4 |
Greater-than-or-equal comparison. |
<= |
Binary | 4 | 4 <= 4 |
Less-than-or-equal comparison. |
> |
Binary | 4 | 4 > 3 |
Greater-than comparison. |
< |
Binary | 4 | 3 < 4 |
Less-than comparison. |
= |
Binary | 3 | 1 = 1 |
Equality comparison. |
<> |
Binary | 3 | 1 <> 0 |
Inequality comparison. |
IS |
Binary | 3 | NULL IS NULL |
Equality comparison (including null). |
IS NOT |
Binary | 3 | 1 IS NOT NULL |
Inequality comparison (including null). |
LIKE |
Binary | 3 | 'abc' LIKE '%b%' |
String glob pattern test. |
NOT LIKE |
Binary | 3 | 'a' NOT LIKE '%b%' |
String glob pattern test (negated). |
IN |
Binary | 3 | 1 IN (1,2,3) |
Membership test. See details below. |
NOT IN |
Binary | 3 | 1 NOT IN (2,3) |
Membership test (negated). See details below. |
BETWEEN |
Ternary | 3 | 5 BETWEEN 1 AND 7 |
Inclusive range test. |
AND |
Binary | 2 | true AND NOT false |
Logical AND. |
OR |
Binary | 1 | false OR true |
Logical OR. |
IS versus =
The IS
and IS NOT
operators are almost the same as =
and <>
. However,
there is an important distinction. SQL dialects traditionally implement
three-valued logic). This means that
the following expression is false:
NULL = NULL
OR NULL <> NULL
OR 1 <> NULL
The IS
and IS NOT
operators work the way equality comparisons do in normal
programming languages, so the following expression is true:
NULL IS NULL
AND NOT (NULL IS NOT NULL)
AND 1 IS NOT NULL
Special operators: LIKE
Some operators are actually a bit more complex than described above.
One such operator is LIKE
, which has a couple of quirks. One is that is
actually a ternary operator whose third operand is optional. The LIKE
operator
takes an optional ESCAPE
clause specifying the character to use to escape
special characters in the pattern. The behavior of this clause is dependent on
your SQL backend, but an example usage for SQLite is:
'string with a % sign' LIKE '%\%%' ESCAPE '\'
Additionally, the verbs MATCH
or REGEXP
can be used instead of LIKE
, as
in:
'test' REGEXP '^te.*'
These operators are not supported on all SQL backends. On SQLite they behave as documented here.
Special operators: IN
Another special operator is IN
. Its right side, which determines the source of
elements to test for membership in, can be one of three things:
- A table name.
- A subquery (wrapped in parentheses).
- A list of expresssions (wrapped in parentheses and separated by commas).
- A bind parameter.
If it is a bind parameter, that parameter's type will be inferred as an array.
At runtime, RZSQL will handle converting the given array of values to multiple
bind parameters, and the SQL query sent to the server will look something like
... x IN (@p0, @p1, @p2... @pN) ...
.
Ternary operator associativity
Ternary operators are also left-associative. Hopefully there are no realistic situations where this would come up, but this means that the following expression:
1 BETWEEN 2 BETWEEN 3 AND 4 AND 5 BETWEEN 6 AND 7
Is parsed as:
(1 BETWEEN (2 BETWEEN 3 AND 4) AND 5) BETWEEN 6 AND 7
Function invocation
Your SQL backend probably has some functions, like these for SQLite.
RZSQL tries to know about those functions so it can correctly typecheck queries using them. You can find the list of supported functions for your backend under the functions section.
The CASE expression
The CASE expression lets you write conditionals. This works pretty much the same way on every SQL backend. There are two forms of CASE expressions.
The most generally useful form looks like this:
CASE
WHEN cond1 THEN result1
WHEN cond2 THEN result2
ELSE result3
END
This is essentially the same thing as the F# expression:
if cond1 then result1
elif cond2 then result2
else result3
The other form of CASE expression has an expression between the CASE keyword and the first WHEN keyword. In this form, each WHEN clause is implicitly compared to the initial expression.
So, this expression:
CASE x
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE result3
END
Is equivalent in effect to this expression:
CASE
WHEN x = value1 THEN result1
WHEN x = value2 THEN result2
ELSE result3
END
The EXISTS expression
The EXISTS expression takes a subquery on its right side. It returns true if the subquery yields any rows, false otherwise.