diff --git a/crates/ironclaw_filesystem/src/postgres.rs b/crates/ironclaw_filesystem/src/postgres.rs index cbb6bbcc263..fd8b7dd094e 100644 --- a/crates/ironclaw_filesystem/src/postgres.rs +++ b/crates/ironclaw_filesystem/src/postgres.rs @@ -2384,6 +2384,8 @@ const POSTGRES_ROOT_FILESYSTEM_SCHEMA: &str = concat!( include_str!("../../../migrations/V32__root_filesystem_sequences.sql"), "\n", include_str!("../../../migrations/V33__root_filesystem_ordered_index_rows.sql"), + "\n", + include_str!("../../../migrations/V34__root_filesystem_ordered_index_path_collation.sql"), ); #[cfg(test)] diff --git a/crates/ironclaw_filesystem/tests/db_root_filesystem_contract.rs b/crates/ironclaw_filesystem/tests/db_root_filesystem_contract.rs index 7aab64ddcd4..1ce05e53070 100644 --- a/crates/ironclaw_filesystem/tests/db_root_filesystem_contract.rs +++ b/crates/ironclaw_filesystem/tests/db_root_filesystem_contract.rs @@ -2592,10 +2592,7 @@ mod postgres_tests { let rows = fs .query_ordered( &tenant_prefix, - &Filter::Eq { - key: status.clone(), - value: IndexValue::Text("queued".into()), - }, + &Filter::All, &ironclaw_filesystem::OrderedPage::new( index_name, status, diff --git a/migrations/V34__root_filesystem_ordered_index_path_collation.sql b/migrations/V34__root_filesystem_ordered_index_path_collation.sql new file mode 100644 index 00000000000..cb62ca716c5 --- /dev/null +++ b/migrations/V34__root_filesystem_ordered_index_path_collation.sql @@ -0,0 +1,35 @@ +-- Keep ordered-projection path ranges bytewise, matching the authoritative +-- root_filesystem_entries.path column. +-- +-- Ordered queries use half-open descendant ranges. The database-default +-- collation can order '/' and '0' differently from bytewise ordering, making +-- an existing projected row invisible on databases initialized with locales +-- such as en_US.utf8. +-- +-- Compatibility: this changes only comparison/index ordering. Stored paths and +-- projection rows are unchanged. +-- +-- Rollback plan: +-- 1. Stop writers and ordered readers. +-- 2. Revert the column to the database-default collation: +-- ALTER TABLE root_filesystem_ordered_index_rows +-- ALTER COLUMN path TYPE TEXT COLLATE "default"; +-- 3. Recreate dependent indexes if PostgreSQL reports they were rebuilt or +-- invalidated by the ALTER COLUMN operation. + +DO $$ +BEGIN + IF to_regclass('root_filesystem_ordered_index_rows') IS NOT NULL + AND EXISTS ( + SELECT 1 + FROM pg_attribute + WHERE attrelid = to_regclass('root_filesystem_ordered_index_rows') + AND attname = 'path' + AND NOT attisdropped + AND attcollation <> 'pg_catalog."C"'::regcollation + ) + THEN + ALTER TABLE root_filesystem_ordered_index_rows + ALTER COLUMN path TYPE TEXT COLLATE "C"; + END IF; +END $$; diff --git a/tests/e2e/scenarios/test_reborn_v2_file_download.py b/tests/e2e/scenarios/test_reborn_v2_file_download.py index d459c78a5bc..e5b75db82ba 100644 --- a/tests/e2e/scenarios/test_reborn_v2_file_download.py +++ b/tests/e2e/scenarios/test_reborn_v2_file_download.py @@ -315,7 +315,9 @@ async def serve_stat(route): await expect(report).to_have_attribute("aria-selected", "true") await expect(report).to_have_attribute("tabindex", "0") assert await tree.locator('[role="treeitem"][tabindex="0"]').count() == 1 - await expect(guide).to_have_count(0) + await expect(guide).to_be_visible() + await expect(guide).to_have_attribute("aria-selected", "false") + await expect(guide).to_have_attribute("tabindex", "-1") # Directory-load errors are live alerts, so they are announced without a # separate pointer interaction.