Skip to content

[f] 116 - Implement sort feature (BE)#6

Merged
giahung68 merged 5 commits intomainfrom
features/sort-feature
Nov 25, 2024
Merged

[f] 116 - Implement sort feature (BE)#6
giahung68 merged 5 commits intomainfrom
features/sort-feature

Conversation

@giahung68
Copy link
Copy Markdown
Collaborator

@giahung68 giahung68 commented Nov 25, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced comment and like counts for snippets, ensuring accurate reflection of user interactions.
    • Improved filtering options for audio sources, providing clearer labels for radio stations.
    • Expanded snippet retrieval functionality with new sorting and filtering capabilities.
    • New functions and triggers introduced to automate the updating of like and comment counts for snippets.
    • Added indexing for comment and like counts to optimize query performance.
  • Bug Fixes

    • Automatic updating of like and comment counts through new triggers for improved data integrity.
  • Chores

    • SQL migrations and functions added to streamline database operations and optimize performance.

@giahung68 giahung68 requested a review from nhphong November 25, 2024 04:05
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 25, 2024

Walkthrough

This pull request introduces several SQL changes to enhance database functionality. Key updates include SQL migration scripts to update the comment_count and like_count fields in the snippets table, a new trigger for automatic like count updates, and new functions for adjusting comment and like counts based on database operations. Modifications to the get_filtering_options and get_snippets functions improve data retrieval and filtering capabilities, providing more descriptive outputs and enhanced sorting options.

Changes

File Path Change Summary
supabase/database/sql/comment_count_migration.sql Introduced SQL update statement to modify comment_count in snippets table, counting non-deleted comments, and created index idx_snippets_comment_count.
supabase/database/sql/get_filtering_options_function.sql Modified get_filtering_options function to include radio_station_name in the selection of distinct records and updated label construction logic.
supabase/database/sql/get_snippets_function.sql Renamed get_snippets to get_snippets_order_by, added p_order_by parameter, enhanced filtering for upvotedBy, expanded ordering capabilities, and simplified return structure.
supabase/database/sql/like_count_migration.sql Introduced SQL migration script to update like_count in snippets table and created index idx_snippets_like_count on like_count.
supabase/database/sql/like_count_trigger.sql Added new trigger update_like_count to automatically update like counts after operations on user_like_snippets table.
supabase/database/sql/update_snippet_comment_count.sql Introduced update_snippet_comment_count function to increment/decrement comment_count on INSERT/DELETE operations.
supabase/database/sql/update_snippet_like_count.sql Introduced update_snippet_like_count function to update like_count based on entries in user_like_snippets table.
supabase/database/sql/update_snippet_upvote_count.sql Introduced update_snippet_upvote_count function to update upvote_count based on entries in label_upvotes table, triggered by changes in snippet_labels.
supabase/database/sql/upvote_count_migration.sql Introduced SQL migration script to update upvote_count in snippets table and created index idx_snippets_upvote_count on upvote_count.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SnippetDB as Snippets Database
    participant CommentsDB as Comments Database
    participant LikesDB as Likes Database

    User->>LikesDB: Like/Unlike Snippet
    LikesDB->>SnippetDB: Trigger update_like_count
    SnippetDB-->>SnippetDB: Update like_count

    User->>CommentsDB: Add/Delete Comment
    CommentsDB->>SnippetDB: Trigger update_snippet_comment_count
    SnippetDB-->>SnippetDB: Update comment_count
Loading

🐰 "In the database, changes abound,
Like counts and comments, joyfully found.
Functions and triggers, all in a row,
Snippets now shine with a vibrant glow!
Hopping through SQL, we celebrate,
With every update, we elevate!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Outside diff range and nitpick comments (11)
supabase/database/sql/comment_count_migration.sql (1)

1-7: Consider performance optimization for large tables.

For large tables, consider batching the updates to prevent long-running transactions:

CREATE OR REPLACE PROCEDURE batch_update_comment_counts(
  batch_size INTEGER DEFAULT 1000
)
LANGUAGE plpgsql
AS $$
DECLARE
  total_rows INTEGER;
  processed_rows INTEGER := 0;
BEGIN
  SELECT COUNT(*) INTO total_rows FROM snippets;
  
  WHILE processed_rows < total_rows LOOP
    WITH batch AS (
      SELECT id 
      FROM snippets 
      ORDER BY id 
      LIMIT batch_size 
      OFFSET processed_rows
    )
    UPDATE snippets s
    SET comment_count = (
      SELECT COUNT(*)
      FROM comments c
      WHERE c.room_id = s.id
      AND deleted_at is null
    )
    WHERE s.id IN (SELECT id FROM batch);
    
    processed_rows := processed_rows + batch_size;
    RAISE NOTICE 'Processed % of % rows', LEAST(processed_rows, total_rows), total_rows;
    COMMIT;
  END LOOP;
END;
$$;

You can then execute this as:

CALL batch_update_comment_counts(1000);
supabase/database/sql/like_count_trigger.sql (1)

1-4: Consider monitoring trigger performance impact.

Since this trigger will execute on every like operation, consider monitoring its performance impact on high-traffic deployments.

Recommendations:

  1. Ensure proper indexing exists on the user_like_snippets table columns used in the count calculation
  2. Consider implementing a batch update strategy if performance becomes a concern
  3. Monitor trigger execution time in production
supabase/database/sql/like_count_migration.sql (1)

1-7: Consider batching the UPDATE operation for large datasets

For large tables, updating all rows at once can cause long-running transactions and table locks. Consider batching the updates to reduce the impact on database performance.

Here's a suggested approach using batching:

DO $$
DECLARE
    batch_size INTEGER := 1000;
    max_id INTEGER;
    current_id INTEGER := 0;
BEGIN
    SELECT MAX(id) INTO max_id FROM snippets;
    
    WHILE current_id <= max_id LOOP
        UPDATE snippets s
        SET like_count = (
            SELECT COUNT(*)
            FROM user_like_snippets uls
            WHERE uls.snippet = s.id
            AND uls.value = 1
        )
        WHERE s.id > current_id
        AND s.id <= current_id + batch_size;
        
        current_id := current_id + batch_size;
        COMMIT;
    END LOOP;
END $$;
supabase/database/sql/update_snippet_like_count.sql (1)

5-12: Verify concurrent update handling.

The current implementation might be susceptible to race conditions during concurrent updates. Consider adding a row-level lock to ensure consistency.

Consider this modification:

 UPDATE snippets
+    SET like_count = (
+        SELECT COUNT(*)
+        FROM user_like_snippets
+        WHERE snippet = COALESCE(NEW.snippet, OLD.snippet)
+        AND value = 1
+    )
-    WHERE id = COALESCE(NEW.snippet, OLD.snippet);
+    WHERE id = COALESCE(NEW.snippet, OLD.snippet)
+    FOR UPDATE;
supabase/database/sql/update_snippet_comment_count.sql (1)

1-19: Consider soft delete implications

If comments implement soft delete, this trigger might need adjustment to handle both soft and hard deletes appropriately.

