From b6ceaae5079040506343f0c41543824e5a63ef7c Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Mon, 12 Oct 2026 23:12:52 +0200 Subject: [PATCH 1/2] test(dal): pin t9's plan to the identity index --- crates/dpp-dal/tests/pg_integration.rs | 38 +++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/dpp-dal/tests/pg_integration.rs b/crates/dpp-dal/tests/pg_integration.rs index 407f9f19..c05f3b5b 100644 --- a/crates/dpp-dal/tests/pg_integration.rs +++ b/crates/dpp-dal/tests/pg_integration.rs @@ -668,14 +668,13 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { let pg = start_pg().await; let repo = PgPassportRepo::new(pg.dal.clone()); - // Enough decoy rows that a seq scan and an index scan would visibly differ - // in the query plan — a handful of rows can fool the planner into a seq - // scan regardless of the index's existence. - for i in 0..200 { - let gtin = format!("{i:013}{}", check_digit_for(&format!("{i:013}"))); + // One decoy per key the lookup compares: the draft's identifier under + // another batch, and the draft's batch under the published passport's + // identifier. The second is also what `batch_id: None` below must skip. + for (gtin, batch) in [("09506000134352", "BATCH-X"), ("01234567890128", "BATCH-D")] { repo.create(battery_passport_with( - >in, - Some("DECOY"), + gtin, + Some(batch), PassportStatus::Draft, )) .await @@ -733,6 +732,17 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { // The identifier expression `0042` indexes, verbatim: Postgres uses an // expression index only for a query that repeats the expression. + // + // Sequential scans are priced out rather than outnumbered. On a table this + // small the planner rightly prefers one; this test used to insert 200 + // decoys, one commit each, to tip it, and on a busy runner that alone ran + // past the CI time budget. A disabled seq scan still appears when no index + // can serve the query, so the assertions below keep their meaning. + let mut tx = pg.dal.pool().begin().await.expect("begin"); + sqlx::query("SET LOCAL enable_seqscan = off") + .execute(&mut *tx) + .await + .expect("disable seq scan"); let plan_rows = sqlx::query( "EXPLAIN SELECT doc FROM odal.passport \ WHERE status IN ('draft','active') \ @@ -744,7 +754,7 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { AND doc->>'batchId' IS NOT DISTINCT FROM 'BATCH-D' \ LIMIT 1", ) - .fetch_all(pg.dal.pool()) + .fetch_all(&mut *tx) .await .expect("explain"); let plan: String = plan_rows @@ -752,8 +762,11 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { .map(|r| r.get::("QUERY PLAN")) .collect::>() .join("\n"); + // By name, not any index scan: `idx_passport_identifier` covers the same + // expression and `idx_passport_status` the status filter, so either would + // pass for the identity index if it were gone. assert!( - plan.contains("Index Scan") || plan.contains("Bitmap Index Scan"), + plan.contains("idx_passport_identity"), "expected idx_passport_identity to be used, got plan:\n{plan}" ); assert!( @@ -762,13 +775,6 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { ); } -/// GS1 mod-10 check digit for a 13-digit data prefix — lets the decoy loop -/// generate 200 distinct, individually valid GTIN-14s. -fn check_digit_for(data13: &str) -> u8 { - let digits: Vec = data13.bytes().map(|b| b - b'0').collect(); - dpp_domain::gs1_check_digit(&digits) -} - /// A structurally-valid but unsigned dossier — enough to persist/round-trip /// through `doc JSONB`; these tests exercise storage, not verification. fn minimal_dossier(passport_id: &str) -> DossierV1 { From 072b2e70aeb867227a8e5f90e4c044ce32b44a83 Mon Sep 17 00:00:00 2001 From: LKSNDRTMLKV Date: Mon, 12 Oct 2026 23:38:36 +0200 Subject: [PATCH 2/2] test(dal): explain find_by_identity's real query --- crates/dpp-dal/tests/pg_integration.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/dpp-dal/tests/pg_integration.rs b/crates/dpp-dal/tests/pg_integration.rs index c05f3b5b..62854704 100644 --- a/crates/dpp-dal/tests/pg_integration.rs +++ b/crates/dpp-dal/tests/pg_integration.rs @@ -730,8 +730,12 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { "a batch mismatch must not fall back to matching on gtin alone" ); - // The identifier expression `0042` indexes, verbatim: Postgres uses an - // expression index only for a query that repeats the expression. + // The statement `find_by_identity` sends, with the draft's values in place + // of its parameters. Its whole shape matters, not only the WHERE clause: + // the ORDER BY and the absence of a LIMIT change what the planner + // optimises for. The identifier expression is the one `0042` indexes, + // verbatim: Postgres uses an expression index only for a query that + // repeats the expression. // // Sequential scans are priced out rather than outnumbered. On a table this // small the planner rightly prefers one; this test used to insert 200 @@ -752,7 +756,8 @@ async fn t9_find_by_identity_matches_draft_and_published_via_index() { doc->'productGroupData'->'productIdentifier'->>'did', \ doc->'productGroupData'->>'gtin')) = '09506000134352' \ AND doc->>'batchId' IS NOT DISTINCT FROM 'BATCH-D' \ - LIMIT 1", + AND doc->>'serialNumber' IS NOT DISTINCT FROM NULL \ + ORDER BY created_at", ) .fetch_all(&mut *tx) .await