multi: tighten kvstore namespace uniqueness

Replace the single kvstores UNIQUE index with namespace-specific partial
unique indexes and add a check that feature-scoped records always have
a group_id.

This is needed because the old uniqueness constraint covered nullable
columns. In SQL, NULL values do not compare equal inside a UNIQUE index,
so duplicate global and group-scoped kvstore rows could be inserted even
though the legacy KVDB bucket layout only allows one record per logical
namespace.

The new indexes mirror the KVDB model directly:
global rows are unique by entry_key, rule_id, and perm; group rows add
group_id; feature rows add feature_id. The CHECK constraint also blocks
invalid feature rows that are not attached to a group.

NOTE: This commit explicitly edits the previous migration instead of
adding a new one. This is ok as SQL dbs are not yet supported in
production, so there are no live deployments to worry about.
This commit is contained in:
Viktor Torstensson 2026-06-04 19:03:16 +02:00
parent 3e5f36c0b8
commit 11115e7afa
No known key found for this signature in database
GPG key ID: 961CC8259AE675D4
2 changed files with 27 additions and 4 deletions

View file

@ -1,5 +1,7 @@
-- Drop indexes first.
DROP INDEX IF EXISTS kvstores_lookup_idx;
DROP INDEX IF EXISTS kvstores_feature_lookup_idx;
DROP INDEX IF EXISTS kvstores_group_lookup_idx;
DROP INDEX IF EXISTS kvstores_global_lookup_idx;
DROP INDEX IF EXISTS features_name_idx;
DROP INDEX IF EXISTS rules_name_idx;

View file

@ -50,8 +50,29 @@ CREATE TABLE IF NOT EXISTS kvstores (
entry_key TEXT NOT NULL,
-- The value of the entry.
value BLOB NOT NULL
value BLOB NOT NULL,
-- Feature-scoped kv stores must always belong to a session group.
CHECK (feature_id IS NULL OR group_id IS NOT NULL)
);
CREATE UNIQUE INDEX IF NOT EXISTS kvstores_lookup_idx
ON kvstores (entry_key, rule_id, perm, group_id, feature_id);
-- Mirror the legacy KVDB namespace semantics precisely. A kv store record is
-- uniquely identified by one of three namespace shapes:
-- 1. Global: entry_key + rule_id + perm
-- 2. Group scoped: entry_key + rule_id + perm + group_id
-- 3. Feature scoped: entry_key + rule_id + perm + group_id + feature_id
--
-- A single UNIQUE index across nullable columns is not sufficient here, as
-- SQL NULL handling can allow duplicates for the global and group-scoped
-- cases that the KVDB bucket layout would never permit.
CREATE UNIQUE INDEX IF NOT EXISTS kvstores_global_lookup_idx
ON kvstores (entry_key, rule_id, perm)
WHERE group_id IS NULL AND feature_id IS NULL;
CREATE UNIQUE INDEX IF NOT EXISTS kvstores_group_lookup_idx
ON kvstores (entry_key, rule_id, perm, group_id)
WHERE group_id IS NOT NULL AND feature_id IS NULL;
CREATE UNIQUE INDEX IF NOT EXISTS kvstores_feature_lookup_idx
ON kvstores (entry_key, rule_id, perm, group_id, feature_id)
WHERE feature_id IS NOT NULL;