Consider:

  1. Adding a condition to check for soft deleted status
  2. Creating a separate trigger for soft delete operations
  3. Maintaining separate counters for active and deleted comments

Would you like me to provide an implementation that handles soft deletes?

supabase/database/sql/update_snippet_upvote_count.sql (1)

13-21: Add concurrency control mechanism

The update operation could be subject to race conditions in a concurrent environment. Consider adding explicit locking or using atomic operations.

    IF snippet_id IS NOT NULL THEN
+       -- Lock the snippets row to prevent concurrent updates
+       PERFORM 1 FROM snippets WHERE id = snippet_id FOR UPDATE;
        UPDATE snippets
        SET upvote_count = (
            SELECT COUNT(*)
            FROM label_upvotes lu
            JOIN snippet_labels sl ON lu.snippet_label = sl.id
            WHERE sl.snippet = snippet_id
        )
        WHERE id = snippet_id;
    END IF;
supabase/database/sql/get_filtering_options_function.sql (3)

79-83: Consider handling NULL radio_station_code case.

While the label construction logic is good, it might be worth handling the edge case where radio_station_code is NULL despite the WHERE clause filter.

Consider updating the CASE statement:

-            'label', CASE 
-                WHEN radio_station_name IS NOT NULL 
-                THEN radio_station_name || ' - ' || radio_station_code
-                ELSE radio_station_code
-            END,
+            'label', CASE
+                WHEN radio_station_name IS NOT NULL AND radio_station_code IS NOT NULL
+                THEN radio_station_name || ' - ' || radio_station_code
+                WHEN radio_station_code IS NOT NULL
+                THEN radio_station_code
+                ELSE 'Unknown'
+            END,

71-75: Consider adding ORDER BY clause for consistent results.

The sources query lacks explicit ordering, which might lead to inconsistent ordering of radio stations in the UI. Consider adding an ORDER BY clause for a consistent user experience.

    SELECT DISTINCT 
        radio_station_code,
        radio_station_name
    FROM public.audio_files
-    WHERE radio_station_code IS NOT NULL
+    WHERE radio_station_code IS NOT NULL
+    ORDER BY COALESCE(radio_station_name, radio_station_code)

71-83: Consider performance optimization for large datasets.

For large datasets, the DISTINCT operation might impact performance. Consider creating an index if this becomes a bottleneck.

CREATE INDEX IF NOT EXISTS idx_audio_files_radio_station 
ON public.audio_files (radio_station_code, radio_station_name) 
WHERE radio_station_code IS NOT NULL;
supabase/database/sql/get_snippets_function.sql (2)

83-88: Consider optimizing the admin visibility filter

The nested EXISTS clause could be simplified for better readability and potentially better performance.

Consider this alternative:

-            user_is_admin OR 
-            NOT EXISTS (
-                SELECT 1 
-                FROM user_hide_snippets uhs 
-                WHERE uhs.snippet = s.id
-            )
+            user_is_admin OR uhs.snippet IS NULL

This leverages the already existing LEFT JOIN to user_hide_snippets instead of using a subquery.


255-273: Simplify ordering logic

The current implementation uses multiple CASE statements with NULL handling which makes it harder to maintain.

Consider this more concise approach:

-        ORDER BY
-          CASE 
-          WHEN p_order_by = 'upvotes' THEN (s.upvote_count + s.like_count)
-          ELSE NULL
-          END DESC NULLS LAST,
-          CASE 
-              WHEN p_order_by = 'comments' THEN s.comment_count
-              ELSE NULL
-          END DESC NULLS LAST,
-          CASE 
-              WHEN p_order_by = 'activities' THEN s.updated_at
-              ELSE NULL
-          END DESC NULLS LAST,
-          CASE 
-              WHEN p_order_by IS NULL OR p_order_by = 'latest' OR p_order_by = '' THEN s.recorded_at
-              ELSE NULL
-          END DESC NULLS LAST,
-          -- Default fallback to recorded_at if no other conditions match
-          s.recorded_at DESC NULLS LAST;
+        ORDER BY
+            CASE p_order_by
+                WHEN 'upvotes' THEN (s.upvote_count + s.like_count)
+                WHEN 'comments' THEN s.comment_count
+                WHEN 'activities' THEN extract(epoch from s.updated_at)
+                ELSE extract(epoch from s.recorded_at)
+            END DESC;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 923b9af and 67932dc.

📒 Files selected for processing (8)
  • supabase/database/sql/comment_count_migration.sql (1 hunks)
  • supabase/database/sql/get_filtering_options_function.sql (1 hunks)
  • supabase/database/sql/get_snippets_function.sql (5 hunks)
  • supabase/database/sql/like_count_migration.sql (1 hunks)
  • supabase/database/sql/like_count_trigger.sql (1 hunks)
  • supabase/database/sql/update_snippet_comment_count.sql (1 hunks)
  • supabase/database/sql/update_snippet_like_count.sql (1 hunks)
  • supabase/database/sql/update_snippet_upvote_count.sql (1 hunks)
🔇 Additional comments (9)
supabase/database/sql/like_count_trigger.sql (2)

1-4: LGTM! The trigger implementation looks correct.

The trigger timing (AFTER) and row-level execution are appropriate for maintaining like counts.

Let's verify the existence of the dependent function and related objects:

✅ Verification successful

Trigger implementation is well-designed and dependencies are in place

The verification confirms:

  • The update_snippet_like_count() function exists and is properly implemented to handle like count updates
  • An index idx_snippets_like_count exists on the target snippets.like_count column for efficient querying
  • The function implementation includes proper handling of both NEW and OLD record references, ensuring concurrency safety
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of dependent function and proper indexing

# Check for the function definition
rg -A 10 "CREATE.*FUNCTION.*update_snippet_like_count"

# Check for indexes on the tables involved
rg -A 5 "CREATE.*INDEX.*user_like_snippets"

Length of output: 997


Script:

#!/bin/bash
# Let's check for any indexes on user_like_snippets table using a broader search
rg -A 5 "CREATE.*INDEX.*like"

# Also check table definition to understand the structure
rg -A 10 "CREATE TABLE.*user_like_snippets"

# Check if there are any migrations related to indexes
fd -e sql | xargs rg "INDEX.*user_like_snippets"

Length of output: 242


1-4: Verify concurrent update handling.

Ensure that the update_snippet_like_count() function handles concurrent updates safely to prevent race conditions.

Let's check the function implementation for proper locking:

✅ Verification successful

Let me gather more information about the update_snippet_like_count function implementation:


The like count update implementation is safe for concurrent operations

The current implementation is safe from race conditions because:

  1. The trigger function uses a single atomic UPDATE operation
  2. The COUNT aggregation is performed within the same transaction as the UPDATE
  3. PostgreSQL's MVCC (Multi-Version Concurrency Control) ensures transaction isolation

The implementation correctly maintains consistency even under concurrent updates, inserts, or deletes of likes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for proper concurrency handling in the function

# Look for locking mechanisms or FOR UPDATE clauses
rg -A 10 "FOR UPDATE|LOCK TABLE|SELECT.*FROM.*snippets.*WHERE" 

Length of output: 6134


Script:

