@@ -544,6 +544,43 @@ class DriftProposalsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
544544 return input.replaceAll ("'" , "''" );
545545 }
546546
547+ /// Returns the SQL subquery for counting comments on a proposal.
548+ String _getCommentsCountSubquery () {
549+ return '''
550+ (
551+ SELECT COUNT(*)
552+ FROM documents_v2 c
553+ WHERE c.ref_id = p.id AND c.ref_ver = p.ver AND c.type = ?
554+ ) as comments_count
555+ ''' ;
556+ }
557+
558+ /// Returns the SQL for common JOINs used in proposal queries.
559+ ///
560+ /// Includes:
561+ /// - `documents_local_metadata` for favorites
562+ /// - `documents_v2` alias 'origin' for original authors (first version)
563+ /// - `documents_v2` alias 't' for template
564+ String _getCommonJoins () {
565+ return '''
566+ LEFT JOIN documents_local_metadata dlm ON p.id = dlm.id
567+ LEFT JOIN documents_v2 origin ON p.id = origin.id AND origin.id = origin.ver AND origin.type = ?
568+ LEFT JOIN documents_v2 t ON p.template_id = t.id AND p.template_ver = t.ver AND t.type = ?
569+ ''' ;
570+ }
571+
572+ /// Returns the SQL SELECT columns from common JOINs.
573+ ///
574+ /// Includes:
575+ /// - `origin_authors` : Authors from the first version (origin table)
576+ /// - `is_favorite` : Favorite status from local metadata
577+ String _getCommonSelectColumns () {
578+ return '''
579+ origin.authors as origin_authors,
580+ COALESCE(dlm.is_favorite, 0) as is_favorite
581+ ''' ;
582+ }
583+
547584 /// Returns the Common Table Expression (CTE) string defining "Effective Proposals".
548585 ///
549586 /// **CTE Stages:**
@@ -630,6 +667,21 @@ class DriftProposalsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
630667 .trim ();
631668 }
632669
670+ /// Returns the SQL subquery for getting all version IDs of a proposal.
671+ String _getVersionIdsSubquery () {
672+ return '''
673+ (
674+ SELECT GROUP_CONCAT(v_list.ver, ',')
675+ FROM (
676+ SELECT ver
677+ FROM documents_v2 v_sub
678+ WHERE v_sub.id = p.id AND v_sub.type = ?
679+ ORDER BY v_sub.ver ASC
680+ ) v_list
681+ ) as version_ids_str
682+ ''' ;
683+ }
684+
633685 /// Processes a database row from [getCollaboratorsActions] query.
634686 Map <String , RawProposalCollaboratorsActions > _processCollaboratorsActions (
635687 List <TypedResult > rows,
@@ -680,6 +732,123 @@ class DriftProposalsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
680732 return tempMap.map ((key, value) => MapEntry (key, RawProposalCollaboratorsActions (value)));
681733 }
682734
735+ /// Internal query to fetch a single proposal by [DocumentRef] .
736+ ///
737+ /// Uses the [_getValidActionsCTE] to resolve action status:
738+ /// - Checks latest action (any version) first - if 'hide', returns 'hide'
739+ /// - Otherwise gets action for specific version to determine draft/final status
740+ ///
741+ // TODO(LynxLynxx): For hidden proposals, consider optimizing by not fetching
742+ // all data (template, comments, versions, etc.) since UI may only need to
743+ // show "proposal is hidden". This would require RawProposalEntity to support
744+ // default/empty values for its fields or a separate HiddenProposalEntity.
745+ Selectable <RawProposalEntity > _queryProposal (DocumentRef ref) {
746+ final proposalColumns = _buildPrefixedColumns ('p' , 'p' );
747+ final templateColumns = _buildPrefixedColumns ('t' , 't' );
748+ final validActionsCTE = _getValidActionsCTE ();
749+ final versionIdsSubquery = _getVersionIdsSubquery ();
750+ final commentsCountSubquery = _getCommentsCountSubquery ();
751+ final commonJoins = _getCommonJoins ();
752+ final commonSelectColumns = _getCommonSelectColumns ();
753+
754+ final query =
755+ '''
756+ WITH $validActionsCTE
757+ SELECT
758+ $proposalColumns ,
759+ $templateColumns ,
760+
761+ -- First check if latest action (any version) is 'hide'
762+ -- If hidden, return 'hide'; otherwise get action for THIS specific version
763+ COALESCE(
764+ (
765+ SELECT
766+ CASE
767+ WHEN COALESCE(json_extract(va_latest.content, '\$ .action'), 'draft') = 'hide'
768+ THEN 'hide'
769+ ELSE NULL
770+ END
771+ FROM valid_actions va_latest
772+ WHERE va_latest.ref_id = p.id
773+ ORDER BY va_latest.ver DESC LIMIT 1
774+ ),
775+ (
776+ SELECT COALESCE(json_extract(va.content, '\$ .action'), 'draft')
777+ FROM valid_actions va
778+ WHERE va.ref_id = p.id AND va.ref_ver = p.ver
779+ ORDER BY va.ver DESC LIMIT 1
780+ ),
781+ 'draft'
782+ ) as action_type,
783+ $versionIdsSubquery ,
784+ $commentsCountSubquery ,
785+ $commonSelectColumns
786+ FROM documents_v2 p
787+ $commonJoins
788+ WHERE p.id = ? AND p.ver = ? AND p.type = ?
789+ ''' ;
790+
791+ return customSelect (
792+ query,
793+ variables: [
794+ // CTE Variable
795+ Variable .withString (DocumentType .proposalActionDocument.uuid),
796+ // Subquery Variables
797+ Variable .withString (DocumentType .proposalDocument.uuid),
798+ Variable .withString (DocumentType .commentDocument.uuid),
799+ // Main Join Variables
800+ Variable .withString (DocumentType .proposalDocument.uuid),
801+ Variable .withString (DocumentType .proposalTemplate.uuid),
802+ // WHERE clause
803+ Variable .withString (ref.id),
804+ Variable .withString (ref.ver ?? '' ),
805+ Variable .withString (DocumentType .proposalDocument.uuid),
806+ ],
807+ readsFrom: {
808+ documentsV2,
809+ documentsLocalMetadata,
810+ documentAuthors,
811+ },
812+ ).map ((row) {
813+ final proposalData = {
814+ for (final col in documentsV2.$columns)
815+ col.$name: row.readNullableWithType (col.type, 'p_${col .$name }' ),
816+ };
817+ final proposal = documentsV2.map (proposalData);
818+
819+ final templateData = {
820+ for (final col in documentsV2.$columns)
821+ col.$name: row.readNullableWithType (col.type, 't_${col .$name }' ),
822+ };
823+
824+ final template = templateData['id' ] != null ? documentsV2.map (templateData) : null ;
825+
826+ final actionTypeRaw = row.readNullable <String >('action_type' ) ?? '' ;
827+ final actionType =
828+ ProposalSubmissionActionDto .fromJson (actionTypeRaw)? .toModel () ??
829+ ProposalSubmissionAction .draft;
830+
831+ final versionIdsRaw = row.readNullable <String >('version_ids_str' ) ?? '' ;
832+ final versionIds = versionIdsRaw.split (',' );
833+
834+ final commentsCount = row.readNullable <int >('comments_count' ) ?? 0 ;
835+ final isFavorite = (row.readNullable <int >('is_favorite' ) ?? 0 ) == 1 ;
836+
837+ final originalAuthorsRaw = row.readNullable <String >('origin_authors' );
838+ final originalAuthors = DocumentConverters .catId.fromSql (originalAuthorsRaw ?? '' );
839+
840+ return RawProposalEntity (
841+ proposal: proposal,
842+ template: template,
843+ actionType: actionType,
844+ versionIds: versionIds,
845+ commentsCount: commentsCount,
846+ isFavorite: isFavorite,
847+ originalAuthors: originalAuthors,
848+ );
849+ });
850+ }
851+
683852 /// Internal query to calculate total ask.
684853 ///
685854 /// Similar to the main CTE but filters specifically for `effective_final_proposals` .
@@ -781,37 +950,24 @@ class DriftProposalsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
781950 final whereClause = filterClauses.isEmpty ? '' : 'AND ${filterClauses .join (' AND ' )}' ;
782951
783952 final effectiveProposals = _getEffectiveProposalsCTE ();
953+ final versionIdsSubquery = _getVersionIdsSubquery ();
954+ final commentsCountSubquery = _getCommentsCountSubquery ();
955+ final commonJoins = _getCommonJoins ();
956+ final commonSelectColumns = _getCommonSelectColumns ();
957+
784958 final cteQuery =
785959 '''
786960 WITH $effectiveProposals
787- SELECT
788- $proposalColumns ,
789- $templateColumns ,
961+ SELECT
962+ $proposalColumns ,
963+ $templateColumns ,
790964 ep.action_type,
791-
792- (
793- SELECT GROUP_CONCAT(v_list.ver, ',')
794- FROM (
795- SELECT ver
796- FROM documents_v2 v_sub
797- WHERE v_sub.id = p.id AND v_sub.type = ?
798- ORDER BY v_sub.ver ASC
799- ) v_list
800- ) as version_ids_str,
801-
802- (
803- SELECT COUNT(*)
804- FROM documents_v2 c
805- WHERE c.ref_id = p.id AND c.ref_ver = p.ver AND c.type = ?
806- ) as comments_count,
807-
808- origin.authors as origin_authors,
809- COALESCE(dlm.is_favorite, 0) as is_favorite
965+ $versionIdsSubquery ,
966+ $commentsCountSubquery ,
967+ $commonSelectColumns
810968 FROM documents_v2 p
811969 INNER JOIN effective_proposals ep ON p.id = ep.id AND p.ver = ep.ver
812- LEFT JOIN documents_local_metadata dlm ON p.id = dlm.id
813- LEFT JOIN documents_v2 origin ON p.id = origin.id AND origin.id = origin.ver AND origin.type = ?
814- LEFT JOIN documents_v2 t ON p.template_id = t.id AND p.template_ver = t.ver AND t.type = ?
970+ $commonJoins
815971 WHERE p.type = ? $whereClause
816972 ORDER BY $orderByClause
817973 LIMIT ? OFFSET ?
@@ -881,135 +1037,6 @@ class DriftProposalsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
8811037 });
8821038 }
8831039
884- /// Internal query to fetch a single proposal by [DocumentRef] .
885- ///
886- /// Uses the [_getValidActionsCTE] to resolve action status:
887- /// - Checks latest action (any version) first - if 'hide', returns 'hide'
888- /// - Otherwise gets action for specific version to determine draft/final status
889- ///
890- // TODO(LynxLynxx): For hidden proposals, consider optimizing by not fetching
891- // all data (template, comments, versions, etc.) since UI may only need to
892- // show "proposal is hidden". This would require RawProposalEntity to support
893- // default/empty values for its fields or a separate HiddenProposalEntity.
894- Selectable <RawProposalEntity > _queryProposal (DocumentRef ref) {
895- final proposalColumns = _buildPrefixedColumns ('p' , 'p' );
896- final templateColumns = _buildPrefixedColumns ('t' , 't' );
897-
898- final validActionsCTE = _getValidActionsCTE ();
899- final query = '''
900- WITH $validActionsCTE
901- SELECT
902- $proposalColumns ,
903- $templateColumns ,
904-
905- -- First check if latest action (any version) is 'hide'
906- -- If hidden, return 'hide'; otherwise get action for THIS specific version
907- COALESCE(
908- (
909- SELECT
910- CASE
911- WHEN COALESCE(json_extract(va_latest.content, '\$ .action'), 'draft') = 'hide'
912- THEN 'hide'
913- ELSE NULL
914- END
915- FROM valid_actions va_latest
916- WHERE va_latest.ref_id = p.id
917- ORDER BY va_latest.ver DESC LIMIT 1
918- ),
919- (
920- SELECT COALESCE(json_extract(va.content, '\$ .action'), 'draft')
921- FROM valid_actions va
922- WHERE va.ref_id = p.id AND va.ref_ver = p.ver
923- ORDER BY va.ver DESC LIMIT 1
924- ),
925- 'draft'
926- ) as action_type,
927-
928- (
929- SELECT GROUP_CONCAT(v_list.ver, ',')
930- FROM (
931- SELECT ver
932- FROM documents_v2 v_sub
933- WHERE v_sub.id = p.id AND v_sub.type = ?
934- ORDER BY v_sub.ver ASC
935- ) v_list
936- ) as version_ids_str,
937-
938- (
939- SELECT COUNT(*)
940- FROM documents_v2 c
941- WHERE c.ref_id = p.id AND c.ref_ver = p.ver AND c.type = ?
942- ) as comments_count,
943-
944- origin.authors as origin_authors,
945- COALESCE(dlm.is_favorite, 0) as is_favorite
946- FROM documents_v2 p
947- LEFT JOIN documents_local_metadata dlm ON p.id = dlm.id
948- LEFT JOIN documents_v2 origin ON p.id = origin.id AND origin.id = origin.ver AND origin.type = ?
949- LEFT JOIN documents_v2 t ON p.template_id = t.id AND p.template_ver = t.ver AND t.type = ?
950- WHERE p.id = ? AND p.ver = ? AND p.type = ?
951- ''' ;
952-
953- return customSelect (
954- query,
955- variables: [
956- // CTE Variable
957- Variable .withString (DocumentType .proposalActionDocument.uuid),
958- // Subquery Variables
959- Variable .withString (DocumentType .proposalDocument.uuid),
960- Variable .withString (DocumentType .commentDocument.uuid),
961- // Main Join Variables
962- Variable .withString (DocumentType .proposalDocument.uuid),
963- Variable .withString (DocumentType .proposalTemplate.uuid),
964- // WHERE clause
965- Variable .withString (ref.id),
966- Variable .withString (ref.ver ?? '' ),
967- Variable .withString (DocumentType .proposalDocument.uuid),
968- ],
969- readsFrom: {
970- documentsV2,
971- documentsLocalMetadata,
972- documentAuthors,
973- },
974- ).map ((row) {
975- final proposalData = {
976- for (final col in documentsV2.$columns)
977- col.$name: row.readNullableWithType (col.type, 'p_${col .$name }' ),
978- };
979- final proposal = documentsV2.map (proposalData);
980-
981- final templateData = {
982- for (final col in documentsV2.$columns)
983- col.$name: row.readNullableWithType (col.type, 't_${col .$name }' ),
984- };
985-
986- final template = templateData['id' ] != null ? documentsV2.map (templateData) : null ;
987-
988- final actionTypeRaw = row.readNullable <String >('action_type' ) ?? '' ;
989- final actionType = ProposalSubmissionActionDto .fromJson (actionTypeRaw)? .toModel () ??
990- ProposalSubmissionAction .draft;
991-
992- final versionIdsRaw = row.readNullable <String >('version_ids_str' ) ?? '' ;
993- final versionIds = versionIdsRaw.split (',' );
994-
995- final commentsCount = row.readNullable <int >('comments_count' ) ?? 0 ;
996- final isFavorite = (row.readNullable <int >('is_favorite' ) ?? 0 ) == 1 ;
997-
998- final originalAuthorsRaw = row.readNullable <String >('origin_authors' );
999- final originalAuthors = DocumentConverters .catId.fromSql (originalAuthorsRaw ?? '' );
1000-
1001- return RawProposalEntity (
1002- proposal: proposal,
1003- template: template,
1004- actionType: actionType,
1005- versionIds: versionIds,
1006- commentsCount: commentsCount,
1007- isFavorite: isFavorite,
1008- originalAuthors: originalAuthors,
1009- );
1010- });
1011- }
1012-
10131040 bool _shouldReturnEarlyFor ({
10141041 required ProposalsFiltersV2 filters,
10151042 int ? size,
0 commit comments