@@ -354,9 +354,8 @@ def _create_private_workspace_query(
354354 product_name : ProductName ,
355355 user_id : UserID ,
356356 workspace_query : WorkspaceQuery ,
357- project_tags_subquery : sql .Subquery ,
358357 is_search_by_multi_columns : bool ,
359- user_groups : list [RowProxy ],
358+ user_groups : list [int ],
360359 ) -> sql .Select | None :
361360 private_workspace_query = None
362361 if workspace_query .workspace_scope is not WorkspaceScope .SHARED :
@@ -378,13 +377,39 @@ def _create_private_workspace_query(
378377 "delete" ,
379378 project_to_groups .c .delete ,
380379 ),
381- )
382- .filter (
383- project_to_groups .c .read # Filters out entries where "read" is False
384- )
385- .label ("access_rights" ),
386- ).group_by (project_to_groups .c .project_uuid )
387- ).subquery ("access_rights_subquery" )
380+ ).label ("access_rights" ),
381+ )
382+ .where (
383+ project_to_groups .c .project_uuid == projects .c .uuid
384+ ) # Correlate with main query
385+ .where (project_to_groups .c .read )
386+ .group_by (project_to_groups .c .project_uuid )
387+ .lateral () # Critical for per-row execution
388+ )
389+
390+ my_access_rights_subquery = (
391+ sa .select (
392+ project_to_groups .c .project_uuid ,
393+ sa .func .jsonb_object_agg (
394+ project_to_groups .c .gid ,
395+ sa .func .jsonb_build_object (
396+ "read" ,
397+ project_to_groups .c .read ,
398+ "write" ,
399+ project_to_groups .c .write ,
400+ "delete" ,
401+ project_to_groups .c .delete ,
402+ ),
403+ ).label ("access_rights" ),
404+ )
405+ .where (
406+ project_to_groups .c .read , # Filters out entries where "read" is False
407+ project_to_groups .c .gid .in_ (
408+ user_groups
409+ ), # Filters gid to be in user_groups
410+ )
411+ .group_by (project_to_groups .c .project_uuid )
412+ ).subquery ("my_access_rights_subquery" )
388413
389414 private_workspace_query = (
390415 sa .select (
@@ -393,13 +418,9 @@ def _create_private_workspace_query(
393418 access_rights_subquery .c .access_rights ,
394419 projects_to_products .c .product_name ,
395420 projects_to_folders .c .folder_id ,
396- sa .func .coalesce (
397- project_tags_subquery .c .tags ,
398- sa .cast (sa .text ("'{}'" ), sa .ARRAY (sa .Integer )),
399- ).label ("tags" ),
400421 )
401422 .select_from (
402- projects .join (access_rights_subquery , isouter = True )
423+ projects .join (my_access_rights_subquery )
403424 .join (projects_to_products )
404425 .join (
405426 projects_to_folders ,
@@ -409,21 +430,19 @@ def _create_private_workspace_query(
409430 ),
410431 isouter = True ,
411432 )
412- .join (project_tags_subquery , isouter = True )
433+ .join (
434+ access_rights_subquery ,
435+ access_rights_subquery .c .project_uuid == projects .c .uuid ,
436+ )
413437 )
414438 .where (
415- (
416- (projects .c .prj_owner == user_id )
417- | sa .text (
418- f"jsonb_exists_any(access_rights_subquery.access_rights, { assemble_array_groups (user_groups )} )"
419- )
420- )
421- & (projects .c .workspace_id .is_ (None )) # <-- Private workspace
439+ (projects .c .workspace_id .is_ (None )) # <-- Private workspace
422440 & (projects_to_products .c .product_name == product_name )
423441 )
424442 )
443+
425444 assert ( # nosec
426- access_rights_subquery .description == "access_rights_subquery "
445+ my_access_rights_subquery .description == "my_access_rights_subquery "
427446 )
428447
429448 if is_search_by_multi_columns :
@@ -438,9 +457,8 @@ def _create_shared_workspace_query(
438457 * ,
439458 product_name : ProductName ,
440459 workspace_query : WorkspaceQuery ,
441- project_tags_subquery : sql .Subquery ,
442- user_groups : list [RowProxy ],
443460 is_search_by_multi_columns : bool ,
461+ user_groups : list [int ],
444462 ) -> sql .Select | None :
445463
446464 if workspace_query .workspace_scope is not WorkspaceScope .PRIVATE :
@@ -450,6 +468,31 @@ def _create_shared_workspace_query(
450468 )
451469
452470 workspace_access_rights_subquery = (
471+ (
472+ sa .select (
473+ workspaces_access_rights .c .workspace_id ,
474+ sa .func .jsonb_object_agg (
475+ workspaces_access_rights .c .gid ,
476+ sa .func .jsonb_build_object (
477+ "read" ,
478+ workspaces_access_rights .c .read ,
479+ "write" ,
480+ workspaces_access_rights .c .write ,
481+ "delete" ,
482+ workspaces_access_rights .c .delete ,
483+ ),
484+ ).label ("access_rights" ),
485+ )
486+ .where (
487+ workspaces_access_rights .c .read ,
488+ )
489+ .group_by (workspaces_access_rights .c .workspace_id )
490+ )
491+ .subquery ("workspace_access_rights_subquery" )
492+ .lateral ()
493+ )
494+
495+ my_workspace_access_rights_subquery = (
453496 sa .select (
454497 workspaces_access_rights .c .workspace_id ,
455498 sa .func .jsonb_object_agg (
@@ -462,11 +505,14 @@ def _create_shared_workspace_query(
462505 "delete" ,
463506 workspaces_access_rights .c .delete ,
464507 ),
465- )
466- .filter (workspaces_access_rights .c .read )
467- .label ("access_rights" ),
468- ).group_by (workspaces_access_rights .c .workspace_id )
469- ).subquery ("workspace_access_rights_subquery" )
508+ ).label ("access_rights" ),
509+ )
510+ .where (
511+ workspaces_access_rights .c .read ,
512+ workspaces_access_rights .c .gid .in_ (user_groups ),
513+ )
514+ .group_by (workspaces_access_rights .c .workspace_id )
515+ ).subquery ("my_workspace_access_rights_subquery" )
470516
471517 shared_workspace_query = (
472518 sa .select (
@@ -475,16 +521,12 @@ def _create_shared_workspace_query(
475521 workspace_access_rights_subquery .c .access_rights ,
476522 projects_to_products .c .product_name ,
477523 projects_to_folders .c .folder_id ,
478- sa .func .coalesce (
479- project_tags_subquery .c .tags ,
480- sa .cast (sa .text ("'{}'" ), sa .ARRAY (sa .Integer )),
481- ).label ("tags" ),
482524 )
483525 .select_from (
484526 projects .join (
485- workspace_access_rights_subquery ,
527+ my_workspace_access_rights_subquery ,
486528 projects .c .workspace_id
487- == workspace_access_rights_subquery .c .workspace_id ,
529+ == my_workspace_access_rights_subquery .c .workspace_id ,
488530 )
489531 .join (projects_to_products )
490532 .join (
@@ -495,20 +537,17 @@ def _create_shared_workspace_query(
495537 ),
496538 isouter = True ,
497539 )
498- .join (project_tags_subquery , isouter = True )
499- )
500- .where (
501- (
502- sa .text (
503- f"jsonb_exists_any(workspace_access_rights_subquery.access_rights, { assemble_array_groups (user_groups )} )"
504- )
540+ .join (
541+ workspace_access_rights_subquery ,
542+ projects .c .workspace_id
543+ == workspace_access_rights_subquery .c .workspace_id ,
505544 )
506- & (projects_to_products .c .product_name == product_name )
507545 )
546+ .where (projects_to_products .c .product_name == product_name )
508547 )
509548 assert ( # nosec
510- workspace_access_rights_subquery .description
511- == "workspace_access_rights_subquery "
549+ my_workspace_access_rights_subquery .description
550+ == "my_workspace_access_rights_subquery "
512551 )
513552
514553 if workspace_query .workspace_scope == WorkspaceScope .ALL :
@@ -541,9 +580,7 @@ def _create_attributes_filters(
541580 filter_trashed : bool | None ,
542581 search_by_multi_columns : str | None ,
543582 search_by_project_name : str | None ,
544- filter_tag_ids_list : list [int ] | None ,
545583 folder_query : FolderQuery ,
546- project_tags_subquery : sql .Subquery ,
547584 ) -> list [ColumnElement ]:
548585 attributes_filters : list [ColumnElement ] = []
549586
@@ -581,14 +618,6 @@ def _create_attributes_filters(
581618 projects .c .name .like (f"%{ search_by_project_name } %" )
582619 )
583620
584- if filter_tag_ids_list :
585- attributes_filters .append (
586- sa .func .coalesce (
587- project_tags_subquery .c .tags ,
588- sa .cast (sa .text ("'{}'" ), sa .ARRAY (sa .Integer )),
589- ).op ("@>" )(filter_tag_ids_list )
590- )
591-
592621 if folder_query .folder_scope is not FolderScope .ALL :
593622 if folder_query .folder_scope == FolderScope .SPECIFIC :
594623 attributes_filters .append (
@@ -614,7 +643,6 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
614643 filter_published : bool | None = None ,
615644 filter_hidden : bool | None = False ,
616645 filter_trashed : bool | None = False ,
617- filter_tag_ids_list : list [int ] | None = None ,
618646 # search
619647 search_by_multi_columns : str | None = None ,
620648 search_by_project_name : str | None = None ,
@@ -624,20 +652,12 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
624652 # order
625653 order_by : OrderBy = DEFAULT_ORDER_BY ,
626654 ) -> tuple [list [ProjectDict ], list [ProjectType ], int ]:
627-
628- if filter_tag_ids_list is None :
629- filter_tag_ids_list = []
630-
631655 async with self .engine .acquire () as conn :
632- user_groups : list [RowProxy ] = await self ._list_user_groups (conn , user_id )
633- project_tags_subquery = (
634- sa .select (
635- projects_tags .c .project_id ,
636- sa .func .array_agg (projects_tags .c .tag_id ).label ("tags" ),
637- )
638- .where (projects_tags .c .project_id .is_not (None ))
639- .group_by (projects_tags .c .project_id )
640- ).subquery ("project_tags_subquery" )
656+ user_groups_proxy : list [RowProxy ] = await self ._list_user_groups (
657+ conn , user_id
658+ )
659+ user_groups_array_str = assemble_array_groups (user_groups_proxy )
660+ user_groups : list [int ] = [group .gid for group in user_groups_proxy ]
641661
642662 ###
643663 # Private workspace query
@@ -647,8 +667,8 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
647667 product_name = product_name ,
648668 user_id = user_id ,
649669 workspace_query = workspace_query ,
650- project_tags_subquery = project_tags_subquery ,
651670 is_search_by_multi_columns = search_by_multi_columns is not None ,
671+ user_groups_array_str = user_groups_array_str ,
652672 user_groups = user_groups ,
653673 )
654674
@@ -658,9 +678,8 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
658678 shared_workspace_query = self ._create_shared_workspace_query (
659679 product_name = product_name ,
660680 workspace_query = workspace_query ,
661- project_tags_subquery = project_tags_subquery ,
662- user_groups = user_groups ,
663681 is_search_by_multi_columns = search_by_multi_columns is not None ,
682+ user_groups = user_groups ,
664683 )
665684
666685 ###
@@ -674,9 +693,7 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
674693 filter_trashed = filter_trashed ,
675694 search_by_multi_columns = search_by_multi_columns ,
676695 search_by_project_name = search_by_project_name ,
677- filter_tag_ids_list = filter_tag_ids_list ,
678696 folder_query = folder_query ,
679- project_tags_subquery = project_tags_subquery ,
680697 )
681698
682699 ###
@@ -709,11 +726,13 @@ async def list_projects_dicts( # pylint: disable=too-many-arguments,too-many-st
709726
710727 if order_by .direction == OrderDirection .ASC :
711728 combined_query = combined_query .order_by (
712- sa .asc (getattr (projects .c , order_by .field ))
729+ sa .asc (getattr (projects .c , order_by .field )),
730+ projects .c .id ,
713731 )
714732 else :
715733 combined_query = combined_query .order_by (
716- sa .desc (getattr (projects .c , order_by .field ))
734+ sa .desc (getattr (projects .c , order_by .field )),
735+ projects .c .id ,
717736 )
718737
719738 prjs , prj_types = await self ._execute_without_permission_check (
0 commit comments