#!/bin/bash
# Search for the update_snippet_like_count function implementation
ast-grep --pattern 'CREATE OR REPLACE FUNCTION update_snippet_like_count()'

# Also try with rg to ensure we don't miss it
rg -A 15 "CREATE.*FUNCTION update_snippet_like_count"

Length of output: 1387

supabase/database/sql/like_count_migration.sql (1)

9-9: 🛠️ Refactor suggestion

Consider using CREATE INDEX CONCURRENTLY

Creating an index can block writes to the table. Using CONCURRENTLY allows the index to be built without preventing concurrent inserts, updates, or deletes.

-CREATE INDEX idx_snippets_like_count ON snippets(like_count DESC);
+CREATE INDEX CONCURRENTLY idx_snippets_like_count ON snippets(like_count DESC);

Note: The CONCURRENTLY option takes longer but is safer for production environments.

Let's check the table size to assess the impact:

supabase/database/sql/update_snippet_like_count.sql (3)

1-2: LGTM! Function signature is correctly defined.

The function is properly declared as a trigger function with the correct return type.


14-14: LGTM! Correct trigger function return value.

Returning NULL is the correct pattern for AFTER triggers.


9-10: Verify edge case handling for NULL values.

While COALESCE handles the basic NULL case, we should verify the behavior when:

  1. Both NEW and OLD are NULL
  2. The value column contains NULL instead of 0 or 1

Let's check the schema and constraints:

supabase/database/sql/get_filtering_options_function.sql (1)

71-74: LGTM! Enhanced radio station information retrieval.

The addition of radio_station_name to the DISTINCT selection provides more descriptive information about radio stations, improving the user experience.

supabase/database/sql/get_snippets_function.sql (2)

73-78: LGTM! Efficient like count calculation

The use of LATERAL join with filtered COUNT is an efficient way to calculate likes and dislikes in a single pass.


294-299: LGTM! Clear and consistent return structure

The return structure is well-organized and includes all necessary pagination information.

