Skip to main content

Prefixes

As the extension catalog grows, name collisions between SQL functions are inevitable. Two extensions might both register concat. SQLite's default behaviour is silent last-wins shadowing — the second registration replaces the first; callers can't tell which implementation they hit.

Sqlink prefixes solve this by giving every extension a short alias + a globally-unique expansion, similar to SPARQL PREFIX foaf: <http://xmlns.com/foaf/0.1/> declarations.

Strictly additive

The hard constraint: existing SQL must not break. Users today write SELECT uuid_v4(); that has to keep working exactly the same way after this feature lands.

ScenarioBare name() behavior
No collisionWorks — single registered impl.
Collision, no pinWorks — last-registered wins (SQLite default); load-time warning logged.
Collision, .prefix prefer name extAWorks — pinned to extA regardless of load order.
extA__name() qualifiedAlways works for any registered extension.

The feature is purely additive: bare-name dispatch is unchanged from today's behaviour; qualified forms (prefix__name) are always available for callers who want explicit dispatch.

Separator: __ (double underscore)

uuid_v4() -- bare name
sqlink__uuid_v4() -- qualified

Double-underscore was chosen because it's legal in unquoted SQLite identifiers, visually distinct from natural single-underscore names like uuid_v4, and ASCII-only. SPARQL's : would require quoting every call site; . conflicts with SQL's schema.function syntax.

Expansion is opaque

Each prefix has a short name + a required expansion string. The expansion can be a URL, a Java-style namespace, a UUID, or any opaque token. Sqlink doesn't validate the format. The expansion is the canonical function identity; the short prefix is a per-database alias.

[package.metadata.extension]
preferred-prefix = "foaf"
prefix-expansion = "http://xmlns.com/foaf/0.1/"

Dot commands

.prefix add NAME EXPANSION [DESC] -- register a new prefix
.prefix list -- name | expansion | description | last_used
.prefix functions NAME -- functions registered under this expansion
.prefix expansion NAME -- print just the expansion
.prefix rename OLD NEW -- change short alias (function identity unchanged)
.prefix modify NAME DESC -- update description
.prefix delete NAME -- remove alias
.prefix prefer NAME EXTENSION -- pin bare-name dispatch on collision (live)
.prefix unprefer NAME -- remove a pin (full revert next session)
.prefix conflicts -- diagnose bare-name ambiguities
.prefix verify -- registry summary

Tables

Prefix registrations live in the user database under the __sqlink_* convention (matching the cas-cache's __cas_* pattern). Same db, travels with the file.

__sqlink_prefix (name PRIMARY KEY, expansion, description, created_at, last_used_at)
__sqlink_prefix_function (expansion, function_name, n_args, extension_name, registered_at)
__sqlink_prefix_pin (function_name, n_args, expansion, set_at)

Deprecation window

In v1, extensions that don't declare preferred-prefix + prefix-expansion in their manifest get a synthetic fallback:

prefix = <crate-name>
expansion = sqlink-internal://<crate-name>

A deprecation warning fires at load time. In v1.1 the fallback becomes a hard error; before then, every in-tree extension gets a real prefix + expansion assigned (a documented v1.1 migration sweep in PLAN-followups).

See the full plan for the source-of-truth design.