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:

FeatureQL
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)
;
Result
F_TINYINT VARCHARF_SMALLINT VARCHARF_INT VARCHARF_BIGINT VARCHARF_DECIMAL VARCHARF_FLOAT VARCHARF_DOUBLE VARCHARF_DOUBLESCI VARCHAR
TINYINTSMALLINTINTBIGINTDECIMAL(4,3)FLOATDOUBLEDOUBLE

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

Untyped integer literals use the BIGINT range. Values outside -92233720368547758089223372036854775807 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:

FeatureQL
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')
;
Result
F_VARCHAR VARCHARF_BOOLEAN VARCHARF_DATE VARCHARF_TIMESTAMP VARCHARF_INTERVAL VARCHARF_JSON VARCHARF_BITSTRING VARCHAR
VARCHARBOOLEANDATETIMESTAMP(3)INTERVALJSONBITSTRING

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.

Preferred forms

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.

CategoryTyped prefixWhat is checked
IntegersTINYINT, SMALLINT, INT, BIGINTInteger syntax, no fraction or exponent, per-type range
Decimals / floatsDECIMAL, FLOAT, DOUBLENumeric syntax (at most one . for decimal)
TemporalDATE, TIMESTAMP, TIMESTAMPTZCalendar date or date-time string shape; DATE must not include a time part
Other scalarsINTERVAL, JSON, BITSTRINGParseable interval text, valid JSON, or binary digits 0/1 only
MetaTYPE, FEATURE, FIELDType/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:

FeatureQL
SELECT (BITSTRING '1010')::VARCHAR;
Result
?_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:

FeatureQL
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)))
;
Result
F_ARRAY VARCHARF_ROW VARCHARF_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:

FeatureQL
SELECT
    EMPTY,
    TYPEOF(EMPTY),
    EMPTY(),
    TYPEOF(EMPTY()),
    NULL::VARCHAR,
    TYPEOF(NULL),
    NULL()::VARCHAR,
    TYPEOF(NULL()),
    NULL(BIGINT)::VARCHAR,
    TYPEOF(NULL(DOUBLE))
;
Result
?_0 VARCHAR?_1 VARCHAR?_2 VARCHAR?_3 VARCHAR?_4 VARCHAR?_5 VARCHAR?_6 VARCHAR?_7 VARCHAR?_8 VARCHAR?_9 VARCHAR
EMPTY()EMPTYEMPTY()EMPTYNULLUNSPECIFIEDNULLUNSPECIFIEDNULLDOUBLE

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.

Last update at: 2026/05/26 17:22:09