Comment on lines +1 to +7
UPDATE snippets s
SET comment_count = (
SELECT COUNT(*)
FROM comments c
WHERE c.room_id = s.id
and deleted_at is null
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

The SQL logic looks correct but consider adding safety measures.

While the UPDATE query is logically correct, consider these improvements for safer execution:

+ BEGIN;
+
+ -- Log start of migration
+ DO $$
+ BEGIN
+   RAISE NOTICE 'Starting comment count migration...';
+ END $$;
+
 UPDATE snippets s
 SET comment_count = (
     SELECT COUNT(*)
     FROM comments c
     WHERE c.room_id = s.id
     and deleted_at is null
 );
+
+ -- Validate the update
+ DO $$
+ DECLARE
+   invalid_count INTEGER;
+ BEGIN
+   SELECT COUNT(*) INTO invalid_count
+   FROM snippets s
+   WHERE s.comment_count != (
+     SELECT COUNT(*)
+     FROM comments c
+     WHERE c.room_id = s.id
+     AND deleted_at is null
+   );
+   
+   IF invalid_count > 0 THEN
+     RAISE EXCEPTION 'Validation failed: % snippets have incorrect comment counts', invalid_count;
+   END IF;
+   
+   RAISE NOTICE 'Migration completed successfully';
+ END $$;
+
+ COMMIT;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPDATE snippets s
SET comment_count = (
SELECT COUNT(*)
FROM comments c
WHERE c.room_id = s.id
and deleted_at is null
);
BEGIN;
-- Log start of migration
DO $$
BEGIN
RAISE NOTICE 'Starting comment count migration...';
END $$;
UPDATE snippets s
SET comment_count = (
SELECT COUNT(*)
FROM comments c
WHERE c.room_id = s.id
and deleted_at is null
);
-- Validate the update
DO $$
DECLARE
invalid_count INTEGER;
BEGIN
SELECT COUNT(*) INTO invalid_count
FROM snippets s
WHERE s.comment_count != (
SELECT COUNT(*)
FROM comments c
WHERE c.room_id = s.id
AND deleted_at is null
);
IF invalid_count > 0 THEN
RAISE EXCEPTION 'Validation failed: % snippets have incorrect comment counts', invalid_count;
END IF;
RAISE NOTICE 'Migration completed successfully';
END $$;
COMMIT;

💡 Codebase verification

Migration needs concurrent update handling

The trigger function update_snippet_comment_count uses simple increment/decrement operations which could lead to race conditions during the migration. The migration's UPDATE query should use a transaction-safe approach to count comments. Consider using:

UPDATE snippets s
SET comment_count = (
    SELECT COUNT(*)
    FROM comments c
    WHERE c.room_id = s.id
    AND deleted_at is null
    FOR UPDATE
);
  • The trigger function in update_snippet_comment_count.sql uses non-atomic comment_count + 1 and comment_count - 1 operations
  • The migration query doesn't use any concurrency control mechanisms like FOR UPDATE
  • Missing trigger creation statement that would bind this function to the comments table
🔗 Analysis chain

Verify concurrent update handling.

Ensure that the trigger function update_snippet_comment_count mentioned in the summary is properly handling concurrent updates during this migration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there's proper concurrent update handling in the trigger function

# Look for the trigger function definition and its concurrency controls
ast-grep --pattern 'CREATE OR REPLACE FUNCTION update_snippet_comment_count'

# Check if there are any other triggers or constraints on the comment_count column
rg -A 10 'CREATE TRIGGER.*comment_count'
rg -A 5 'ADD CONSTRAINT.*comment_count'

Length of output: 163


Script:

#!/bin/bash
# Let's try a broader search to find any trigger-related code
rg -i "trigger" supabase/database/sql/

# Search for any function that updates comment_count
rg -i "comment_count" supabase/database/sql/

# Look for any migration files that might contain trigger definitions
fd . supabase/database/sql/ -t f

Length of output: 2462


Script:

#!/bin/bash
# Let's examine the content of the trigger function
cat supabase/database/sql/update_snippet_comment_count.sql

# Also check if there's a trigger creation for this function
rg -A 5 "CREATE TRIGGER.*comment" supabase/database/sql/

Length of output: 575

Comment on lines +6 to +11
SET like_count = (
SELECT COUNT(*)
FROM user_like_snippets
WHERE snippet = COALESCE(NEW.snippet, OLD.snippet)
AND value = 1
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Performance optimization needed for user_like_snippets table

Based on the verification attempts, we couldn't find the table definition or any existing indexes for the user_like_snippets table in the codebase. This suggests that the recommended index on (snippet, value) is indeed missing, which could impact query performance for the COUNT operation.

  • Add an index on (snippet, value) columns in the user_like_snippets table to optimize the COUNT query
  • Consider implementing a materialized count if the table grows significantly
🔗 Analysis chain

Consider optimizing the COUNT query performance.

For large tables, counting all matching rows can become a performance bottleneck. Consider these optimizations:

  1. Add an index on (snippet, value) in the user_like_snippets table
  2. Consider maintaining a materialized count if performance becomes an issue

Let's verify if the required index exists:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the index exists on user_like_snippets table
rg -A 5 "CREATE.*INDEX.*user_like_snippets.*snippet.*value"

Length of output: 61


Script:

#!/bin/bash
# Let's check for any index definitions in the schema files
fd -e sql | xargs rg -i "CREATE.*INDEX|CREATE TABLE.*user_like_snippets"

Length of output: 3456


Script:

#!/bin/bash
# Let's check for the user_like_snippets table definition and any related indexes
rg -i "CREATE TABLE.*user_like_snippets" -A 10

Length of output: 48


Script:

#!/bin/bash
# Let's search for any mention of user_like_snippets table in migration files
rg -i "user_like_snippets" supabase/migrations/

Length of output: 47

Comment on lines +12 to +14
UPDATE snippets
SET comment_count = comment_count - 1
WHERE id = OLD.room_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent negative comment counts

The decrement operation should include a safety check to prevent negative counts.

-        UPDATE snippets
-        SET comment_count = comment_count - 1
-        WHERE id = OLD.room_id;
+        UPDATE snippets
+        SET comment_count = GREATEST(0, comment_count - 1)
+        WHERE id = OLD.room_id;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPDATE snippets
SET comment_count = comment_count - 1
WHERE id = OLD.room_id;
UPDATE snippets
SET comment_count = GREATEST(0, comment_count - 1)
WHERE id = OLD.room_id;

Comment on lines +6 to +8
UPDATE snippets
SET comment_count = comment_count + 1
WHERE id = NEW.room_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent race conditions in counter updates

The direct increment operation could lead to race conditions in concurrent scenarios. Consider using atomic updates with proper locking.

-        UPDATE snippets
-        SET comment_count = comment_count + 1
-        WHERE id = NEW.room_id;
+        UPDATE snippets
+        SET comment_count = (
+            SELECT COUNT(*)
+            FROM comments
+            WHERE room_id = NEW.room_id
+        )
+        WHERE id = NEW.room_id;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPDATE snippets
SET comment_count = comment_count + 1
WHERE id = NEW.room_id;
UPDATE snippets
SET comment_count = (
SELECT COUNT(*)
FROM comments
WHERE room_id = NEW.room_id
)
WHERE id = NEW.room_id;

Comment on lines +1 to +19
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
-- For INSERT
IF (TG_OP = 'INSERT') THEN
UPDATE snippets
SET comment_count = comment_count + 1
WHERE id = NEW.room_id;

-- For DELETE
ELSIF (TG_OP = 'DELETE') THEN
UPDATE snippets
SET comment_count = comment_count - 1
WHERE id = OLD.room_id;
END IF;

RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and validation

The function should handle several edge cases:

  1. Non-existent snippets
  2. NULL values in NEW/OLD records
  3. Failed updates
 CREATE OR REPLACE FUNCTION update_snippet_comment_count()
 RETURNS TRIGGER AS $$
 BEGIN
+    -- Validate input
+    IF (TG_OP = 'INSERT' AND NEW.room_id IS NULL) OR 
+       (TG_OP = 'DELETE' AND OLD.room_id IS NULL) THEN
+        RAISE EXCEPTION 'room_id cannot be null';
+    END IF;
+
     -- For INSERT
     IF (TG_OP = 'INSERT') THEN
+        -- Verify snippet exists
+        IF NOT EXISTS (SELECT 1 FROM snippets WHERE id = NEW.room_id) THEN
+            RAISE EXCEPTION 'Snippet with id % does not exist', NEW.room_id;
+        END IF;
+
         UPDATE snippets
-        SET comment_count = comment_count + 1
+        SET comment_count = COALESCE(comment_count, 0) + 1
         WHERE id = NEW.room_id;
+
+        IF NOT FOUND THEN
+            RAISE EXCEPTION 'Failed to update comment count for snippet %', NEW.room_id;
+        END IF;
     
     -- For DELETE
     ELSIF (TG_OP = 'DELETE') THEN
+        -- Verify snippet exists
+        IF NOT EXISTS (SELECT 1 FROM snippets WHERE id = OLD.room_id) THEN
+            RAISE EXCEPTION 'Snippet with id % does not exist', OLD.room_id;
+        END IF;
+
         UPDATE snippets
-        SET comment_count = comment_count - 1
+        SET comment_count = GREATEST(0, COALESCE(comment_count, 1) - 1)
         WHERE id = OLD.room_id;
+
+        IF NOT FOUND THEN
+            RAISE EXCEPTION 'Failed to update comment count for snippet %', OLD.room_id;
+        END IF;
     END IF;
     
     RETURN NULL;
 END;
 $$ LANGUAGE plpgsql;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
-- For INSERT
IF (TG_OP = 'INSERT') THEN
UPDATE snippets
SET comment_count = comment_count + 1
WHERE id = NEW.room_id;
-- For DELETE
ELSIF (TG_OP = 'DELETE') THEN
UPDATE snippets
SET comment_count = comment_count - 1
WHERE id = OLD.room_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
-- Validate input
IF (TG_OP = 'INSERT' AND NEW.room_id IS NULL) OR
(TG_OP = 'DELETE' AND OLD.room_id IS NULL) THEN
RAISE EXCEPTION 'room_id cannot be null';
END IF;
-- For INSERT
IF (TG_OP = 'INSERT') THEN
-- Verify snippet exists
IF NOT EXISTS (SELECT 1 FROM snippets WHERE id = NEW.room_id) THEN
RAISE EXCEPTION 'Snippet with id % does not exist', NEW.room_id;
END IF;
UPDATE snippets
SET comment_count = COALESCE(comment_count, 0) + 1
WHERE id = NEW.room_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Failed to update comment count for snippet %', NEW.room_id;
END IF;
-- For DELETE
ELSIF (TG_OP = 'DELETE') THEN
-- Verify snippet exists
IF NOT EXISTS (SELECT 1 FROM snippets WHERE id = OLD.room_id) THEN
RAISE EXCEPTION 'Snippet with id % does not exist', OLD.room_id;
END IF;
UPDATE snippets
SET comment_count = GREATEST(0, COALESCE(comment_count, 1) - 1)
WHERE id = OLD.room_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Failed to update comment count for snippet %', OLD.room_id;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

💡 Codebase verification

Missing trigger setup for comment count updates

The function update_snippet_comment_count() is defined but the corresponding trigger to execute this function is missing. While we found:

  • A migration file that initializes comment counts
  • Several other triggers in the system
  • A similar pattern with the like count trigger

The trigger to execute update_snippet_comment_count() after INSERT or DELETE on the comments table needs to be created.

Add the following trigger definition:

CREATE TRIGGER update_comment_count
AFTER INSERT OR DELETE ON comments
FOR EACH ROW
EXECUTE FUNCTION update_snippet_comment_count();
🔗 Analysis chain

Verify trigger setup and initialization

Let's verify the trigger setup and initial comment counts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify trigger setup and initialization

# Check if trigger exists and is properly configured
echo "Checking trigger configuration..."
rg -A 10 "CREATE TRIGGER.*update_snippet_comment_count"

# Look for comment_count column definition
echo "Checking comment_count column..."
rg -A 5 "comment_count.*snippets"

# Check for initialization/migration scripts
echo "Checking initialization scripts..."
fd -e sql | rg -i "migration|init.*comment.*count"

Length of output: 527


Script:

#!/bin/bash
# Let's check the content of these migration files and any trigger setup

echo "Checking comment count migration content..."
cat supabase/database/sql/comment_count_migration.sql

echo -e "\nChecking remote schema migration..."
cat supabase/migrations/20241029135348_remote_schema.sql | grep -A 10 -B 10 "comment_count\|update_snippet_comment_count"

echo -e "\nChecking snippets table definition..."
rg "CREATE TABLE.*snippets" -A 15

Length of output: 627


Script:

#!/bin/bash
# Let's try a different approach to find the trigger setup and table definition

# Search for trigger creation
echo "Searching for trigger creation..."
rg "CREATE TRIGGER" -A 5

# Look for snippets table with different patterns
echo -e "\nSearching for snippets table..."
rg "CREATE TABLE.*snippets|ALTER TABLE.*snippets" -A 15

# Check for comment_count column addition
echo -e "\nSearching for comment_count column addition..."
rg "ALTER TABLE.*ADD.*comment_count|comment_count.*INTEGER"

Length of output: 4025

Comment on lines +1 to +5
CREATE OR REPLACE FUNCTION update_snippet_upvote_count()
RETURNS TRIGGER AS $$
DECLARE
snippet_id UUID;
BEGIN
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add trigger creation statement

The trigger function is defined but the corresponding trigger creation statement (CREATE TRIGGER) is missing. This should specify when the trigger executes (BEFORE/AFTER) and for which operations (INSERT/UPDATE/DELETE).

Add the trigger creation statement. Example:

CREATE TRIGGER update_snippet_upvote_count_trigger
AFTER INSERT OR DELETE OR UPDATE ON label_upvotes
FOR EACH ROW
EXECUTE FUNCTION update_snippet_upvote_count();

Comment on lines +7 to +9
SELECT snippet INTO snippet_id
FROM snippet_labels
WHERE id = COALESCE(NEW.snippet_label, OLD.snippet_label);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for invalid snippet_label

The query silently continues if the snippet_label is invalid. Consider adding explicit error handling to maintain data integrity.

    SELECT snippet INTO snippet_id
    FROM snippet_labels
    WHERE id = COALESCE(NEW.snippet_label, OLD.snippet_label);
+   IF NOT FOUND THEN
+       RAISE EXCEPTION 'Invalid snippet_label: %', COALESCE(NEW.snippet_label, OLD.snippet_label);
+   END IF;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SELECT snippet INTO snippet_id
FROM snippet_labels
WHERE id = COALESCE(NEW.snippet_label, OLD.snippet_label);
SELECT snippet INTO snippet_id
FROM snippet_labels
WHERE id = COALESCE(NEW.snippet_label, OLD.snippet_label);
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid snippet_label: %', COALESCE(NEW.snippet_label, OLD.snippet_label);
END IF;

Comment on lines +13 to +20
UPDATE snippets
SET upvote_count = (
SELECT COUNT(*)
FROM label_upvotes lu
JOIN snippet_labels sl ON lu.snippet_label = sl.id
WHERE sl.snippet = snippet_id
)
WHERE id = snippet_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add indexes to optimize label upvote queries

Based on the codebase analysis, the tables are frequently joined and queried, but only one index (idx_snippets_like_count) exists. The following indexes should be added to optimize the upvote count calculation and related queries:

  • snippet_labels(snippet): Used in multiple JOIN operations and WHERE clauses
  • label_upvotes(snippet_label): Used in JOIN operations and COUNT queries
  • label_upvotes(upvoted_by, snippet_label): Used in unique constraint and filtering
CREATE INDEX IF NOT EXISTS idx_snippet_labels_snippet ON snippet_labels(snippet);
CREATE INDEX IF NOT EXISTS idx_label_upvotes_snippet_label ON label_upvotes(snippet_label);
CREATE UNIQUE INDEX IF NOT EXISTS idx_label_upvotes_user_label ON label_upvotes(upvoted_by, snippet_label);
🔗 Analysis chain

Optimize query performance with appropriate indexes

The COUNT subquery with JOIN could be performance-intensive. Ensure proper indexes exist on the join columns and frequently queried fields.

Let's verify the existing indexes:

Consider adding these indexes if not present:

CREATE INDEX IF NOT EXISTS idx_snippet_labels_snippet ON snippet_labels(snippet);
CREATE INDEX IF NOT EXISTS idx_label_upvotes_snippet_label ON label_upvotes(snippet_label);
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing indexes on relevant columns

# Search for index definitions
rg -A 3 'CREATE.*INDEX' supabase/database/sql/

# Search for table definitions to verify primary keys
rg -A 10 'CREATE TABLE (snippet_labels|label_upvotes|snippets)' supabase/database/sql/

Length of output: 251


Script:

#!/bin/bash
# Let's search for table definitions in all SQL files
rg -A 10 'CREATE TABLE' supabase/database/sql/

# Also search for any foreign key constraints that might imply indexes
rg -B 2 -A 2 'FOREIGN KEY' supabase/database/sql/

# Search specifically for label_upvotes and snippet_labels related definitions
rg -A 10 '(label_upvotes|snippet_labels)' supabase/database/sql/

Length of output: 22235

Comment on lines +1 to +25
CREATE OR REPLACE FUNCTION update_snippet_upvote_count()
RETURNS TRIGGER AS $$
DECLARE
snippet_id UUID;
BEGIN
-- Get the snippet_id from snippet_labels
SELECT snippet INTO snippet_id
FROM snippet_labels
WHERE id = COALESCE(NEW.snippet_label, OLD.snippet_label);

-- Update the upvote count for the affected snippet
IF snippet_id IS NOT NULL THEN
UPDATE snippets
SET upvote_count = (
SELECT COUNT(*)
FROM label_upvotes lu
JOIN snippet_labels sl ON lu.snippet_label = sl.id
WHERE sl.snippet = snippet_id
)
WHERE id = snippet_id;
END IF;

RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing trigger definition for upvote count updates

The function update_snippet_upvote_count() is defined but there's no corresponding trigger to execute it. The function needs to be triggered after INSERT, UPDATE, or DELETE operations on the label_upvotes table to maintain accurate upvote counts.

  • Add a trigger definition similar to the like count trigger:
CREATE TRIGGER update_upvote_count
AFTER INSERT OR UPDATE OR DELETE ON label_upvotes
FOR EACH ROW
EXECUTE FUNCTION update_snippet_upvote_count();
🔗 Analysis chain

Verify trigger execution order with related triggers

This trigger function is part of a set of related triggers (comment_count, like_count). Ensure proper trigger execution order to maintain data consistency.


Let me gather more information about the trigger definitions for all these functions.


Let me try one more search to find the trigger definition for the upvote count function and table definitions using a different pattern.


Let me try one final search with a different approach to find all SQL files and their content.


Let me check the content of the relevant files for trigger definitions and table relationships.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for related triggers and their execution order

# Search for other trigger definitions
rg -A 5 'CREATE TRIGGER' supabase/database/sql/

# Search for related trigger functions
rg -A 5 'update_snippet.*_count\(\)' supabase/database/sql/

Length of output: 2003


Script:

#!/bin/bash
# Search for trigger definitions for all count-related functions
rg -A 3 'CREATE TRIGGER.*update_.*_count' supabase/database/sql/

# Search for table definitions to understand the relationships
rg -A 10 'CREATE TABLE (snippets|snippet_labels|label_upvotes)' supabase/database/sql/

Length of output: 482


Script:

#!/bin/bash
# Search for trigger definition for upvote count
rg -A 3 'CREATE TRIGGER' supabase/database/sql/update_snippet_upvote_count.sql

# Search for table definitions with a broader pattern
rg -A 10 'CREATE TABLE' supabase/database/sql/schema.sql

Length of output: 212


Script:

#!/bin/bash
# Find all SQL files in the database directory
fd -e sql . supabase/database/

# Search for trigger definition in all SQL files
rg -A 3 'CREATE TRIGGER.*upvote' supabase/database/

# Search for table definitions in all SQL files
rg -A 5 'CREATE TABLE' supabase/database/

Length of output: 1670


Script:

#!/bin/bash
# Check content of upvote-related files
cat supabase/database/sql/update_snippet_upvote_count.sql
cat supabase/database/sql/toggle_upvote_label_function.sql

# Check content of table definitions in migration files
cat supabase/database/sql/comment_count_migration.sql
cat supabase/database/sql/like_count_migration.sql

Length of output: 3621

Comment on lines +212 to +254
AND (
p_filter IS NULL OR
NOT p_filter ? 'upvotedBy' OR
(
CASE
WHEN jsonb_array_length(p_filter->'upvotedBy') = 0 THEN TRUE
ELSE (
CASE
WHEN (
p_filter->'upvotedBy' ? 'by_me' AND
p_filter->'upvotedBy' ? 'by_others'
) THEN
EXISTS (
SELECT 1
FROM label_upvotes lu
WHERE lu.snippet_label IN (
SELECT id FROM snippet_labels WHERE snippet = s.id
)
)
WHEN p_filter->'upvotedBy' ? 'by_me' THEN
EXISTS (
SELECT 1
FROM label_upvotes lu
WHERE lu.snippet_label IN (
SELECT id FROM snippet_labels WHERE snippet = s.id
)
AND lu.upvoted_by = current_user_id
)
WHEN p_filter->'upvotedBy' ? 'by_others' THEN
EXISTS (
SELECT 1
FROM label_upvotes lu
WHERE lu.snippet_label IN (
SELECT id FROM snippet_labels WHERE snippet = s.id
)
AND lu.upvoted_by != current_user_id
)
ELSE FALSE
END
)
END
)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize upvotedBy filter performance

The current implementation uses multiple similar subqueries which could impact performance with large datasets.

Consider using a CTE to improve performance:

+    WITH label_upvotes_info AS (
+        SELECT DISTINCT
+            sl.snippet,
+            bool_or(lu.upvoted_by = current_user_id) as upvoted_by_me,
+            bool_or(lu.upvoted_by != current_user_id) as upvoted_by_others
+        FROM snippet_labels sl
+        LEFT JOIN label_upvotes lu ON lu.snippet_label = sl.id
+        GROUP BY sl.snippet
+    )
     -- Replace the entire upvotedBy filter with:
     AND (
         p_filter IS NULL OR
         NOT p_filter ? 'upvotedBy' OR
         EXISTS (
             SELECT 1
             FROM label_upvotes_info lui
             WHERE lui.snippet = s.id
             AND (
                 (p_filter->'upvotedBy' ? 'by_me' AND lui.upvoted_by_me) OR
                 (p_filter->'upvotedBy' ? 'by_others' AND lui.upvoted_by_others)
             )
         )
     )

Committable suggestion skipped: line range outside the PR's diff.

AND uls.value = 1
);

CREATE INDEX idx_snippets_like_count ON snippets(like_count DESC);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps creating index for comment_count and upvote_count as well? 🤔

-- For INSERT
IF (TG_OP = 'INSERT') THEN
UPDATE snippets
SET comment_count = comment_count + 1
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to recalculate this (instead of incrementing by "1")

Comment thread supabase/database/sql/like_count_trigger.sql
)
ORDER BY
CASE
WHEN p_order_by = 'upvotes' THEN (s.upvote_count + s.like_count)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some indentation here to make the case-when statement here more readable

@nhphong
Copy link
Copy Markdown
Collaborator

nhphong commented Nov 25, 2024

Overall, this PR looks good to me. 💯

# Conflicts:
#	supabase/database/sql/get_snippets_function.sql
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
supabase/database/sql/get_snippets_function.sql (1)

83-88: Improve code formatting for better readability

The EXISTS clause indentation is inconsistent with the surrounding code.

 AND (
     -- If user is admin, show all snippets (including hidden ones)
     -- If user is not admin, only show non-hidden snippets
-            user_is_admin OR 
-            NOT EXISTS (
-                SELECT 1 
-                FROM user_hide_snippets uhs 
-                WHERE uhs.snippet = s.id
-            )
+    user_is_admin OR 
+    NOT EXISTS (
+        SELECT 1 
+        FROM user_hide_snippets uhs 
+        WHERE uhs.snippet = s.id
+    )
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 67932dc and 20423b9.

📒 Files selected for processing (1)
  • supabase/database/sql/get_snippets_function.sql (6 hunks)
🔇 Additional comments (4)
supabase/database/sql/get_snippets_function.sql (4)

73-76: LGTM! Efficient implementation of like counts

The use of FILTER clause for counting likes/dislikes is an efficient approach.


213-254: LGTM with previous optimization suggestion

The upvotedBy filter implementation is functionally correct.

Referring to previous optimization suggestion using CTEs for better performance.


267-285: LGTM with previous validation suggestion

The ordering logic implementation is correct.

Referring to previous suggestion about adding input validation for p_order_by parameter.


306-311: LGTM! Improved return structure

The simplified return structure with direct use of total_count is more accurate and maintainable.

Comment on lines +1 to +2
CREATE
OR REPLACE FUNCTION get_snippets (
page INTEGER DEFAULT 0,
page_size INTEGER DEFAULT 10,
p_language TEXT DEFAULT 'english',
p_filter JSONB DEFAULT '{}'::jsonb
) RETURNS jsonb SECURITY DEFINER AS $$
OR REPLACE FUNCTION get_snippets_order_by (p_language text,p_filter jsonb,page INTEGER,page_size INTEGER, p_order_by text) RETURNS jsonb SECURITY DEFINER AS $$
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Breaking changes in function signature require attention

The function signature changes will break existing callers:

  1. Removal of default values for parameters
  2. Changed parameter order
  3. Function renamed from get_snippets to get_snippets_order_by

Consider:

  1. Creating a new function instead of replacing the existing one
  2. Or maintaining backward compatibility by keeping default values:
-CREATE OR REPLACE FUNCTION get_snippets_order_by (p_language text,p_filter jsonb,page INTEGER,page_size INTEGER, p_order_by text)
+CREATE OR REPLACE FUNCTION get_snippets_order_by (
+    p_language text DEFAULT 'english',
+    p_filter jsonb DEFAULT '{}'::jsonb,
+    page INTEGER DEFAULT 0,
+    page_size INTEGER DEFAULT 10,
+    p_order_by text DEFAULT 'latest'
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE
OR REPLACE FUNCTION get_snippets (
page INTEGER DEFAULT 0,
page_size INTEGER DEFAULT 10,
p_language TEXT DEFAULT 'english',
p_filter JSONB DEFAULT '{}'::jsonb
) RETURNS jsonb SECURITY DEFINER AS $$
OR REPLACE FUNCTION get_snippets_order_by (p_language text,p_filter jsonb,page INTEGER,page_size INTEGER, p_order_by text) RETURNS jsonb SECURITY DEFINER AS $$
CREATE
OR REPLACE FUNCTION get_snippets_order_by (
p_language text DEFAULT 'english',
p_filter jsonb DEFAULT '{}'::jsonb,
page INTEGER DEFAULT 0,
page_size INTEGER DEFAULT 10,
p_order_by text DEFAULT 'latest'
) RETURNS jsonb SECURITY DEFINER AS $$

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (1)
supabase/database/sql/upvote_count_migration.sql (1)

1-13: Ensure transaction safety for the migration

This migration performs a bulk update of the snippets table. While most migration systems automatically wrap migrations in transactions, it's worth verifying that this is the case in your setup to ensure atomic updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 20423b9 and f337a0e.

📒 Files selected for processing (3)
  • supabase/database/sql/comment_count_migration.sql (1 hunks)
  • supabase/database/sql/update_snippet_comment_count.sql (1 hunks)
  • supabase/database/sql/upvote_count_migration.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • supabase/database/sql/comment_count_migration.sql
🔇 Additional comments (2)
supabase/database/sql/upvote_count_migration.sql (2)

13-13: Verify index usage and existing indexes

The descending index will optimize sorting by upvote_count, but we should verify:

  1. That there are no overlapping indexes
  2. That the queries will actually use this index
✅ Verification successful

Index creation is appropriate and will be utilized

The index creation is verified to be beneficial because:

  1. No overlapping indexes exist on the upvote_count column
  2. The index will be used by the get_snippets_function.sql which sorts by upvote_count when p_order_by = 'upvotes'
  3. The column is actively maintained by a trigger function update_snippet_upvote_count()
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing indexes on snippets table
rg -A 5 "CREATE.*INDEX.*ON snippets"

# Check if there are ORDER BY upvote_count queries that will use this index
rg -A 5 "ORDER BY.*upvote_count"

Length of output: 436


Script:

#!/bin/bash
# Let's check SQL files and views for queries that might use upvote_count in WHERE or ORDER BY
rg -t sql "upvote_count" -B 2 -A 2

# Also check for any functions or stored procedures that might use upvote_count
ast-grep --pattern 'CREATE FUNCTION $$$upvote_count$$$'

# Let's also check application code for potential query usage
rg "upvote_count" -g "!*.sql" -B 2 -A 2

Length of output: 1995


1-7: Verify handling of soft-deleted records

The CTE logic for counting upvotes is correct, but we should verify if there's a need to handle soft-deleted records in either snippet_labels or label_upvotes tables.

✅ Verification successful

Based on the schema information from the output, I can now provide a definitive response about the soft deletion concern:

No need to handle soft-deleted records

The schema shows that neither snippet_labels nor label_upvotes tables have soft delete columns (like deleted_at or is_deleted). The only mention of deleted_at in the codebase is in an unrelated comment_count_migration.sql file. Therefore, the current upvote counting logic in the CTE is correct as is.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if these tables have soft delete columns
rg -A 5 "CREATE TABLE (snippet_labels|label_upvotes)" 

Length of output: 55


Script:

#!/bin/bash
# Let's try a broader search for table definitions
fd -e sql | xargs rg -l "CREATE TABLE"

Length of output: 40


Script:

#!/bin/bash
# Search for any mentions of deleted/active status in SQL files
fd -e sql | xargs rg -i "(deleted_at|is_deleted|active|status)"

Length of output: 3659


Script:

#!/bin/bash
# Look for table definitions in schema files
fd "schema|table" -e sql | xargs cat

Length of output: 61650

Comment on lines +1 to +14
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE snippets
SET comment_count = (
SELECT COUNT(*)
FROM comments
WHERE room_id = COALESCE(NEW.room_id, OLD.room_id)
)
WHERE id = COALESCE(NEW.room_id, OLD.room_id);

RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and error handling

While the recalculation approach is good, the function needs additional safeguards:

 CREATE OR REPLACE FUNCTION update_snippet_comment_count() 
 RETURNS TRIGGER AS $$
 BEGIN
+    -- Validate input
+    IF (TG_OP = 'INSERT' AND NEW.room_id IS NULL) OR 
+       (TG_OP = 'DELETE' AND OLD.room_id IS NULL) THEN
+        RAISE EXCEPTION 'room_id cannot be null';
+    END IF;
+
+    -- Verify snippet exists
+    IF NOT EXISTS (
+        SELECT 1 
+        FROM snippets 
+        WHERE id = COALESCE(NEW.room_id, OLD.room_id)
+    ) THEN
+        RAISE EXCEPTION 'Snippet with id % does not exist', 
+            COALESCE(NEW.room_id, OLD.room_id);
+    END IF;
+
     UPDATE snippets
     SET comment_count = (
         SELECT COUNT(*)
         FROM comments 
-        WHERE room_id = COALESCE(NEW.room_id, OLD.room_id)
+        WHERE room_id = COALESCE(NEW.room_id, OLD.room_id)
+        AND deleted_at IS NULL  -- Only count non-deleted comments
     )
     WHERE id = COALESCE(NEW.room_id, OLD.room_id);
+
+    IF NOT FOUND THEN
+        RAISE EXCEPTION 'Failed to update comment count for snippet %',
+            COALESCE(NEW.room_id, OLD.room_id);
+    END IF;
     
     RETURN NULL;
 END;
 $$ LANGUAGE plpgsql;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE snippets
SET comment_count = (
SELECT COUNT(*)
FROM comments
WHERE room_id = COALESCE(NEW.room_id, OLD.room_id)
)
WHERE id = COALESCE(NEW.room_id, OLD.room_id);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_snippet_comment_count()
RETURNS TRIGGER AS $$
BEGIN
-- Validate input
IF (TG_OP = 'INSERT' AND NEW.room_id IS NULL) OR
(TG_OP = 'DELETE' AND OLD.room_id IS NULL) THEN
RAISE EXCEPTION 'room_id cannot be null';
END IF;
-- Verify snippet exists
IF NOT EXISTS (
SELECT 1
FROM snippets
WHERE id = COALESCE(NEW.room_id, OLD.room_id)
) THEN
RAISE EXCEPTION 'Snippet with id % does not exist',
COALESCE(NEW.room_id, OLD.room_id);
END IF;
UPDATE snippets
SET comment_count = (
SELECT COUNT(*)
FROM comments
WHERE room_id = COALESCE(NEW.room_id, OLD.room_id)
AND deleted_at IS NULL -- Only count non-deleted comments
)
WHERE id = COALESCE(NEW.room_id, OLD.room_id);
IF NOT FOUND THEN
RAISE EXCEPTION 'Failed to update comment count for snippet %',
COALESCE(NEW.room_id, OLD.room_id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

💡 Codebase verification

⚠️ Potential issue

Create trigger to execute the update_snippet_comment_count function

Add the following trigger definition to the same SQL file:

CREATE TRIGGER update_comment_count
AFTER INSERT OR DELETE ON comments
FOR EACH ROW
EXECUTE FUNCTION update_snippet_comment_count();

Based on the verification:

  1. The trigger is indeed missing from the codebase
  2. Similar trigger patterns exist (e.g., update_like_count trigger in like_count_trigger.sql)
  3. The function exists but is not being executed by any trigger
🔗 Analysis chain

Create trigger to execute the function

The trigger function is defined but the corresponding trigger is missing.

Add the following trigger definition:

CREATE TRIGGER update_comment_count
AFTER INSERT OR DELETE ON comments
FOR EACH ROW
EXECUTE FUNCTION update_snippet_comment_count();
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if trigger exists
rg "CREATE TRIGGER.*update_snippet_comment_count"

Length of output: 51


Script:

#!/bin/bash
# Let's search for any trigger definitions in SQL files
fd -e sql -x cat {} | rg -i "CREATE\s+TRIGGER"

# Also search for any files that might contain trigger definitions
fd -e sql | grep -i "trigger"

Length of output: 1997


Script:

#!/bin/bash
# Let's check the content of like_count_trigger.sql as it might have a similar pattern
cat supabase/database/sql/like_count_trigger.sql

# Also let's check for any other files that might contain our function name
fd -e sql -x cat {} | rg -A 5 "update_snippet_comment_count"

Length of output: 417

Comment on lines +8 to +11
UPDATE snippets s
SET upvote_count = COALESCE(uc.total_upvotes, 0)
FROM upvote_counts uc
WHERE s.id = uc.snippet_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider RIGHT JOIN to update all snippets

The current UPDATE might miss snippets that have no entries in snippet_labels. Consider modifying the CTE to start from the snippets table to ensure all snippets get their upvote_count updated.

 WITH upvote_counts AS (
-    SELECT sl.snippet AS snippet_id,
+    SELECT s.id AS snippet_id,
            COUNT(lu.id) AS total_upvotes
-    FROM snippet_labels sl
-    LEFT JOIN label_upvotes lu ON lu.snippet_label = sl.id
-    GROUP BY sl.snippet
+    FROM snippets s
+    LEFT JOIN snippet_labels sl ON sl.snippet = s.id
+    LEFT JOIN label_upvotes lu ON lu.snippet_label = sl.id
+    GROUP BY s.id
 )

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
supabase/database/sql/get_snippets_function.sql (2)

216-252: Improve readability with consistent indentation

The nested CASE statements would be more readable with consistent indentation:

-                CASE
-                    WHEN jsonb_array_length(p_filter->'upvotedBy') = 0 THEN TRUE
-                    ELSE (
-                        CASE
-                            WHEN (
-                                p_filter->'upvotedBy' ? 'by_me' AND
-                                p_filter->'upvotedBy' ? 'by_others'
-                            ) THEN
+                CASE
+                    WHEN jsonb_array_length(p_filter->'upvotedBy') = 0 THEN TRUE
+                    ELSE (
+                        CASE
+                            WHEN (p_filter->'upvotedBy' ? 'by_me' AND
+                                 p_filter->'upvotedBy' ? 'by_others') THEN

268-293: Consider performance optimization for sorting logic

The current implementation has several areas for improvement:

  1. Consider adding index hints for commonly used sorting columns
  2. Document the sorting priority and fallback behavior

Consider refactoring to:

+        -- Add index hints for commonly used columns
+        /*+
+            INDEX(s snippets_recorded_at_idx)
+            INDEX(s snippets_updated_at_idx)
+            INDEX(s snippets_engagement_idx)
+        */
         ORDER BY (
             CASE WHEN p_order_by = 'upvotes'
                  THEN s.upvote_count + s.like_count
             END DESC,

Also, consider creating a composite index for engagement sorting:

CREATE INDEX snippets_engagement_idx ON snippets (upvote_count + like_count DESC, recorded_at DESC);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f337a0e and 1f43bb9.

📒 Files selected for processing (1)
  • supabase/database/sql/get_snippets_function.sql (4 hunks)
🔇 Additional comments (2)
supabase/database/sql/get_snippets_function.sql (2)

314-319: LGTM! Return structure improvements

The simplified return structure with direct use of total_count improves clarity and correctness.


2-2: Verify security implications of SECURITY DEFINER

The function uses SECURITY DEFINER which executes with the privileges of the function owner. Ensure:

  1. Proper access controls are in place
  2. SQL injection vectors are properly handled
✅ Verification successful

Let me analyze the results and generate additional verification scripts to check for proper input validation and sanitization.


Based on the analysis of the results, I can now provide a final response regarding the security implications:

Security concerns with SECURITY DEFINER are properly addressed

The function implementation shows proper security controls:

  1. Input validation is handled through:

    • Language parameter is validated via explicit CASE statements
    • Order by parameter is restricted to specific values ('upvotes', 'comments', 'activities', 'latest')
    • Filter parameter is type-safe through JSONB type and accessed via -> operator
    • No dynamic SQL execution (no EXECUTE statements)
  2. Access control is enforced:

    • User authentication check via auth.uid()
    • Admin role verification through user_roles
    • All database access is done through proper SQL constructs without string concatenation
  3. SQL injection prevention:

    • Uses parameterized values
    • Employs COALESCE for null safety
    • All string comparisons use exact matching
    • No user input is used in dynamic SQL

The SECURITY DEFINER usage here follows security best practices with proper input validation and access controls.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential security issues in function definition and usage

# Check for other SECURITY DEFINER functions that might be chained
rg -l "SECURITY\s+DEFINER" --type sql

# Check for proper input validation
rg -A 5 "p_(language|filter|order_by)" --type sql

# Check for usage of dynamic SQL (potential SQL injection vector)
rg "EXECUTE|EXECUTE\s+IMMEDIATE|EXEC" --type sql

Length of output: 26442


Script:

#!/bin/bash
# Check for input validation and sanitization in the get_snippets function

# Check how p_language is validated
rg -A 10 "p_language\s*:=|p_language\s*=|p_language\s+text" --type sql

# Check how p_filter is validated/sanitized
rg -A 10 "p_filter\s*:=|p_filter\s*=|p_filter\s+jsonb" --type sql

# Check how p_order_by is validated
rg -A 10 "p_order_by\s*:=|p_order_by\s*=|p_order_by\s+text" --type sql

# Check for any SQL injection prevention patterns
rg "quote_literal|quote_ident|COALESCE|NULLIF" --type sql

Length of output: 13394

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants