Add project to attributes update service

What does this MR do and why?

Add project to attributes update service that attaches and detaches projects with attributes, while respecting the category multiple_selection value.

Changelog: added
EE: true

Query plans

upsert_all

Raw SQL
INSERT INTO "project_to_security_attributes" ("project_id", "security_attribute_id", "traversal_ids", "created_at", "updated_at")
    VALUES (71329491, 1, '{9970,96981785,97058478,98867842,100569068}', '2025-08-31 15:24:21.809314', '2025-08-31 15:24:21.809314'),
    (71329491, 2, '{9970,96981785,97058478,98867842,100569068}', '2025-08-31 15:24:21.809314', '2025-08-31 15:24:21.809314')
ON CONFLICT ("id")
    DO UPDATE SET
        "project_id" = excluded."project_id",
        "security_attribute_id" = excluded."security_attribute_id",
        "traversal_ids" = excluded."traversal_ids",
        "created_at" = excluded."created_at",
        "updated_at" = excluded."updated_at"
    RETURNING
        "id"
Plan

See full plan here.

 ModifyTable on public.project_to_security_attributes  (cost=0.00..0.03 rows=2 width=72) (actual time=0.991..1.119 rows=2 loops=1)
   Buffers: shared hit=79 read=5 dirtied=10 written=5
   WAL: records=17 fpi=0 bytes=1507
   I/O Timings: read=0.192 write=0.138
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=72) (actual time=0.216..0.224 rows=2 loops=1)
         Buffers: shared hit=15 read=1 dirtied=1
         WAL: records=1 fpi=0 bytes=99
         I/O Timings: read=0.088 write=0.000
Trigger RI_ConstraintTrigger_c_3639295303 for constraint fk_rails_5fe496b3fb: time=2.254 calls=2
Settings: seq_page_cost = '4', work_mem = '100MB', random_page_cost = '1.5', effective_cache_size = '338688MB', jit = 'off'

delete_all - previous version

Raw SQL
DELETE FROM "project_to_security_attributes"
WHERE "project_to_security_attributes"."project_id" = 71329491
    AND "project_to_security_attributes"."id" IN (1, 2, 3, 4)
Plan

See full plan here.

 ModifyTable on public.project_to_security_attributes  (cost=0.14..3.17 rows=0 width=0) (actual time=0.056..0.057 rows=0 loops=1)
   Buffers: shared hit=7
   WAL: records=2 fpi=0 bytes=108
   I/O Timings: read=0.000 write=0.000
   ->  Index Scan using index_project_security_attributes_project_id_unique on public.project_to_security_attributes  (cost=0.14..3.17 rows=1 width=6) (actual time=0.028..0.029 rows=2 loops=1)
         Index Cond: (project_to_security_attributes.project_id = 71329491)
         Filter: (project_to_security_attributes.id = ANY ('{1,2,3,4}'::bigint[]))
         Rows Removed by Filter: 0
         Buffers: shared hit=5
         I/O Timings: read=0.000 write=0.000
Settings: effective_cache_size = '338688MB', jit = 'off', seq_page_cost = '4', work_mem = '100MB', random_page_cost = '1.5'

delete_all - new version

Raw SQL
DELETE FROM "project_to_security_attributes"
WHERE "project_to_security_attributes"."id" IN (1, 2, 3, 4)
Plan

See full plan here.

 ModifyTable on public.project_to_security_attributes  (cost=0.00..0.00 rows=0 width=0) (actual time=0.005..0.005 rows=0 loops=1)
   I/O Timings: read=0.000 write=0.000
   ->  Seq Scan on public.project_to_security_attributes  (cost=0.00..0.00 rows=1 width=6) (actual time=0.004..0.004 rows=0 loops=1)
         Filter: (project_to_security_attributes.id = ANY ('{1,2,3,4}'::bigint[]))
         Rows Removed by Filter: 0
         I/O Timings: read=0.000 write=0.000
Settings: random_page_cost = '1.5', effective_cache_size = '338688MB', jit = 'off', seq_page_cost = '4', work_mem = '100MB'

category_multiple_selection_constraint

Raw SQL
SELECT
    1 AS one
FROM
    "security_attributes"
    INNER JOIN "project_to_security_attributes" ON "security_attributes"."id" = "project_to_security_attributes"."security_attribute_id"
WHERE
    "project_to_security_attributes"."project_id" = 68794259
    AND "security_attributes"."security_category_id" = 1
    AND "security_attributes"."id" != 1
LIMIT 1

Plan

See full plan here.

 Limit  (cost=0.00..0.01 rows=1 width=4) (actual time=0.006..0.007 rows=0 loops=1)
   I/O Timings: read=0.000 write=0.000
   ->  Nested Loop  (cost=0.00..0.01 rows=1 width=4) (actual time=0.005..0.005 rows=0 loops=1)
         I/O Timings: read=0.000 write=0.000
         ->  Seq Scan on public.security_attributes  (cost=0.00..0.00 rows=1 width=8) (actual time=0.004..0.004 rows=0 loops=1)
               Filter: ((security_attributes.id <> 1) AND (security_attributes.security_category_id = 1))
               Rows Removed by Filter: 0
               I/O Timings: read=0.000 write=0.000
         ->  Seq Scan on public.project_to_security_attributes  (cost=0.00..0.00 rows=1 width=8) (actual time=0.000..0.000 rows=0 loops=0)
               Filter: (project_to_security_attributes.project_id = 68794259)
               Rows Removed by Filter: 0
               I/O Timings: read=0.000 write=0.000
Settings: effective_cache_size = '338688MB', jit = 'off', seq_page_cost = '4', random_page_cost = '1.5', work_mem = '100MB'

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Relates to Add attributes attach service (#566385 - closed) • Gal Katz • 18.4

Edited by Gal Katz

Merge request reports

Loading