Literals
Literals are the constant values you write directly in queries — numbers, strings, dates, arrays. Because FeatureQL infers types from literals, the way you write a value determines its type. 1 is a BIGINT, 1.0 is a DECIMAL, and 1e0 is a DOUBLE — three different types from three different notations.
Many typed literals (TYPE 'value') are validated at compile time so malformed values fail before SQL runs on your backend.
Numeric literals
Integer size, decimal precision, and floating-point type are all controlled by how you write the number:
SELECT
f_tinyint := TYPEOF(TINYINT '1'),
f_smallint := TYPEOF(SMALLINT '1'),
f_int := TYPEOF(INT '1'),
f_bigint := TYPEOF(1),
f_decimal := TYPEOF(1.123),
f_float := TYPEOF(FLOAT '1.1'),
f_double := TYPEOF(DOUBLE '1.1'),
f_doublesci := TYPEOF(1.1E0)
;| F_TINYINT VARCHAR | F_SMALLINT VARCHAR | F_INT VARCHAR | F_BIGINT VARCHAR | F_DECIMAL VARCHAR | F_FLOAT VARCHAR | F_DOUBLE VARCHAR | F_DOUBLESCI VARCHAR |
|---|---|---|---|---|---|---|---|
| TINYINT | SMALLINT | INT | BIGINT | DECIMAL(4,3) | FLOAT | DOUBLE | DOUBLE |
The TYPEOF() results reveal the inferred types. Plain 1 defaults to BIGINT. Decimal literals like 1.123 get automatic precision tracking (DECIMAL(4,3)). For floating point, use either the keyword prefix (FLOAT '1.1', DOUBLE '1.1') or scientific notation (1.1e0 → DOUBLE).
Untyped integer literals use the BIGINT range. Values outside -9223372036854775808 … 9223372036854775807 are rejected at compile time — there is no silent widening to a larger integer type.
Other scalar literals
Strings, booleans, dates, timestamps, intervals, JSON, and bitstrings each have their own syntax:
SELECT
f_varchar := TYPEOF('A'),
f_boolean := TYPEOF(TRUE),
f_date := TYPEOF(DATE '2024-10-15'),
f_timestamp := TYPEOF(TIMESTAMP '2024-10-15 10:04:00'),
f_interval := TYPEOF(INTERVAL '3 DAYS 1 MINUTE'),
f_json := TYPEOF(JSON '"A"'),
f_bitstring := TYPEOF(BITSTRING '1010')
;| F_VARCHAR VARCHAR | F_BOOLEAN VARCHAR | F_DATE VARCHAR | F_TIMESTAMP VARCHAR | F_INTERVAL VARCHAR | F_JSON VARCHAR | F_BITSTRING VARCHAR |
|---|---|---|---|---|---|---|
| VARCHAR | BOOLEAN | DATE | TIMESTAMP(3) | INTERVAL | JSON | BITSTRING |
Strings use single quotes only — 'A', never "A". Write booleans as TRUE or FALSE, not BOOLEAN 'true'. Temporal literals require a keyword prefix: DATE '2024-10-15', TIMESTAMP '2024-10-15 10:04:00'. Timestamps support up to microsecond precision: TIMESTAMP '2024-10-15 10:04:00.123456'. JSON literals wrap JSON content in single quotes after the JSON keyword. Bitstrings use BITSTRING '1010' where the payload is a non-empty string of 0 and 1 only.
Use plain 'text' for strings and TRUE/FALSE for booleans. VARCHAR 'hello' and BOOLEAN 'true' are rejected with a short message pointing you to the preferred syntax.
Typed literal validation
When you write TYPE 'value', FeatureQL checks the string payload against rules for that type. Invalid literals produce a compile-time error with a specific code (for example UE/LITERAL-DATE-INVALID-FORMAT) instead of failing later in SQL or returning a surprising coerced value.
| Category | Typed prefix | What is checked |
|---|---|---|
| Integers | TINYINT, SMALLINT, INT, BIGINT | Integer syntax, no fraction or exponent, per-type range |
| Decimals / floats | DECIMAL, FLOAT, DOUBLE | Numeric syntax (at most one . for decimal) |
| Temporal | DATE, TIMESTAMP, TIMESTAMPTZ | Calendar date or date-time string shape; DATE must not include a time part |
| Other scalars | INTERVAL, JSON, BITSTRING | Parseable interval text, valid JSON, or binary digits 0/1 only |
| Meta | TYPE, FEATURE, FIELD | Type/feature/field name resolution (not a string payload check) |
A common mistake is putting a date-and-time string on a DATE literal. FeatureQL rejects that and tells you to use TIMESTAMP instead:
@fql-playground(literals_date_rejects_datetime_string)
Integer typed literals and untyped BIGINT literals share the same range checks. This fails at compile time because the value does not fit in BIGINT:
@fql-playground(literals_untyped_integer_rejects_bigint_overflow)
BITSTRING literals accept only binary digits. Valid values compile to SQL as CAST('…' AS BITSTRING) on backends such as DuckDB:
SELECT (BITSTRING '1010')::VARCHAR;| ?_0 VARCHAR |
|---|
| 1010 |
Invalid payloads are caught immediately:
@fql-playground(literals_bitstring_rejects_non_binary_digits)
Complex type literals
Arrays and rows let you build structured data inline:
SELECT
f_array := TYPEOF(ARRAY(1, 2, 3)),
f_row := TYPEOF(ROW(1, 'A')),
f_array_of_rows := TYPEOF(ARRAY(ROW(1 AS col1, 'A' AS col2)))
;| F_ARRAY VARCHAR | F_ROW VARCHAR | F_ARRAY_OF_ROWS VARCHAR |
|---|---|---|
| ARRAY<BIGINT> | ROW<field_1 BIGINT, field_2 VARCHAR> | ARRAY<ROW<col1 BIGINT, col2 VARCHAR>> |
ARRAY[1,2,3] creates a homogeneous list. ROW(1, 'A') creates a struct with auto-generated field names. Add AS to name the fields: ROW(1 as col1, 'A' as col2). Nest them — ARRAY[ROW(...)] — to create the array-of-rows structure that is central to FeatureQL's data model.
Special values
FeatureQL has two kinds of "nothing" — NULL and EMPTY — and they behave differently:
SELECT
EMPTY,
TYPEOF(EMPTY),
EMPTY(),
TYPEOF(EMPTY()),
NULL::VARCHAR,
TYPEOF(NULL),
NULL()::VARCHAR,
TYPEOF(NULL()),
NULL(BIGINT)::VARCHAR,
TYPEOF(NULL(DOUBLE))
;| ?_0 VARCHAR | ?_1 VARCHAR | ?_2 VARCHAR | ?_3 VARCHAR | ?_4 VARCHAR | ?_5 VARCHAR | ?_6 VARCHAR | ?_7 VARCHAR | ?_8 VARCHAR | ?_9 VARCHAR |
|---|---|---|---|---|---|---|---|---|---|
| EMPTY() | EMPTY | EMPTY() | EMPTY | NULL | UNSPECIFIED | NULL | UNSPECIFIED | NULL | DOUBLE |
NULL works like SQL's NULL: it represents a missing value and can be cast to any type. NULL without a type is UNSPECIFIED; NULL(DOUBLE) creates a typed null directly. NULL(DOUBLE) and NULL::DOUBLE are equivalent — both produce a typed null.
EMPTY is different — it represents an explicitly empty value used for optional function parameters. Unlike NULL, EMPTY cannot be cast to another type. If you try EMPTY::BIGINT, you get an error.