June 1, 2023·4 min read·engineeringrqlitesqlitejson

rqlite gains JSON5 parsing through SQLite 3.42

rqlite 7.20.0 embeds SQLite 3.42.0. JSON5 input support broadens what SQL functions can parse while strict JSON validation retains its existing meaning.

A parser can accept a document without declaring it valid under a stricter interchange format. That distinction becomes practical in rqlite 7.20.0, whose June 1 changelog records an upgrade to SQLite 3.42.0. SQLite's JSON functions now understand JSON5 extensions, while the one-argument json_valid() function continues to test strict JSON.

This creates a useful ingestion choice. SQL can interpret more permissive input, including syntax intended to make documents easier for people to edit. Applications can also retain a strict storage rule. Those behaviors coexist because parsing and validation answer different questions within the same engine.

I value rqlite's use of SQLite because it keeps the relational engine recognizable beneath the distributed store. An engine upgrade can add useful SQL behavior without requiring a separate document-processing service. The exact semantics still matter, especially when a permissive parser meets an existing schema constraint.

Parsing and validation now have different acceptance sets

The SQLite 3.42.0 release notes, dated May 16, identify JSON5 support as a change to the JSON SQL functions. The versioned JSON implementation shows how the parser records nonstandard syntax. Its parse state has a hasNonstd field, and individual nodes can carry a JSON5 flag for rendering normalized output.

The code includes handling for single-quoted strings and hexadecimal integer literals. When rendering such nodes, it converts them into ordinary JSON representations. A parser that recognizes those inputs can therefore support an explicit normalization step before storing or returning the document.

For example, a SQL expression can normalize an object whose numeric value uses hexadecimal notation. The following query demonstrates both the permissive input and the strict validation distinction in the engine included by this release.

SELECT json('{"limit":0x10}');
SELECT json_valid('{"limit":0x10}');
SELECT json_valid(json('{"limit":0x10}'));

The first expression produces {"limit":16}. The second returns zero because the original text uses a JSON5 extension. Validating the normalized output returns one because it validates the normalized JSON output. This example concerns SQL behavior, so an application submitting the statements through rqlite must still encode its request according to the database's HTTP interface.

json_valid() in this version has one registered argument. Its implementation requires both a successful parse and the absence of nonstandard input. The argument supplies the text to validate, without an additional option selecting a different validation standard. The compatibility rule is visible directly in the function registration and its validation code.

Existing strict constraints retain their purpose

A schema that accepts text only when json_valid(payload) succeeds remains strict. Broadening the parser does not automatically broaden that predicate. This is useful when the database receives permissive source documents but other consumers expect standard JSON.

There are two separate policy decisions. An application may reject extended syntax at its ingestion boundary, or it may normalize accepted input before applying a strict storage rule. The engine supplies the mechanisms, but it cannot decide which input contract an application intends to expose. Calling json() explicitly makes the conversion visible in the SQL expression.

Normalization also changes the stored representation. The hexadecimal spelling in the example becomes a decimal number. If retaining the original spelling matters, normalized JSON alone cannot provide it. Original source text and normalized data serve different purposes, even when they describe the same value. A system that retains both can preserve the submitted spelling while also exposing a representation suitable for strict JSON consumers.

The versioned source makes the distinction visible for error reporting as well. Its comments explain that json_error_position() returns zero for interpretable extended input even when json_valid() returns false. A nonzero error position indicates that the parser could not interpret the input, including the extensions it supports. These results need to be read according to their individual contracts.

The engine upgrade also tightens a limit

SQLite's release notes lower the maximum recursion depth for JSON arrays and objects from 2,000 to 1,000. This belongs in the same discussion as broader input syntax because an engine upgrade can simultaneously accept new spellings and reject previously accepted nesting depths. More permissive syntax does not imply that every parsing limit increased.

The change affects unusually deeply nested documents. A shallow configuration object and a chain of a thousand nested arrays exercise different parser requirements. The release note gives an explicit bound, so applications that generate recursive JSON structures have a concrete compatibility condition to consider.

Other changes in SQLite 3.42 concern query planning, including avoiding computation of unused subquery columns and improvements to WHERE-clause push-down. These are engine changes that rqlite picks up through the embedded SQLite version. They do not establish a particular throughput gain for a distributed workload, whose behavior also depends on operations outside the SQL planner.

The release notes also describe changes to SQLite's own command-line program. Those should not be confused with rqlite's separate CLI. An embedded-engine upgrade does not mean every option in the upstream sqlite3 shell becomes an option of the rqlite executable.

Address validation protects the other boundary

The rqlite changelog includes another change alongside the engine upgrade. Pull request 1286 rejects equal HTTP and Raft bind addresses and equal advertised addresses. These are separate listeners with distinct uses, so identical configuration strings indicate an invalid arrangement.

The checks compare configured address values. They should not be described as a general network reachability test or proof that distinct hostnames resolve to distinct endpoints. The change moves a specific configuration error into startup validation, where the process can report that the addresses must differ.

I appreciate this combination of changes because each makes a boundary more explicit. At the SQL boundary, parsing extended input and asserting strict JSON remain separate operations. At startup, the HTTP and Raft addresses have an enforced distinction. Both behaviors give an application or operator a concrete contract to read, with the JSON example showing why acceptance by one function need not imply acceptance by another.