@@ -92,6 +92,44 @@ $$;--> statement-breakpoint
9292CALL backfill_document_storage_key_0222();-- > statement-breakpoint
9393DROP PROCEDURE backfill_document_storage_key_0222();-- > statement-breakpoint
9494
95+ -- (1b) Fully URL-decode keys that carry encodings beyond the separator (e.g.
96+ -- '%2C' -> ',', '%26' -> '&', multi-byte UTF-8 like '%C3%ADa' -> 'ía'). The app
97+ -- derives the canonical key with decodeURIComponent (extractStorageKey), so a
98+ -- storage_key that is only %2F-decoded would never match the runtime key for
99+ -- these documents — breaking their liveness lookups and stranding their
100+ -- ownership bindings. The bulk pass above already handled '%2F'; only the
101+ -- remaining residual-'%' rows are re-derived here (a small subset), so the heavy
102+ -- decode never runs over the whole table. This single UPDATE evaluates its target
103+ -- set once, so a key that legitimately contains a literal '%' is not reprocessed.
104+ CREATE OR REPLACE FUNCTION url_decode_0222 (input text ) RETURNS text LANGUAGE sql IMMUTABLE AS $$
105+ SELECT CASE
106+ WHEN input IS NULL THEN NULL
107+ WHEN position(' %' IN input) = 0 THEN input
108+ ELSE convert_from(
109+ (
110+ SELECT string_agg(
111+ CASE
112+ WHEN r .m [1 ] ~ ' ^%[0-9A-Fa-f]{2}$' THEN decode(substring (r .m [1 ] FROM 2 ), ' hex' )
113+ ELSE convert_to(r .m [1 ], ' UTF8' )
114+ END,
115+ ' ' ::bytea ORDER BY r .ord
116+ )
117+ FROM regexp_matches(input, ' %[0-9A-Fa-f]{2}|.' , ' g' ) WITH ORDINALITY AS r(m, ord)
118+ ),
119+ ' UTF8'
120+ )
121+ END
122+ $$;-- > statement-breakpoint
123+ UPDATE " document"
124+ SET " storage_key" = regexp_replace(
125+ url_decode_0222(substring (split_part(" file_url" , ' ?' , 1 ) FROM ' /api/files/serve/(.*)$' )),
126+ ' ^(s3|blob)/' ,
127+ ' '
128+ )
129+ WHERE " storage_key" LIKE ' kb/%'
130+ AND position(' %' IN " storage_key" ) > 0 ;-- > statement-breakpoint
131+ DROP FUNCTION url_decode_0222(text );-- > statement-breakpoint
132+
95133CREATE INDEX CONCURRENTLY IF NOT EXISTS " doc_storage_key_idx" ON " document" USING btree (" storage_key" ) WHERE " storage_key" IS NOT NULL ;-- > statement-breakpoint
96134
97135-- (2) Backfill workspace_files ownership bindings from the populated storage_key.
0 commit comments