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.
Numeric literals
Integer size, decimal precision, and floating-point type are all controlled by how you write the number:
SELECT
TYPEOF(TINYINT '1') AS F_TINYINT,
TYPEOF(SMALLINT '1') AS F_SMALLINT,
TYPEOF(MEDIUMINT '1') AS F_MEDIUMINT,
TYPEOF(1) AS F_BIGINT,
TYPEOF(1.123) AS F_DECIMAL,
TYPEOF(FLOAT '1.1') AS F_FLOAT,
TYPEOF(DOUBLE '1.1') AS F_DOUBLE,
TYPEOF(1.1e0) AS F_DOUBLESCI,
;| F_TINYINT VARCHAR | F_SMALLINT VARCHAR | F_MEDIUMINT VARCHAR | F_BIGINT VARCHAR | F_DECIMAL VARCHAR | F_FLOAT VARCHAR | F_DOUBLE VARCHAR | F_DOUBLESCI VARCHAR |
|---|---|---|---|---|---|---|---|
| TINYINT | SMALLINT | MEDIUMINT | 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).
Other scalar literals
Strings, booleans, dates, timestamps, and JSON each have their own syntax:
SELECT
TYPEOF('A') AS F_VARCHAR,
TYPEOF(TRUE) AS F_BOOLEAN,
TYPEOF(DATE '2024-10-15') AS F_DATE,
TYPEOF(TIMESTAMP '2024-10-15 10:04:00') AS F_TIMESTAMP,
TYPEOF(JSON '"A"') AS F_JSON,
;| F_VARCHAR VARCHAR | F_BOOLEAN VARCHAR | F_DATE VARCHAR | F_TIMESTAMP VARCHAR | F_JSON VARCHAR |
|---|---|---|---|---|
| VARCHAR | BOOLEAN | DATE | TIMESTAMP(3) | JSON |
Strings use single quotes only — 'A', never "A". 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.
Complex type literals
Arrays and rows let you build structured data inline:
SELECT
TYPEOF(ARRAY[1,2,3]) AS F_ARRAY,
TYPEOF(ROW(1, 'A')) AS F_ROW,
TYPEOF(ARRAY[ROW(1 as col1, 'A' as col2)]) AS F_ARRAY_OF_ROWS,
;| 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(DOUBLE)::VARCHAR,
TYPEOF(NULL(DOUBLE)),
-- EMPTY::BIGINT, -- You cannot cast EMPTY to another type
-- NULL::EMPTY -- We cannot cast as EMPTY
-- NULL::UNSPECIFIED -- We cannot cast as UNSPECIFIED
;| ?_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.