SQLite
Every type, null, row count and parameter rule on SQLite
This page is the reference behind the main docs for SQLite: the place to look when a type, a | null, a row count or a parameter surprises you. Postgres and MySQL have the same page, with every answer in the same place.
| Versions | The type check prepares each statement in Reylo's own SQLite 3.53, against the schema of your database file; your app runs on its driver's SQLite (Functions) |
| URL | A file: or sqlite: URL and a path, which starts at the directory of package.json, in DATABASE_URL or direct_url |
| Type check | Reads the schema through Node's built-in node:sqlite, so it needs no driver, from your database file or your schema files |
| Drivers | better-sqlite3, node:sqlite, bun:sqlite, libSQL, Turso's engine, Cloudflare D1, sqlite3, sqlite and Bun.sql (Drivers) |
View the schema behind the examples
-- not strict, since a STRICT table takes no BOOLEAN or BIGINT column
create table users (
id integer primary key,
name text not null,
email text unique,
nick text,
github_id bigint,
active boolean not null default 1,
created_at text not null
default (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);create table orders (
id integer primary key,
user_id integer not null references users (id),
total_cents integer not null,
placed_at text not null
default (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
ships_on text,
receipt blob
) strict;create table events (
id integer primary key,
kind text not null,
payload text not null
) strict;Data types
SQLite types values, not columns: a declared type gives a column an affinity, which Reylo types it by, and only a STRICT table holds a column to it (Declared types and STRICT tables).
In each pair below, the SQL is a value and the JavaScript is what Reylo returns for it: select <SQL> as v gives the row { v: <JavaScript> }. Reylo decodes every value of its own queries itself, on every driver:
| Declared type, or expression | TypeScript | The value .run() returns |
|---|---|---|
INTEGER, INT, and any other type with INT in its name | number | 42 |
BIGINT, INT8, UNSIGNED BIG INT | bigint | 9007199254740993n |
REAL, FLOAT, DOUBLE | number | 1.5 |
TEXT, VARCHAR, and any type with CHAR, CLOB or TEXT | string | "Ada" |
BLOB | Uint8Array | Uint8Array.of(1, 2, 255) |
BOOLEAN, BOOL | boolean | true |
A comparison, IS NULL, IN, BETWEEN, LIKE, GLOB, EXISTS, NOT, AND, OR | boolean | false |
NUMERIC, DECIMAL, DATE, DATETIME, and any other name | number | string | 1.5, "2026-10-20" |
No type, or ANY in a STRICT table | unknown | whatever was stored |
count(*), length(…) | number | 2 |
json_object(…), json_group_array(…) | the object or array it builds | { total: 1250 } |
json_extract(…), ->, ->> | unknown | 1250 |
Integers
The declared width decides, as on Postgres and MySQL: a column declared BIGINT, INT8 or UNSIGNED BIG INT is a JavaScript bigint, exact to SQLite's 64 bits, and every other integer is a number, INTEGER, INT, the rowid and count(*) included:
import from "./lite_user_orders.sql";
const rows = await .();select u.id, u.github_id, count(o.id) as orders
from users u
left join orders o on o.user_id = u.id
group by u.idWhat stays a bigint
A BIGINT column stays a bigint through a subquery, a view, min(), max(), coalesce() and CASE. These pairs read users.github_id, which holds 9007199254740993.
github_id9007199254740993nmax(github_id)9007199254740993ncoalesce(github_id, 0)9007199254740993n(select github_id from users)9007199254740993nWhat SQLite computes from it
What SQLite computes from a BIGINT, such as github_id + 1, abs(github_id) or sum(github_id), is an integer like count(*), a number. Here users.github_id holds 41.
github_id + 142abs(github_id)41sum(github_id)41Past what a number holds
A number past ±(2^53 − 1), which it cannot hold, is an error that names its column, never a rounded number. Declare the column BIGINT, or cast the value to text in the statement: cast(max(id) as text).
9007199254740993throws DecodeErrorcast(9007199254740993 as text)"9007199254740993"Drivers that read it exactly
Past 2^53, what a BIGINT comes back as depends on the driver. The drivers better-sqlite3, node:sqlite, bun:sqlite and Turso's engine read the exact integer, so it comes back exact on them. The pair reads users.github_id, which holds 9007199254740993.
github_id9007199254740993nDrivers that round it
The drivers sqlite3 and sqlite, Cloudflare D1, and Bun.sql without safeIntegers round an integer past 2^53 before Reylo sees it, and Reylo refuses the rounded number, in a BIGINT column too. The pair reads users.github_id, which holds 9007199254740993.
github_idthrows DecodeErrorOn libSQL
At its default intMode, libSQL refuses an integer past 2^53 itself, with a RangeError that names no column, as in the first pair. With intMode: "bigint" or "string" a BIGINT is exact, as in the second. Both read users.github_id, which holds 9007199254740993.
github_idthrows RangeErrorgithub_id9007199254740993nNo BIGINT in a STRICT table
A STRICT table takes no BIGINT column, so SQLite does not hold one to integers (Declared types and STRICT tables).
Integer parameters
An INTEGER parameter takes a number.
Decimals
SQLite has no decimal type: a NUMERIC or DECIMAL column holds an integer, a real or text, whichever the row stored, so it is number | string. A REAL is a double, the same on every driver.
Exact amounts
Keep an exact amount as an integer of its smallest unit, as orders.total_cents does, or as text.
12501250'12.50'"12.50"Sums and averages
The sum() of integers is an integer, and avg() and total() are reals. Here orders.total_cents holds 1250 and 1251.
sum(total_cents)2501avg(total_cents)1250.5total(total_cents)2501Dates and times
SQLite has no date or time type. A date is the text, the number or the real you store, and its type is its column's: a TEXT column's date is a string, as SQLite's own date functions write it:
import from "./lite_order_dates.sql";
const order = await .(, { : 1 });select placed_at, ships_on from orders where id = :idInstants as ISO 8601 text
Store an instant as ISO 8601 text in UTC with Z, as strftime('%Y-%m-%dT%H:%M:%fZ', 'now') writes it, which Temporal.Instant.from() and new Date() read. SQLite's date functions read it too, and since every value has the same width, text order is time order.
strftime('%Y-%m-%dT%H:%M:%fZ', '2026-10-20 10:00:00')"2026-10-20T10:00:00.000Z"UTC with no zone
SQLite's current_timestamp and datetime() write UTC with no zone, which a reader can't tell from local time.
datetime('2026-10-20T10:00:00.000Z')"2026-10-20 10:00:00"datetime(1792490400, 'unixepoch')"2026-10-20 10:00:00"Columns declared DATE
A column declared DATE or DATETIME has NUMERIC affinity, so it holds a number or a text, number | string. Declare it TEXT, or INTEGER for Unix time.
Text that is no date
SQLite's date functions, date(), datetime(), unixepoch() and the rest, answer NULL for text they cannot read as a date, so keep every date in one form they read: ISO 8601 text, or Unix time in an INTEGER.
date('20/10/2026')nulldate('2026-10-20T10:00:00.000Z')"2026-10-20"Casting a parameter to a date
A parameter's ::date or ::timestamp cast is SQLite's CAST(… AS TEXT) (Casts), since SQLite's CAST to a date reads '2026-10-12' as 2026.
cast('2026-10-12' as date)2026Truth values
SQLite has no boolean type, and computes a truth value as 1 or 0. Reylo makes every truth value the statement computes a boolean, and a column declared BOOLEAN or BOOL:
import from "./lite_user_flags.sql";
const rows = await .();select id, active, name like 'A%' as starts_with_a from usersWhat the statement computes
A truth value the statement computes is a comparison, IN, BETWEEN, EXISTS, IS, NOT, AND, OR, TRUE, FALSE, like, glob, regexp and match, and a CASE, coalesce, ifnull, iif or subquery whose every value is one.
1 = 1true'Ada' like 'B%'falseiif(1 = 1, 0 = 1, 1 = 1)falseThrough a subquery or a view
A column declared BOOLEAN reads as a boolean through any subquery or view too. The pair reads users.active, which holds 1.
(select active from users)trueFlags in a STRICT table
A STRICT table takes no BOOLEAN column, so a flag there is an INTEGER, a number.
Values other than 0 and 1
SQLite holds a column declared BOOLEAN to nothing, so it may hold 2, 'yes' or a BLOB. Reylo reads 0 and 1 as false and true, and refuses any other value, naming its column. Read the stored number with active + 0. Here users.active holds 2.
activethrows DecodeErroractive + 02Truth value parameters
A truth value parameter is bound as 1 or 0, since better-sqlite3 refuses a boolean and libSQL would bind true as the real 1.0.
Inside JSON the statement builds
A truth value is the number SQLite writes, 1 or 0.
json_object('a', 1 = 1){ a: 1 }JSON
SQLite has no JSON type: a stored document is TEXT, a string. JSON the statement builds with json_object, json_array, json_group_array or json_group_object is typed from its arguments and comes back parsed, each value as SQLite writes it in JSON, a truth value as 1 or 0:
import from "./lite_order_summaries.sql";
const rows = await .();select u.name,
json_object('total_cents', o.total_cents, 'active', u.active) as summary
from orders o
join users u on u.id = o.user_idBuilt JSON inside built JSON
Built JSON nests inside other built JSON through coalesce, case and a scalar subquery.
json_object('n', (select json_object('a', 1))){ n: { a: 1 } }json_object('n', case when 1 then json_object('a', 1) end){ n: { a: 1 } }Where it stops being JSON
Through a subquery's or a view's column, a cast or lag, SQLite may no longer know it is JSON, and Reylo types it unknown. A column or lag loses it; a cast keeps it on SQLite 3.53 and loses it on Turso's engine.
json_object('n', (select j from (select json_object('a', 1) as j))){ n: "{\"a\":1}" }json_object('n', lag(json_object('a', 1), 0) over ()){ n: "{\"a\":1}" }Over no rows
A json_group_array over no rows is [], never null.
(select json_group_array(id) from users where 0)[]Reading a document
What json_extract, -> and ->> return is unknown, since it depends on the document.
json_extract('{"a":1}', '$.a')1'{"a":[1,2]}' -> '$.a'"[1,2]"'{"a":"x"}' ->> '$.a'"x"JSON Schema checks
A CHECK that holds a column to a JSON Schema (sqlite-jsonschema's jsonschema_matches) types nothing, since pragma ignore_check_constraints lets any connection write past it.
Integers past 2^53
JavaScript's JSON.parse rounds an integer past 2^53 inside a document.
json_object('n', 9007199254740993){ n: 9007199254740992 }Typed JSON parameters
A typed JSON parameter (:events::json('…')) is bound as its JSON text, which json_each reads, as in the main docs' JSON parameters.
Binary
A BLOB is a Uint8Array on every driver: better-sqlite3, Turso's engine, sqlite3 and sqlite give a Buffer, which is one, libSQL an ArrayBuffer and Cloudflare D1 an array of numbers, which Reylo turns into one.
Binary parameters
A BLOB parameter takes a Uint8Array, a Buffer or not, on every driver.
Declared types and STRICT tables
Reylo types a column by its declared type's affinity, by the rule SQLite applies, in this order:
Names with INT
A type with INT in its name is INTEGER, whatever else the name holds.
Names with CHAR, CLOB or TEXT
Failing that, a type with CHAR, CLOB or TEXT in its name is TEXT.
Names with BLOB
Failing those, a type with BLOB in its name is BLOB.
Names with REAL, FLOA or DOUB
Failing those, a type with REAL, FLOA or DOUB in its name is REAL.
Any other name
Any other type is NUMERIC.
A column declared BIGINT, INT8 or UNSIGNED BIG INT is INTEGER too, and a bigint by its width (Integers). Outside a STRICT table, SQLite stores a value of any class in any column:
import from "./lite_loose.sql";
const rows = await .();-- users is not strict: name and nick may hold a BLOB
select name, nick from usersA STRICT table's promise
In a STRICT table the declared type is a promise: SQLite refuses a value of another class, and the types above are exact. Declare tables strict wherever you can.
What a STRICT table takes
A STRICT table takes only INT, INTEGER, REAL, TEXT, BLOB and ANY, so no BOOLEAN, BIGINT, DATE or NUMERIC column.
Columns of any class
A column declared with no type, or ANY in a STRICT table, holds any class, so it is unknown.
Columns declared BOOLEAN
A BOOLEAN column, which has NUMERIC affinity, is a boolean by its declared name (Truth values).
Check constraints
A CHECK narrows no type, since pragma ignore_check_constraints lets a connection write past it: check (role in ('admin', 'member')) leaves role a string.
Functions
SQLite says nothing of a function's result, so Reylo keeps a table of SQLite's built-in functions: count is a non-null integer, coalesce takes its arguments' type, upper is text that is NULL only where its argument is.
Dates that fail
A function that SQLite answers with NULL for some non-null arguments is | null whatever its arguments. The date and time functions return NULL for text they cannot read as a date and for a failing modifier, so date(placed_at) is string | null, while date('now') and datetime('now', '+7 days') are string.
date('not a date')nulldate('2026-10-20', '+1 fortnight')nulldate('2026-10-20', '+7 days')"2026-10-27"Math outside its domain
The math functions with a domain (sqrt, ln, log, acos, pow, …) return NULL outside it, so they are | null too.
sqrt(-1)nullsqrt(4)2Formats that write nothing
The printf function returns NULL when its format writes nothing, so it is | null too.
printf('')nullFunctions the table lacks
A built-in function the table lacks, such as unhex, is unknown. Cast its result to type it: cast(unhex(hash) as blob).
Functions your app registers
Reylo prepares the statement in its own SQLite, which has none of the functions your application registers (better-sqlite3's db.function(), node:sqlite's db.function()) or a loadable extension adds. A call to one fails the type check, cast or not: "no such function: slugify". Keep the call out of the .sql file and apply it in TypeScript.
Which functions exist
Which functions exist is the answer of the SQLite in the Node that runs the type check, from pragma_function_list: a call to one it lacks is an error at the call, "no such function: soundex on this database's SQLite". Builds differ in the math functions, soundex and each release's additions.
Your app's SQLite
Your app runs on its driver's SQLite, which may be older: better-sqlite3 12.11 carries 3.53.2, and libSQL, Cloudflare D1 and bun:sqlite carry their own. Turso's engine is a rewrite of SQLite, not a build of it, with functions of its own.
Argument counts
The type check knows a function by its name alone, so an argument count only a newer SQLite takes (iif with two arguments, before 3.48) is not caught.
Functions in the schema
A table whose CHECK, index or generated column calls a function the type check's SQLite lacks is read with that function stubbed, so it types as any other.
Values Reylo refuses
Every SQLite driver hands Reylo the stored value, so two values are refused, each with an error that names its column:
An integer past ±(2^53 − 1)
Reylo refuses one in a column typed number, which cannot hold it, and one the driver already rounded, in a BIGINT column too. Declare the column BIGINT, or read it with cast(n as text).
9007199254740993throws DecodeErrorA truth value other than 0 or 1
Reylo refuses one in a column declared BOOLEAN or BOOL, a BLOB included. Store 0 or 1 in it, or read the stored number with active + 0. Here users.active holds the BLOB x'01'.
activethrows DecodeErrorNullability
A column is | null unless Reylo infers it never is. SQLite's own check says "may be null" of everything but a literal or a column, so Reylo reads the schema and the statement:
- A table's column is non-null where it is declared
NOT NULL. - An outer join makes the joined table's columns nullable, by SQLite's own mark on the join.
- A test in the
wherethat fails onNULLmakes the column non-null:where nick is not null,where lower(nick) = :nick, and aleft join's column tested in thewhere. - An expression is non-null by Reylo's table of SQLite's functions:
length(name),coalesce(nick, name),name || '!'andid + 1over non-null inputs,count. A division is| null, since SQLite answersNULLfor a division by zero. Arithmetic on aREALwith no answer is the one exception (Limits). - An aggregate other than
countisNULLover no rows.
import from "./lite_nicknames.sql";
const rows = await .();select upper(nick) as shout, coalesce(nick, name) as label
from usersA test in the where shows the joined row exists, so its columns are non-null; a window function is typed like any other, lag nullable on the first row:
import from "./lite_shipped.sql";
const rows = await .();import from "./lite_order_gaps.sql";
const rows = await .();select u.name, o.total_cents, o.ships_on
from users u
left join orders o on o.user_id = u.id
where o.ships_on is not nullselect id, total_cents,
lag(total_cents) over (order by id) as previous_total,
row_number() over (order by id) as n
from ordersWhere you know more than Reylo can infer, say it in the statement: coalesce(x, …) gives a default, and a where x is not null test both filters and makes the column non-null.
Row counts
How many rows .run() resolves to comes from what Reylo infers the statement can return:
| Resolves to | cardinality | On SQLite, when the statement |
|---|---|---|
Row | one | is an aggregate without a group by, values of one row, or inserts one row and has returning, an on conflict do update included |
Row | null | maybeOne | matches every column of a primary key, an integer primary key or a unique index with =, through joins on the joined table's key; has limit 1; is a distinct or group by over values the where pins; or is an update or delete by key with returning |
void | none | is an insert, update, delete or replace without returning |
Row[] | many | is anything else |
What counts for nothing on SQLite:
- A unique index on an expression, or with a
where. A statement withwhere lower(email) = :emailisRow[], whatever indexlower(email)has. - A key that SQLite converts. SQLite gives a numeric side's affinity to a
TEXTor untyped key, sowhere code = cast(:code as integer)matches'1'and'01'. - A key compared under another collation. SQLite compares under the left side's collation, so a
NOCASEcolumn on the left of a plain key,r.k = c.code, meets'x'and'X'. - A declared column named
rowid, which is no rowid.
At run time a Row statement throws unless it gets exactly one row, and a Row | null statement throws when it gets more than one.
Parameters
Placeholders
Write each parameter as :name, or as SQLite's own @name or $name, one prefix per statement. Reylo sends ?, and .run() takes an object keyed by name, without the prefix.
- A statement may use SQLite's positional
?or?1instead, never beside a name, and the object's keys are then the numbers,{ 1: 42 }. - Each parameter appears once in a statement, so that one place types it. To use a value twice, select it once in a
withand read it from there. - SQLite reads
$1as a parameter named$1, so Reylo refuses it and says to write?1.
What a parameter takes
Every value .run() returns goes back in as a parameter of its type, on every driver, so a row can be passed straight to the next statement; a null goes back in where the parameter takes one (Parameters that may be null):
| Affinity | A parameter takes |
|---|---|
INTEGER | number |
A BIGINT, INT8 or UNSIGNED BIG INT column | number or bigint |
REAL | number |
TEXT | string |
BLOB | Uint8Array |
NUMERIC, a BOOLEAN or DATE column's included | number, string or boolean |
| A typed JSON parameter | what its schema admits |
- A
booleanis bound as 1 or 0 on every driver. - The
sqlite3andsqlitedrivers bind abigintasNULL, so Reylo passes them one within 2^53 as anumberand one past it as its digits to aBIGINTcolumn's parameter, and refuses one past it anywhere else.
Parameters without a type
SQLite types a parameter only where its own reading of the statement places one:
- compared with a typed expression, or as an element of a row value compared with a typed one (
(created_at, id) < (:at, :id)) - an
insert's orupdate's column - a
cast, arithmetic,limitandoffset,in,between, and acasearm - an argument of a built-in function from Reylo's table (
lower(:s)is text,abs(:n)a number,coalesce(:a, 0)an integer)
Anywhere else, such as a bare select :x or :x is null, the parameter is unknown until a cast types it:
import , { type Params } from "./lite_echo.sql";select :value as value, :count::int as countBy default such a parameter is an error in the type check, which asks for a cast; strict_params: false in .reylo.yaml leaves it unknown, as above. An argument of a function your application registers never gets that far: the call fails the type check (Limits).
Parameters that may be null
A parameter takes null in two places:
- as the first argument of
coalesceorifnull, with a cast or without; - written whole, with no cast, to a column that may hold
NULL.
A cast is the parameter's whole type, so :email::text never takes null, and a test for null (:p is null, :p is col) makes nothing nullable. A column holds NULL unless it is NOT NULL or a check (… is not null) holds it:
import from "./lite_users_by_nick.sql";
const = await .(, { nick: null });select id, name from users
where nick = coalesce(:nick::text, nick)Casts
SQLite has no ::, so Reylo writes :p::type as SQLite's CAST, by the type's affinity, and checks the value against the type before the statement runs, since SQLite's CAST never fails: CAST('abc' AS INTEGER) is 0, and CAST('1.9' AS INTEGER) is 1. SQLite's own cast(:p as …) works too.
| You write | SQLite runs |
|---|---|
::int, ::integer, ::bigint | CAST(? AS INTEGER) |
::real, ::float, ::double precision | CAST(? AS REAL) |
::text, ::varchar(20) | CAST(? AS TEXT) |
::numeric, ::decimal | CAST(? AS NUMERIC) |
::date, ::time, ::timestamp, ::uuid | CAST(? AS TEXT) |
::blob | CAST(? AS BLOB) |
::json, ::jsonb | json(?), jsonb(?) |
- The check refuses a number that is no integer for
::int, and a value out of SQLite's 64-bit range, before the statement runs. - A
::booleancast is an error: SQLite'sCASTto a boolean reads a value as the number it starts with. Pass 1 or 0 and write::integer. - SQLite has no arrays, so a
::text[]cast is an error.
How the text is read
Reylo reads the statement as SQLite does: a :name, @name or $name is a placeholder only outside single-quoted strings (with ''), double-quoted names and comments (-- and /* … */).
Drivers
Every SQLite driver turns each stored value into a JavaScript value in native code, and the drivers differ only on an integer and a BLOB.
Reylo runs its own query on the database you pass and gives each value its type after the driver, so a setting of yours does not reach Reylo's query, and none of Reylo's reaches yours.
| Driver | How Reylo reads its values | Lost before Reylo sees it |
|---|---|---|
better-sqlite3, node:sqlite, bun:sqlite | Reylo's own statement, prepared once per database, with its own integer setting, whatever the database's | |
| libSQL | The driver's rows, each value given its type | At its default intMode, an integer past 2^53, which libSQL refuses; with intMode: "string", a value Reylo types unknown that SQLite stored as an integer comes back as its digits, since libSQL gives nothing to tell it from text |
Turso's engine, @tursodatabase/database | Reylo's own statement, prepared per call, with its own integer setting, whatever the database's | |
sqlite3, sqlite | The driver's rows; the statement is not kept, since a statement left open makes the app's close() fail | An integer past 2^53, rounded |
| Cloudflare D1 | The driver's rows; a BLOB's array of numbers made a Uint8Array | An integer past 2^53, rounded |
Bun.sql | The driver's rows | An integer past 2^53, rounded, without safeIntegers; no rows, with no error, for a statement with a " inside a single-quoted string |
- The drivers
better-sqlite3,node:sqlite,bun:sqliteand theDatabaseof@tursodatabase/database/compattake.runSync()too, which returns the result itself, so it works inside theirdb.transaction(fn). - The drivers
sqlite3andsqlitebind abigintasNULL, so Reylo passes them one that anumberholds as that number, and refuses a larger one before the statement runs. Every other SQLite driver binds abigintexactly. - Each driver takes its transactions too: libSQL's
.transaction(), the handle Turso's.transactionAsync()passes its function,Bun.sql's.begin()and.savepoint(), and D1's.withSession()sessions. - On Bun 1.4.3,
Bun.sqlreturns no rows for a statement whose text holds a"inside a single-quoted string, such asjson_extract('{"a":1}', '$.a'), so.run()throws "Expected exactly one row, got 0" where one row is expected. Pass the document as a parameter:json_extract(:doc::text, '$.a'). - Turso's engine 0.8 stops the process on a recursive
WITHwhose anchor and recursive member both read a CTE overjson_each, such as one over a JSON parameter, with no Reylo involved. Write that CTEas materialized.
The type check needs no driver: it reads the schema through node:sqlite, which Node has built in since 22.5.
Limits
The edges of what Reylo types on SQLite, each with what to write instead:
- A declared type outside a
STRICTtable, which SQLite does not hold a value to. Declare tablesstrict. - Built JSON read through a subquery's or a view's column, a cast or
lag, which SQLite may no longer mark as JSON, so it isunknown. Build it in the query that returns it (JSON). - A function your application registers, or an extension's. Reylo prepares the statement in its own SQLite, which lacks the function, so the type check fails with "no such function: slugify", cast or not. Keep the call out of the
.sqlfile and apply it in TypeScript to what.run()returns. - A built-in function Reylo's table lacks, such as
unhex. Its result isunknown: cast it in the statement,cast(unhex(hash) as blob). - A table in an attached database. The type check reads one database file, and an
ATTACHhappens on your app's connection at run time, soselect … from other.usersis an error: the database has no schemaother. ATEMPtable is the same. - A virtual table's module. Reylo's SQLite has FTS3, FTS4, FTS5, R-tree, Geopoly and
dbstat, so a schema that uses one is read, and the type check does not ask whether your driver's SQLite has the module. - Arithmetic on a
REALthat has no answer. SQLite answersNULLwhere a real's arithmetic is not a number, and Reylo types it non-null:+,-and*over non-null values, and/and%by a literal other than 0.- Such a value is
nullwhere the type saysnumber:1e999 * 0,price - pricewherepriceholds1e999, and5 % 0.5, whose divisor%reads as the integer 0. - Write
coalesce(price * qty, 0)where aREALmay hold an infinity. An overflow is no such case:9e307 * 9e307isInfinity.
- Such a value is
- A parameter in place of a name. A parameter is a value:
select * from :tableis SQLite's syntax error, andorder by :columnsorts by a constant. Choose a column withcase :sort when 'name' then name end. - A statement nested deeper than 350 expressions, which is SQLite's error, "Expression tree is too large".
- One statement per file. A file holds one
select,insert,update,delete,replaceorvalues. A file of several statements or of DDL, such as a migration, is an error in the type check.